mirror of
https://github.com/yeongpin/cursor-free-vip.git
synced 2025-08-02 20:47:35 +08:00
Add script to auto-translate missing keys in translation files with colored output and parallel processing
This commit is contained in:
parent
4286b94a86
commit
1158068a31
108
fill_missing_translations.py
Normal file
108
fill_missing_translations.py
Normal 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])
|
@ -32,7 +32,10 @@
|
||||
"exiting": "جاري الخروج ……",
|
||||
"bypass_version_check": "تجاوز فحص إصدار Cursor",
|
||||
"check_user_authorized": "فحص صلاحية المستخدم",
|
||||
"bypass_token_limit": "تجاوز حد الرمز المميز (Token)"
|
||||
"bypass_token_limit": "تجاوز حد الرمز المميز (Token)",
|
||||
"restore_machine_id": "استعادة معرف الجهاز من النسخ الاحتياطي",
|
||||
"lang_invalid_choice": "اختيار غير صالح. الرجاء إدخال أحد الخيارات التالية: ({lang_choices})",
|
||||
"language_config_saved": "تم حفظ تكوين اللغة بنجاح"
|
||||
},
|
||||
"languages": {
|
||||
"en": "الإنجليزية",
|
||||
@ -765,5 +768,48 @@
|
||||
"connection_error": "خطأ في الاتصال بخادم التحديث",
|
||||
"unexpected_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": "للإلغاء"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user