diff --git a/.gitattributes b/.gitattributes index dfe0770..31bb774 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Auto detect text files and perform LF normalization * text=auto +*.sh text eol=lf diff --git a/installer.sh b/installer.sh index 786b48f..9062914 100644 --- a/installer.sh +++ b/installer.sh @@ -1,183 +1,183 @@ -#!/bin/bash -## setup command=wget -q --no-check-certificate https://raw.githubusercontent.com/OwnerPlugins/LottoStats/main/installer.sh -O - | /bin/bash - -version='1.0' -changelog='\nInitial release\nLotto statistics plugin for Enigma2' - -TMPPATH=/tmp/LottoStatistiche-install -FILEPATH=/tmp/LottoStatistiche-main.tar.gz - -echo "Starting LottoStatistiche installation..." - -if [ ! -d /usr/lib64 ]; then - PLUGINPATH=/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche -else - PLUGINPATH=/usr/lib64/enigma2/python/Plugins/Extensions/LottoStatistiche -fi - -cleanup() { - echo "Cleaning up temporary files..." - [ -d "$TMPPATH" ] && rm -rf "$TMPPATH" - [ -f "$FILEPATH" ] && rm -f "$FILEPATH" - [ -d "/tmp/LottoStatistiche-main" ] && rm -rf "/tmp/LottoStatistiche-main" -} - -detect_os() { - if [ -f /var/lib/dpkg/status ]; then - OSTYPE="DreamOs" - STATUS="/var/lib/dpkg/status" - elif [ -f /etc/opkg/opkg.conf ] || [ -f /var/lib/opkg/status ]; then - OSTYPE="OE" - STATUS="/var/lib/opkg/status" - else - OSTYPE="Unknown" - STATUS="" - fi - echo "Detected OS type: $OSTYPE" -} - -detect_os - -cleanup -mkdir -p "$TMPPATH" - -if ! command -v wget >/dev/null 2>&1; then - echo "Installing wget..." - case "$OSTYPE" in - "DreamOs") - apt-get update && apt-get install -y wget || { echo "Failed to install wget"; exit 1; } - ;; - "OE") - opkg update && opkg install wget || { echo "Failed to install wget"; exit 1; } - ;; - *) - echo "Unsupported OS type. Cannot install wget." - exit 1 - ;; - esac -fi - -# ============================================================ -# NO EXTRA DEPENDENCIES NEEDED - Only standard Enigma2 libs -# ============================================================ - -echo "Downloading LottoStatistiche..." -wget --no-check-certificate 'https://github.com/OwnerPlugins/LottoStats/archive/refs/heads/main.tar.gz' -O "$FILEPATH" -if [ $? -ne 0 ]; then - echo "Failed to download LottoStatistiche package!" - cleanup - exit 1 -fi - -echo "Extracting package..." -tar -xzf "$FILEPATH" -C "$TMPPATH" -if [ $? -ne 0 ]; then - echo "Failed to extract LottoStatistiche package!" - cleanup - exit 1 -fi - -# ============================================================ -# INSTALL PLUGIN FILES -# ============================================================ -echo "Installing plugin files..." -mkdir -p "$PLUGINPATH" - -# Cerca la cartella LottoStatistiche -SOURCE_DIR=$(find "$TMPPATH" -type d -iname "LottoStatistiche" 2>/dev/null | head -1) - -if [ -n "$SOURCE_DIR" ]; then - echo "Found plugin directory: $SOURCE_DIR" - cp -r "$SOURCE_DIR"/* "$PLUGINPATH/" 2>/dev/null - echo "Copied from $SOURCE_DIR" -else - # Fallback: cerca la struttura standard - FOUND=0 - for dir in "$TMPPATH"/*/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche; do - if [ -d "$dir" ]; then - cp -r "$dir"/* "$PLUGINPATH/" 2>/dev/null - echo "Copied from $dir" - FOUND=1 - break - fi - done - if [ $FOUND -eq 0 ]; then - for dir in "$TMPPATH"/*/usr/lib64/enigma2/python/Plugins/Extensions/LottoStatistiche; do - if [ -d "$dir" ]; then - cp -r "$dir"/* "$PLUGINPATH/" 2>/dev/null - echo "Copied from $dir" - FOUND=1 - break - fi - done - fi - if [ $FOUND -eq 0 ]; then - echo "Could not find plugin files in extracted archive" - echo "Available directories in tmp:" - find "$TMPPATH" -type d | head -20 - cleanup - exit 1 - fi -fi - -sync - -echo "Verifying installation..." -if [ -d "$PLUGINPATH" ] && [ -n "$(ls -A "$PLUGINPATH" 2>/dev/null)" ]; then - echo "Plugin directory found and not empty: $PLUGINPATH" - echo "Contents:" - ls -la "$PLUGINPATH/" | head -10 -else - echo "Plugin installation failed or directory is empty!" - cleanup - exit 1 -fi - -cleanup -sync - -# ============================================================ -# GET SYSTEM INFO -# ============================================================ -FILE="/etc/image-version" -box_type=$(sed -n '1p' /etc/hostname 2>/dev/null || echo "Unknown") - -if [ -r /etc/os-release ]; then - distro_value=$(grep '^NAME=' /etc/os-release 2>/dev/null | cut -d'"' -f2) - distro_version=$(grep '^VERSION_ID=' /etc/os-release 2>/dev/null | cut -d'"' -f2) -elif [ -r /etc/issue ]; then - distro_value=$(head -n 1 /etc/issue 2>/dev/null | awk '{print $1}') - distro_version=$(head -n 1 /etc/issue 2>/dev/null | awk '{print $2}') -elif [ -r /etc/vtiversion.info ]; then - distro_value=$(head -n 1 /etc/vtiversion.info 2>/dev/null) -elif [ -r /etc/issue.net ]; then - distro_value=$(head -n 1 /etc/issue.net 2>/dev/null | awk '{print $1}') - distro_version=$(head -n 1 /etc/issue.net 2>/dev/null | awk '{print $2}') -fi - -[ -z "$distro_value" ] && distro_value="Unknown" -[ -z "$distro_version" ] && distro_version="Unknown" -python_vers=$(python --version 2>&1) - -cat </dev/null 2>&1; then + echo "Installing wget..." + case "$OSTYPE" in + "DreamOs") + apt-get update && apt-get install -y wget || { echo "Failed to install wget"; exit 1; } + ;; + "OE") + opkg update && opkg install wget || { echo "Failed to install wget"; exit 1; } + ;; + *) + echo "Unsupported OS type. Cannot install wget." + exit 1 + ;; + esac +fi + +# ============================================================ +# NO EXTRA DEPENDENCIES NEEDED - Only standard Enigma2 libs +# ============================================================ + +echo "Downloading LottoStatistiche..." +wget --no-check-certificate 'https://github.com/OwnerPlugins/LottoStats/archive/refs/heads/main.tar.gz' -O "$FILEPATH" +if [ $? -ne 0 ]; then + echo "Failed to download LottoStatistiche package!" + cleanup + exit 1 +fi + +echo "Extracting package..." +tar -xzf "$FILEPATH" -C "$TMPPATH" +if [ $? -ne 0 ]; then + echo "Failed to extract LottoStatistiche package!" + cleanup + exit 1 +fi + +# ============================================================ +# INSTALL PLUGIN FILES +# ============================================================ +echo "Installing plugin files..." +mkdir -p "$PLUGINPATH" + +# Cerca la cartella LottoStatistiche +SOURCE_DIR=$(find "$TMPPATH" -type d -iname "LottoStatistiche" 2>/dev/null | head -1) + +if [ -n "$SOURCE_DIR" ]; then + echo "Found plugin directory: $SOURCE_DIR" + cp -r "$SOURCE_DIR"/* "$PLUGINPATH/" 2>/dev/null + echo "Copied from $SOURCE_DIR" +else + # Fallback: cerca la struttura standard + FOUND=0 + for dir in "$TMPPATH"/*/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche; do + if [ -d "$dir" ]; then + cp -r "$dir"/* "$PLUGINPATH/" 2>/dev/null + echo "Copied from $dir" + FOUND=1 + break + fi + done + if [ $FOUND -eq 0 ]; then + for dir in "$TMPPATH"/*/usr/lib64/enigma2/python/Plugins/Extensions/LottoStatistiche; do + if [ -d "$dir" ]; then + cp -r "$dir"/* "$PLUGINPATH/" 2>/dev/null + echo "Copied from $dir" + FOUND=1 + break + fi + done + fi + if [ $FOUND -eq 0 ]; then + echo "Could not find plugin files in extracted archive" + echo "Available directories in tmp:" + find "$TMPPATH" -type d | head -20 + cleanup + exit 1 + fi +fi + +sync + +echo "Verifying installation..." +if [ -d "$PLUGINPATH" ] && [ -n "$(ls -A "$PLUGINPATH" 2>/dev/null)" ]; then + echo "Plugin directory found and not empty: $PLUGINPATH" + echo "Contents:" + ls -la "$PLUGINPATH/" | head -10 +else + echo "Plugin installation failed or directory is empty!" + cleanup + exit 1 +fi + +cleanup +sync + +# ============================================================ +# GET SYSTEM INFO +# ============================================================ +FILE="/etc/image-version" +box_type=$(sed -n '1p' /etc/hostname 2>/dev/null || echo "Unknown") + +if [ -r /etc/os-release ]; then + distro_value=$(grep '^NAME=' /etc/os-release 2>/dev/null | cut -d'"' -f2) + distro_version=$(grep '^VERSION_ID=' /etc/os-release 2>/dev/null | cut -d'"' -f2) +elif [ -r /etc/issue ]; then + distro_value=$(head -n 1 /etc/issue 2>/dev/null | awk '{print $1}') + distro_version=$(head -n 1 /etc/issue 2>/dev/null | awk '{print $2}') +elif [ -r /etc/vtiversion.info ]; then + distro_value=$(head -n 1 /etc/vtiversion.info 2>/dev/null) +elif [ -r /etc/issue.net ]; then + distro_value=$(head -n 1 /etc/issue.net 2>/dev/null | awk '{print $1}') + distro_version=$(head -n 1 /etc/issue.net 2>/dev/null | awk '{print $2}') +fi + +[ -z "$distro_value" ] && distro_value="Unknown" +[ -z "$distro_version" ] && distro_version="Unknown" +python_vers=$(python --version 2>&1) + +cat < 50: - text += f"\n... {_('and')} {len(archive) - - 50} {_('more draws')} ({_('use up/down to scroll')})" + text += f"\n... {_('and')} {len(archive) - 50} {_('more draws')} ({_('use up/down to scroll')})" return text diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/dieci_lotto_screen.py b/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/dieci_lotto_screen.py index 5a35376..1bc4931 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/dieci_lotto_screen.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/dieci_lotto_screen.py @@ -34,8 +34,7 @@ def _get_info_text(self): text = _("🔢 LAST 10eLOTTO DRAW:\n") if data and 'ultima' in data: - text += f" {_('Drawn numbers')}: {', '.join(map(str, - data['ultima']))}\n" + text += f" {_('Drawn numbers')}: {', '.join(map(str, data['ultima']))}\n" text += _("\n📊 10eLOTTO STATISTICS:\n") if data and 'frequenze' in data: diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/main_screen.py b/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/main_screen.py index 5f45815..a1856b6 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/main_screen.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/main_screen.py @@ -14,7 +14,8 @@ from .smorfia_screen import SmorfiaScreen from .dieci_lotto_screen import DieciLottoScreen from .superenalotto_screen import SuperenalottoScreen -from ..core.update import aggiorna_archivio +from ..core.update import update_archive +from ..core.update_superenalotto import download_and_convert_se from . import _ diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/superenalotto_screen.py b/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/superenalotto_screen.py index 44d8cb2..7d9ae13 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/superenalotto_screen.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/screens/superenalotto_screen.py @@ -41,11 +41,7 @@ def _get_info_text(self): # Last draw last = archive[-1] - text = f"🎰 { - _('LAST DRAW')} ({ - _('contest')} { - last['concorso']} - { - last['data']}):\n" + text = f"🎰 {_('LAST DRAW')} ({_('contest')} {last['concorso']} - {last['data']}):\n" text += f" {_('Numbers')}: {', '.join(map(str, last['numeri']))}\n" if last['jolly']: text += f" Jolly: {last['jolly']}\n" diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/translate_utils.py b/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/translate_utils.py deleted file mode 100644 index 06b9bc1..0000000 --- a/usr/lib/enigma2/python/Plugins/Extensions/LottoStatistiche/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()}")