diff --git a/CONTROL/control b/CONTROL/control index 4e162da9..5661e5e3 100644 --- a/CONTROL/control +++ b/CONTROL/control @@ -1,5 +1,5 @@ Package: enigma2-plugin-extensions-foreca-one -Version: 1.3.1 +Version: 1.3.2 Section: base Priority: optional Architecture: all diff --git a/CONTROL/preinst b/CONTROL/preinst index 6b86c125..e2e81129 100755 --- a/CONTROL/preinst +++ b/CONTROL/preinst @@ -31,14 +31,15 @@ else echo "✓ No previous installation found" fi -# Determine python command and install dependencies +# ForecaOne requires Python 3; install dependencies accordingly echo "Installing required dependencies..." if [ -e "/usr/bin/python3" ]; then PY="python3" echo "✓ Using Python 3" else - PY="python" - echo "✓ Using Python 2.7" + echo "ERROR: Python 3 is required but was not found (/usr/bin/python3 missing)." + echo "ForecaOne only supports Python 3." + exit 1 fi # Update package list and install dependencies diff --git a/CONTROL/prerm b/CONTROL/prerm index cdbecfbd..8b9dc47a 100755 --- a/CONTROL/prerm +++ b/CONTROL/prerm @@ -1,3 +1,3 @@ #!/bin/bash -echo 'Removing package : Foreca v.1.0.0' +echo 'Removing package : Foreca v.1.3.2' exit 0 diff --git a/README.md b/README.md index 261310bb..f40558e2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Enigma2 Plugin - Version + Version License @@ -205,6 +205,15 @@ ## Installation +### Automatic (recommended) +Download and run `installer.sh` directly on your Enigma2 box. It detects your image/OS, installs the required dependencies (requests, Pillow, etc.), and copies the plugin files for you: +``` +wget --no-check-certificate 'https://github.com/Belfagor2005/ForecaOne/raw/main/installer.sh' -O installer.sh +chmod +x installer.sh +./installer.sh +``` + +### Manual 1. Copy the `Foreca1` folder to your Enigma2 plugins directory: ``` /usr/lib/enigma2/python/Plugins/Extensions/ @@ -213,7 +222,8 @@ ``` chmod -R 755 /usr/lib/enigma2/python/Plugins/Extensions/Foreca1 ``` -3. Restart Enigma2 or the plugin menu to make the plugin visible. +3. Install the required dependencies yourself (`requests`, `Pillow`) using your image's package manager (`opkg`/`apt-get`). +4. Restart Enigma2 or the plugin menu to make the plugin visible. ## Initial Configuration diff --git a/installer.sh b/installer.sh index d044313b..8ad6d3b2 100755 --- a/installer.sh +++ b/installer.sh @@ -1,7 +1,7 @@ #!/bin/bash -version='1.3.1' -changelog='Introduce configurable translation engine allowing users to choose between gettext (local .po files) and Google Translate for UI translations. Features include:\n- New translation engine config option in plugin settings\n- Support for 100+ languages via Google Translate with automatic system language detection\n- Placeholder preservation logic to handle format strings in translated text\n- New "Translation Settings" menu item in main interface\n- Improved translation function with fallback mechanisms and error handling\n- Version bump to 1.3.0\n- Code formatting improvements (f-string to % formatting for Python 2 compatibility)\n- Enhanced documentation with section headers and docstrings' +version='1.3.2' +changelog='Bug fix release following a full repository audit:\n- Fixed crash bugs in day navigation, wind-symbol maps, and the map layer menu\n- Removed hardcoded API credentials and tightened saved config file permissions\n- Validated third-party API responses before use and URL-encoded search input\n- Fixed UI freezes on weather refresh, meteogram, and lunar calendar screens by backgrounding network/CPU-heavy work\n- Enforced the Python 3 requirement in install scripts (fixes silent failures on Python-2-only systems)\n- Removed dead code and fixed missing Pillow dependency in the opkg install path' TMPPATH=/tmp/ForecaOne-install FILEPATH=/tmp/ForecaOne-main.tar.gz @@ -92,23 +92,14 @@ if ! command -v wget >/dev/null 2>&1; then esac fi -if python --version 2>&1 | grep -q '^Python 3\.'; then - echo "Python3 image detected" - PYTHON="PY3" - Packagesix="python3-six" - Packagerequests="python3-requests" - Packagepillow="python3-pillow" -else - echo "Python2 image detected" - PYTHON="PY2" - Packagerequests="python-requests" - Packagepillow="python-pillow" - if [ "$OSTYPE" = "DreamOs" ] || [ "$OSTYPE" = "Debian" ]; then - Packagesix="python-six" - else - Packagesix="python-six" - fi +if ! command -v python3 >/dev/null 2>&1 || ! python3 --version 2>&1 | grep -q '^Python 3\.'; then + echo "ERROR: Python 3 is required but was not found on this system." + echo "ForecaOne only supports Python 3 (see README.md)." + exit 1 fi +echo "Python3 image detected: $(python3 --version 2>&1)" +Packagerequests="python3-requests" +Packagepillow="python3-pillow" install_pkg() { local pkg=$1 @@ -130,8 +121,8 @@ install_pkg() { fi } -[ "$PYTHON" = "PY3" ] && install_pkg "$Packagesix" install_pkg "$Packagerequests" +install_pkg "$Packagepillow" if [ "$OSTYPE" = "OE" ]; then echo "Installing additional dependencies for OpenEmbedded..." @@ -222,7 +213,7 @@ fi [ -z "$distro_value" ] && distro_value="Unknown" [ -z "$distro_version" ] && distro_version="Unknown" -python_vers=$(python --version 2>&1) +python_vers=$(python3 --version 2>&1) cat < 0: - merged = self.merge_tile_grid(tile_paths) - if merged and callback: - callback(merged) - else: - if DEBUG: - print("[Foreca1] No valid tiles downloaded") - from twisted.internet import reactor - reactor.callFromThread(self._show_no_tiles_error) - callback(None) - - Thread(target=download_thread).start() - def check_credentials(self): """Check if credentials are configured""" return bool(self.user and self.password) diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py index 2a99730a..89b14040 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py @@ -1009,7 +1009,13 @@ def Fav2(self): self._load_favorite(2, self.path_loc2) def _update_fav_button_names(self): - """Update the buttons with city names (truncated to 11 characters)""" + """Update the buttons with city names (truncated to 11 characters). + Fetches names (network calls) in a background thread; only the + final widget update runs on the UI thread. + """ + Thread(target=self._update_fav_button_names_worker).start() + + def _update_fav_button_names_worker(self): fav_paths = [self.path_loc0, self.path_loc1, self.path_loc2] button_names = [] @@ -1056,6 +1062,11 @@ def _update_fav_button_names(self): print(f"[DEBUG] Error Fav {i}: {e}") button_names.append(f"Fav{i}") + from twisted.internet import reactor + reactor.callFromThread(self._apply_fav_button_names, button_names) + + def _apply_fav_button_names(self, button_names): + """Set the favorite button labels. Runs on the UI thread.""" # Assign: 0=home(blue), 1=fav1(green), 2=fav2(yellow) self["key_blue"].setText(_(button_names[0])) self["key_green"].setText(_(button_names[1])) @@ -1071,6 +1082,8 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None): """Load data for the specified favorite. path_loc can be just the ID or "ID/Name_with_underscores". forced_name can be used to override the name. + Kicks off the (blocking) fetch in a background thread so the UI + doesn't freeze while waiting on the network. """ if self.weather_anim_running: self.weather_anim_timer.stop() @@ -1084,6 +1097,20 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None): self.windspeed_anim_timer.stop() self.windspeed_anim_running = False + self.myloc = fav_index + + # Sequence guard: if a newer _load_favorite call comes in before this + # one finishes (e.g. rapid arrow-key presses), the older worker's + # results are discarded instead of overwriting the newer ones. + self._load_favorite_seq = getattr(self, '_load_favorite_seq', 0) + 1 + seq = self._load_favorite_seq + Thread( + target=self._load_favorite_worker, + args=(fav_index, path_loc, forced_name, seq) + ).start() + + def _load_favorite_worker(self, fav_index, path_loc, forced_name, seq): + """Fetch weather/location data for a favorite. Runs off the UI thread.""" # Extract ID and stored name (if present) if '/' in path_loc: location_id, stored_name = path_loc.split('/', 1) @@ -1092,7 +1119,6 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None): location_id = path_loc stored_name = None - self.myloc = fav_index day_index = self.tag # Fetch location data from API (for country, lat, lon, timezone) @@ -1346,6 +1372,18 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None): print( f"[DEBUG] Daily forecast for day {self.tag}: {day_selected.__dict__ if day_selected else 'None'}") + if seq != getattr(self, '_load_favorite_seq', seq): + # A newer _load_favorite call superseded this one; drop stale results. + return + + from twisted.internet import reactor + reactor.callFromThread(self._apply_favorite_data, target_date, seq) + + def _apply_favorite_data(self, target_date, seq): + """Populate widgets once _load_favorite_worker finishes. Runs on the UI thread.""" + if seq != getattr(self, '_load_favorite_seq', seq): + return + # Update UI self._update_moon(target_date=target_date) self.my_cur_weather() diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/slideshow.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/slideshow.py index 314f4bdf..e2d6b00e 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/slideshow.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/slideshow.py @@ -184,7 +184,7 @@ def download_all_images(self): """Download all 6 images for the region""" for i in range(self.total_images): - url = f"http://img.wetterkontor.de/karten/{self.region_code}{i}.jpg" + url = f"https://img.wetterkontor.de/karten/{self.region_code}{i}.jpg" cache_file = join( WETTERKONTOR_CACHE, f"{self.region_code}_{i}.jpg") diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/translate_utils.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/translate_utils.py deleted file mode 100644 index 06b9bc1c..00000000 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/translate_utils.py +++ /dev/null @@ -1,621 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# Copyright (c) @Lululla 2026 -# Google Translate API for Foreca One Weather Plugin - -import hashlib -import json -import socket -import time -from json import JSONDecodeError, loads -from os import makedirs, remove -from os.path import dirname, exists, join - -from urllib.error import HTTPError, URLError -from urllib.parse import urlencode -from urllib.request import Request, urlopen - -from Components.config import config - -from . import DEBUG, HEADERS, SYSTEM_DIR - -# ============================================================ -# CUSTOM CONFIGURATION -# ============================================================ - -# Translation API URL (can be changed if needed) -TRANSLATE_API_URL = "https://translate.googleapis.com/translate_a/single" - -# Timeout for HTTP requests (in seconds) -REQUEST_TIMEOUT = 8 - -# Character limit for batch translation (to avoid errors) -MAX_CHARS_PER_REQUEST = 2000 - -# Local cache to avoid repetitive requests -CACHE_FILE = join(SYSTEM_DIR, "translation_cache.json") -_translation_cache = {} -_cache_hits = 0 -_cache_misses = 0 -_cache_dirty = False # flag to know if there are changes to save - -# Enable logging -ENABLE_LOGGING = True - - -# ============================================================ -# CACHE PERSISTENCE -# ============================================================ - - -def _ensure_cache_dir(): - """Create the directory for the cache file if it does not exist.""" - cache_dir = dirname(CACHE_FILE) - if not exists(cache_dir): - try: - makedirs(cache_dir) - except Exception as e: - _log(f"Error creating cache directory: {e}") - - -def load_cache_from_disk(): - """Load the cache from the JSON file at startup.""" - global _translation_cache - _ensure_cache_dir() - if exists(CACHE_FILE): - try: - with open(CACHE_FILE, 'r', encoding='utf-8') as f: - _translation_cache = json.load(f) - _log(f"Cache loaded from disk ({len(_translation_cache)} entries)") - except Exception as e: - _log(f"Error loading cache: {e}") - _translation_cache = {} - else: - _translation_cache = {} - - -def save_cache_to_disk(): - """Save the cache to disk if there are changes.""" - global _cache_dirty - if not _cache_dirty: - return - _ensure_cache_dir() - try: - with open(CACHE_FILE, 'w', encoding='utf-8') as f: - json.dump(_translation_cache, f, ensure_ascii=False, indent=2) - _log(f"Cache saved to disk ({len(_translation_cache)} entries)") - _cache_dirty = False - except Exception as e: - _log(f"Error saving cache: {e}") - - -# ============================================================ -# UTILITY FUNCTIONS -# ============================================================ - - -def _log(message): - """Custom logging""" - if ENABLE_LOGGING and DEBUG: - timestamp = time.time() - print(f"[Foreca-1-Translate][{timestamp:.2f}] {message}") - - -def _get_system_language(): - """Get system language in short format""" - try: - lang = config.misc.language.value - return lang.split('_')[0].lower() - except Exception: - lang = config.osd.language.value - return lang.split('_')[0].lower() - -# print("System Language:", _get_system_language()) - - -def _to_unicode(text): - """Convert any input into a Unicode string.""" - if text is None: - return "" - - if isinstance(text, str): - return text - - if isinstance(text, bytes): - try: - return text.decode("utf-8", errors="ignore") - except Exception: - return str(text, errors="ignore") - - try: - return str(text) - except Exception: - return "" - - -def _clean_whitespace(text): - text_unicode = _to_unicode(text) - while " " in text_unicode: - text_unicode = text_unicode.replace(" ", " ") - return text_unicode.strip() - - -# ============================================================ -# ARABIC LANGUAGE DETECTION -# ============================================================ - - -def _is_arabic_char(char): - """Check if a character is Arabic""" - try: - code = ord(char) - # Unicode ranges for Arabic characters - return ( - 0x0600 <= code <= 0x06FF or - 0x0750 <= code <= 0x077F or - 0x08A0 <= code <= 0x08FF or - 0xFB50 <= code <= 0xFDFF or - 0xFE70 <= code <= 0xFEFF - ) - except Exception: - return False - - -def _is_text_arabic(text): - """ - Determines whether a text is predominantly Arabic. - Returns True if more than 60% of alphabetic characters are Arabic. - """ - text_unicode = _to_unicode(text) - if not text_unicode: - return False - - total_letters = 0 - arabic_letters = 0 - - for char in text_unicode: - # Consider only alphabetic characters (exclude spaces, numbers, - # punctuation) - if char.isalpha(): - total_letters += 1 - if _is_arabic_char(char): - arabic_letters += 1 - - # If there are no letters, it's not Arabic - if total_letters == 0: - return False - - # Calculate percentage - arabic_ratio = float(arabic_letters) / float(total_letters) - - # Threshold to consider the text Arabic (60%) - return arabic_ratio >= 0.6 - - -# ============================================================ -# CACHE AND PERFORMANCE -# ============================================================ - - -def _get_cache_key(text, target_lang): - """Generate a unique cache key using MD5 (stable across runs)""" - # Use MD5 because it is fast and deterministic - key_string = f"{target_lang}:{text}".encode('utf-8') - return hashlib.md5(key_string).hexdigest() - - -def _cache_translation(text, target_lang, translated): - """Store a translation in the cache and save immediately to disk.""" - global _cache_dirty - cache_key = _get_cache_key(text, target_lang) - _translation_cache[cache_key] = translated - _cache_dirty = True - save_cache_to_disk() - return translated - - -def _get_cached_translation(text, target_lang): - """Retrieve a translation from the cache""" - global _cache_hits, _cache_misses - cache_key = _get_cache_key(text, target_lang) - - if cache_key in _translation_cache: - _cache_hits += 1 - return _translation_cache[cache_key] - - _cache_misses += 1 - return None - - -def get_cache_stats(): - """Return cache statistics""" - return { - 'hits': _cache_hits, - 'misses': _cache_misses, - 'size': len(_translation_cache), - 'hit_rate': _cache_hits / max(1, _cache_hits + _cache_misses) - } - - -def clear_cache(): - """Clear the translation cache and delete the file""" - global _cache_hits, _cache_misses, _cache_dirty - _translation_cache.clear() - _cache_hits = 0 - _cache_misses = 0 - _cache_dirty = False - if exists(CACHE_FILE): - try: - remove(CACHE_FILE) - except Exception as e: - _log(f"Error deleting cache file: {e}") - _log("Cache cleared") - - -# ============================================================ -# MAIN TRANSLATION FUNCTION -# ============================================================ - -def translate_text(text, target_lang=None, use_cache=True): - """ - Translates text using the Google Translate API. - - Args: - text (str): Text to translate - target_lang (str): Target language (e.g. 'it', 'en', 'de') - If None, uses the system language - use_cache (bool): Whether to use the local cache - - Returns: - str: Translated text or original text in case of error - """ - start_time = time.time() - _log(f"Target language: '{target_lang}'") - # Input validation - if not text: - return "" - - # Convert to Unicode - text_unicode = _to_unicode(text) - - # Use system language if not specified - if target_lang is None: - target_lang = _get_system_language() - - # Normalize language (ensure lowercase) - target_lang = target_lang.lower() - - # If the text is already Arabic, do not translate it - if _is_text_arabic(text_unicode): - _log(f"Arabic text detected, not translated: '{text_unicode[:50]}...'") - return text_unicode - - # Check cache if enabled - if use_cache: - cached = _get_cached_translation(text_unicode, target_lang) - if cached is not None: - _log(f"Cache HIT: '{text_unicode[:30]}...' -> '{cached[:30]}...'") - return cached - - # Error handling for overly long texts - if len(text_unicode) > MAX_CHARS_PER_REQUEST: - _log("Text too long (" + - str(len(text_unicode)) + - " chars), truncated to " + - str(MAX_CHARS_PER_REQUEST)) - text_unicode = text_unicode[:MAX_CHARS_PER_REQUEST] - - # Prepare the request - params = { - "client": "gtx", # Fake client to bypass restrictions - "sl": "auto", # Automatic source language - "tl": target_lang, # Target language - "dt": "t", # Response type: translation only - "q": text_unicode, # Text to translate - } - - try: - # Build the URL - query_string = urlencode(params) - url = f"{TRANSLATE_API_URL}?{query_string}" - - _log(f"Translating: '{text_unicode[:40]}...' -> {target_lang}") - - # Set timeout to avoid blocking - socket.setdefaulttimeout(REQUEST_TIMEOUT) - - # Perform the request - req = Request(url) - for key, value in HEADERS.items(): - req.add_header(key, value) - response = urlopen(req, timeout=REQUEST_TIMEOUT) - raw_data = response.read() - - # Decode the response - if isinstance(raw_data, bytes): - raw_data = raw_data.decode('utf-8') - - # Parse JSON response - data = loads(raw_data) - - # Extract the translation from the JSON structure - translated_text = "" - if isinstance(data, list) and data: - # Typical structure: [[[translation, original], ...], ...] - for item in data[0]: - if item and isinstance(item, list) and item[0]: - translated_text += item[0] - - # Clean the result - if translated_text: - translated_text = _clean_whitespace(translated_text) - - # Save to cache - if use_cache: - _cache_translation(text_unicode, target_lang, translated_text) - - elapsed = time.time() - start_time - _log(( - f"Translation completed in {elapsed:.2f}s: '{text_unicode[:30]}...' -> " - f"'{translated_text[:30]}...'" - )) - - return translated_text - else: - _log(f"Empty API response for: '{text_unicode[:30]}...'") - return text_unicode - - except socket.timeout: - _log(f"TIMEOUT during translation: '{text_unicode[:30]}...'") - return text_unicode - - except (URLError, HTTPError) as e: - _log(f"HTTP error {getattr(e, 'code', 'N/A')}: {str(e)}") - return text_unicode - - except JSONDecodeError as e: - _log(f"JSON error: {str(e)}") - return text_unicode - - except Exception as e: - error_type = type(e).__name__ - _log(f"Error {error_type}: {str(e)}") - return text_unicode - - finally: - # Restore default timeout - socket.setdefaulttimeout(None) - - -# ============================================================ -# AUXILIARY FUNCTIONS FOR SPECIAL CASES -# ============================================================ - - -def translate_batch(texts, target_lang=None, use_cache=True): - """ - Translates a list of texts in batch. - Optimized to reduce the number of HTTP requests. - - Args: - texts (list): List of texts to translate - target_lang (str): Target language - use_cache (bool): Use cache - - Returns: - list: List of translated texts - """ - if not texts: - return [] - - # Use system language if not specified - if target_lang is None: - target_lang = _get_system_language() - results = [] - batch_text = [] - batch_indices = [] - - for i, text in enumerate(texts): - text_unicode = _to_unicode(text) - - # Check cache - if use_cache: - cached = _get_cached_translation(text_unicode, target_lang) - if cached is not None: - results.append(cached) - continue - - # If the text is Arabic, do not translate it - if _is_text_arabic(text_unicode): - results.append(text_unicode) - continue - - # Add to batch - batch_text.append(text_unicode) - batch_indices.append(i) - results.append(None) # Placeholder - - # If there are texts to translate in batch - if batch_text: - try: - # Join texts with a special separator - separator = u" ||| " - combined_text = separator.join(batch_text) - - # Translate the batch - combined_translated = translate_text( - combined_text, - target_lang, - use_cache=False # Do not use cache for batch - ) - - # Split results - if separator in combined_translated: - translated_parts = combined_translated.split(separator) - else: - # Fallback: split by approximate number - translated_parts = [combined_translated] * len(batch_text) - - # Update results - for idx, translated in zip(batch_indices, translated_parts): - results[idx] = translated - - # Save to cache - if use_cache and idx < len(texts): - text_unicode = _to_unicode(texts[idx]) - _cache_translation(text_unicode, target_lang, translated) - - except Exception as e: - _log(f"Batch translation error: {str(e)}") - # Fallback: translate individually - for idx in batch_indices: - if results[idx] is None and idx < len(texts): - results[idx] = translate_text( - texts[idx], target_lang, use_cache) - - # Replace None with original text - for i in range(len(results)): - if results[i] is None: - results[i] = _to_unicode(texts[i]) - - return results - - -def safe_translate(text, fallback=None, **kwargs): - """ - Safe version of translate_text that always returns a valid string. - Args: - text (str): Text to translate - fallback (str): Fallback text if translation fails - **kwargs: Additional arguments for translate_text - Returns: - str: Translated text, fallback or original - """ - try: - translated = translate_text(text, **kwargs) - if translated and translated.strip(): - return translated - - # If translation is empty, use fallback - if fallback is not None: - return _to_unicode(fallback) - - return _to_unicode(text) - - except Exception as e: - _log(f"Error in safe_translate: {str(e)}") - if fallback is not None: - return _to_unicode(fallback) - return _to_unicode(text) - - -def trans(text, target_lang=None): - """ - Simplified translation function for single strings. - Uses cache and translate_text. - """ - if target_lang is None: - target_lang = _get_system_language() - target_lang = target_lang.lower() - - if not text or not isinstance(text, str): - return text or "" - - text = text.strip() - if not text: - return "" - - # Do not translate Arabic text - if _is_text_arabic(text): - return text - - # Check cache using full key (language + hash) - cached = _get_cached_translation(text, target_lang) - if cached is not None: - return cached - - # Translate (translate_text already handles internal caching if - # use_cache=True) - translated = translate_text(text, target_lang, use_cache=True) - if translated and translated != text: - return translated - return text - - -def translate_batch_strings(texts, target_lang=None): - """ - High-level batch translation for a list of strings. - """ - if not texts: - return [] - valid_texts = [str(t).strip() for t in texts if t and str(t).strip()] - if not valid_texts: - return [] - - # Use the existing cache via translate_batch - return translate_batch(valid_texts, target_lang, use_cache=True) - - -# ============================================================ -# TEST FUNCTION (for debugging) -# ============================================================ - - -def test_translation(): - """Test function to verify functionality""" - test_cases = [ - ("Hello world", "it", "Ciao mondo"), - ("Weather forecast", "es", "Pronóstico del tiempo"), - ("Temperature", "fr", "Température"), - ] - if DEBUG: - print("=" * 60) - print("Foreca One TRANSLATION TEST") - print("=" * 60) - - all_passed = True - - for original, lang, expected in test_cases: - result = translate_text(original, lang) - - if result and result.lower() == expected.lower(): - status = "✓ PASS" - else: - status = "✗ FAIL" - all_passed = False - if DEBUG: - print( - f"{status}: '{original}' -> '{result}' (expected: '{expected}')") - if DEBUG: - print("=" * 60) - stats = get_cache_stats() - print(( - f"Cache statistics: {stats['hits']} hits, {stats['misses']} misses, " - f"rate: {stats['hit_rate']:.1%}" - )) - print("=" * 60) - - return all_passed - - -# ============================================================ -# INITIALIZATION -# ============================================================ - -# Load cache at module startup -load_cache_from_disk() - -if __name__ == "__main__": - # Test mode when run directly - if DEBUG: - print("Google Translate API for Foreca") - print("Enhanced custom version") - - if test_translation(): - print("✓ All tests passed!") - else: - print("✗ Some tests failed") -else: - # Imported as a module - _log("Foreca One translation module loaded") - _log(f"System language: {_get_system_language()}") diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py index e932caf3..b5818e7d 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py @@ -341,7 +341,7 @@ def __init__(self, session, unit_manager): "red": (self.exit, _("Exit")), "green": (self.save, _("Save")), "blue": (self.open_advanced, _("Advanced")), - "left": (self.down, _("Prev")), + "left": (self.up, _("Prev")), "right": (self.down, _("Next")), "up": (self.up, _("Prev")), "down": (self.down, _("Next"))