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": "جاري الخروج ……",
|
"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": "للإلغاء"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user