Add script to auto-translate missing keys in translation files with colored output and parallel processing

This commit is contained in:
Ahmed Nagi 2025-04-26 13:13:35 +02:00
parent 4286b94a86
commit 1158068a31
2 changed files with 155 additions and 1 deletions

View File

@ -0,0 +1,108 @@
"""
Compares two JSON translation files in /locales (e.g., en.json and ar.json).
Finds keys missing in the target file, translates their values using Google Translate (web scraping),
and inserts the translations. Runs in parallel for speed and creates a backup of the target file.
"""
import json
import requests
import sys
from pathlib import Path
import urllib.parse
import re
from concurrent.futures import ThreadPoolExecutor, as_completed
from colorama import init, Fore, Style
init(autoreset=True)
# Recursively get all keys in the JSON as dot-separated paths
def get_keys(d, prefix=''):
keys = set()
for k, v in d.items():
full_key = f"{prefix}.{k}" if prefix else k
if isinstance(v, dict):
keys |= get_keys(v, full_key)
else:
keys.add(full_key)
return keys
# Get value from nested dict by dot-separated path
def get_by_path(d, path):
for p in path.split('.'):
d = d[p]
return d
# Set value in nested dict by dot-separated path
def set_by_path(d, path, value):
parts = path.split('.')
for p in parts[:-1]:
if p not in d:
d[p] = {}
d = d[p]
d[parts[-1]] = value
# Translate text using Google Translate web scraping (mobile version)
def translate(text, source, target):
url = f"https://translate.google.com/m?sl={source}&tl={target}&q={requests.utils.quote(text)}"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
m = re.search(r'class=\"result-container\">(.*?)<', response.text)
if m:
return m.group(1)
else:
print(Fore.RED + f"Translation not found for: {text}")
return text
else:
print(Fore.RED + f"Request failed for: {text}")
return text
# Main logic: compare keys, translate missing, and update file
def main(en_filename, other_filename):
# Always use the /locales directory
en_path = Path("locales") / en_filename
other_path = Path("locales") / other_filename
# Infer language code from filename (before .json)
en_lang = Path(en_filename).stem
other_lang = Path(other_filename).stem
with open(en_path, encoding='utf-8') as f:
en = json.load(f)
with open(other_path, encoding='utf-8') as f:
other = json.load(f)
en_keys = get_keys(en)
other_keys = get_keys(other)
missing = en_keys - other_keys
print(Fore.YELLOW + f"Missing keys: {len(missing)}")
# Parallel translation using ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=8) as executor:
future_to_key = {
executor.submit(translate, get_by_path(en, key), en_lang, other_lang): key
for key in missing
}
for future in as_completed(future_to_key):
key = future_to_key[future]
value = get_by_path(en, key)
try:
translated = future.result()
print(Fore.CYAN + f"Translated [{key}]: '{value}' -> " + Fore.MAGENTA + f"'{translated}'")
except Exception as exc:
print(Fore.RED + f"Error translating {key}: {exc}")
translated = value
set_by_path(other, key, translated)
# Save the updated file and create a backup
backup_path = other_path.with_suffix('.bak.json')
other_path.rename(backup_path)
with open(other_path, 'w', encoding='utf-8') as f:
json.dump(other, f, ensure_ascii=False, indent=4)
print(Fore.GREEN + f"File updated. Backup saved to {backup_path}")
if __name__ == "__main__":
# Example: python3 fill_missing_translations.py en.json ar.json
if len(sys.argv) != 3:
print("Usage: python3 fill_missing_translations.py en.json ar.json")
sys.exit(1)
main(sys.argv[1], sys.argv[2])

View File

@ -32,7 +32,10 @@
"exiting": "جاري الخروج ……", "exiting": "جاري الخروج ……",
"bypass_version_check": "تجاوز فحص إصدار Cursor", "bypass_version_check": "تجاوز فحص إصدار Cursor",
"check_user_authorized": "فحص صلاحية المستخدم", "check_user_authorized": "فحص صلاحية المستخدم",
"bypass_token_limit": "تجاوز حد الرمز المميز (Token)" "bypass_token_limit": "تجاوز حد الرمز المميز (Token)",
"restore_machine_id": "استعادة معرف الجهاز من النسخ الاحتياطي",
"lang_invalid_choice": "اختيار غير صالح. الرجاء إدخال أحد الخيارات التالية: ({lang_choices})",
"language_config_saved": "تم حفظ تكوين اللغة بنجاح"
}, },
"languages": { "languages": {
"en": "الإنجليزية", "en": "الإنجليزية",
@ -765,5 +768,48 @@
"connection_error": "خطأ في الاتصال بخادم التحديث", "connection_error": "خطأ في الاتصال بخادم التحديث",
"unexpected_error": "خطأ غير متوقع أثناء تحديث الرمز: {error}", "unexpected_error": "خطأ غير متوقع أثناء تحديث الرمز: {error}",
"extraction_error": "خطأ في استخراج الرمز: {error}" "extraction_error": "خطأ في استخراج الرمز: {error}"
},
"restore": {
"update_failed": "Failed to update storage file: {error}",
"read_backup_failed": "Failed to read backup file: {error}",
"please_enter_number": "Please enter a valid number",
"no_backups_found": "No backup files found",
"title": "Restore Machine ID from Backup",
"sqlite_updated": "SQLite database updated successfully",
"permission_denied": "Permission denied. Please try running as administrator",
"sqlite_not_found": "SQLite database not found",
"backup_creation_failed": "فشل في إنشاء نسخة احتياطية: {error}",
"windows_machine_guid_updated": "تم تحديث GUID Machine Windows بنجاح",
"select_backup": "حدد النسخ الاحتياطي لاستعادة",
"sqm_client_key_not_found": "لم يتم العثور على مفتاح تسجيل SQMClient",
"machine_id_backup_created": "تم إنشاء نسخة احتياطية من ملف الجهاز",
"system_ids_update_failed": "فشل في تحديث معرفات النظام: {error}",
"current_backup_created": "تم إنشاء نسخة احتياطية من ملف التخزين الحالي",
"update_windows_machine_guid_failed": "فشل في تحديث GUID MAVEN",
"updating_pair": "تحديث زوج القيمة الرئيسية",
"press_enter": "اضغط على Enter للمتابعة",
"missing_id": "معرف مفقود: {id}",
"current_file_not_found": "لم يتم العثور على ملف التخزين الحالي",
"sqlite_update_failed": "فشل في تحديث قاعدة بيانات SQLite: {error}",
"success": "تم استعادة معرف الجهاز بنجاح",
"process_error": "استعادة خطأ العملية: {error}",
"update_macos_system_ids_failed": "فشل في تحديث معرفات نظام MacOS: {error}",
"machine_id_update_failed": "فشل في تحديث ملف الماكينة: {error}",
"available_backups": "ملفات النسخ الاحتياطي المتاحة",
"windows_machine_id_updated": "معرف جهاز Windows تم تحديثه بنجاح",
"updating_sqlite": "تحديث قاعدة بيانات SQLite",
"invalid_selection": "اختيار غير صالح",
"update_windows_system_ids_failed": "فشل في تحديث معرفات نظام Windows: {error}",
"macos_platform_uuid_updated": "تم تحديث منصة MacOS UUID بنجاح",
"ids_to_restore": "معرفات الماكينة لاستعادة",
"operation_cancelled": "تم إلغاء العملية",
"machine_id_updated": "تم تحديث ملف الجهاز بنجاح",
"update_windows_machine_id_failed": "فشل في تحديث معرف جهاز Windows: {error}",
"storage_updated": "تم تحديث ملف التخزين بنجاح",
"failed_to_execute_plutil_command": "فشل تنفيذ أمر بلوتيل",
"updating_system_ids": "تحديث معرفات النظام",
"starting": "بدء عملية استعادة معرف الجهاز",
"confirm": "هل أنت متأكد من أنك تريد استعادة هذه المعرفات؟",
"to_cancel": "للإلغاء"
} }
} }