29
This commit is contained in:
@@ -1,24 +1,372 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import subprocess
|
||||
def main():
|
||||
with open('assets/i18n/en-US.json', 'r+') as f:
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
from collections import defaultdict
|
||||
|
||||
# Mapping from mobile locale codes to root locale codes
|
||||
LOCALE_MAPPING = {
|
||||
"en-US": "en",
|
||||
"fr-FR": "fr",
|
||||
"fr-CA": "fr",
|
||||
"de-DE": "de",
|
||||
"es-ES": "es",
|
||||
"es-PE": "es",
|
||||
"it-IT": "it",
|
||||
"ja-JP": "ja",
|
||||
"ko-KR": "ko",
|
||||
"nl-NL": "nl",
|
||||
"pl-PL": "pl",
|
||||
"pt-PT": "pt",
|
||||
"pt-BR": "pt_BR",
|
||||
"ru-RU": "ru",
|
||||
"sv-SE": "sv",
|
||||
"sv-FI": "sv",
|
||||
"tr-TR": "tr",
|
||||
"uk-UA": "uk",
|
||||
"zh-CN": "zh_SIMPLIFIED",
|
||||
"zh-TW": "zh_Hant",
|
||||
"id-ID": "id",
|
||||
"th-TH": "th",
|
||||
"el-GR": "el",
|
||||
"ro-RO": "ro",
|
||||
"hu-HU": "hu",
|
||||
"sk-SK": "sk",
|
||||
"lv-LV": "lv",
|
||||
"vi-VN": "vi",
|
||||
"lt-LT": "lt",
|
||||
"mn-MN": "mn",
|
||||
"sr-Latn": "sr_Latn",
|
||||
"da-DK": "da",
|
||||
"nb-NO": "nb_NO",
|
||||
"ar-JO": "ar",
|
||||
"ca": "ca",
|
||||
"cs-CZ": "cs",
|
||||
"gl-ES": "gl",
|
||||
"sr-Cyrl": "sr_Cyrl",
|
||||
"sl-SI": "sl",
|
||||
"he-IL": "he",
|
||||
"fi-FI": "fi",
|
||||
"es-MX": "es",
|
||||
"hi-IN": "hi",
|
||||
# Add more mappings as needed
|
||||
}
|
||||
|
||||
# Regex pattern to find translation keys in Dart code
|
||||
TRANSLATION_KEY_PATTERN = r'["\']([\w_\.]+)["\']\.tr\(\)'
|
||||
|
||||
def find_mobile_i18n_files():
|
||||
mobile_i18n_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "i18n")
|
||||
if not os.path.exists(mobile_i18n_dir):
|
||||
print(f"Mobile i18n directory not found: {mobile_i18n_dir}")
|
||||
return []
|
||||
|
||||
return [f for f in os.listdir(mobile_i18n_dir) if f.endswith('.json')]
|
||||
|
||||
def find_root_i18n_dir():
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
root_dir = os.path.dirname(current_dir)
|
||||
i18n_dir = os.path.join(root_dir, "i18n")
|
||||
|
||||
if not os.path.exists(i18n_dir):
|
||||
print(f"Root i18n directory not found: {i18n_dir}")
|
||||
return None
|
||||
|
||||
return i18n_dir
|
||||
|
||||
def shorten_key(key):
|
||||
"""Try to shorten translation keys by removing common prefixes"""
|
||||
# Define prefixes to remove
|
||||
prefixes = [
|
||||
"action_common_",
|
||||
"album_info_card_",
|
||||
"album_viewer_",
|
||||
"asset_list_",
|
||||
"backup_controller_page_",
|
||||
"cache_settings_",
|
||||
"control_bottom_app_bar_",
|
||||
"search_page_",
|
||||
"theme_setting_"
|
||||
]
|
||||
|
||||
# Check if key starts with any of the prefixes
|
||||
for prefix in prefixes:
|
||||
if key.startswith(prefix):
|
||||
return key[len(prefix):]
|
||||
|
||||
return key
|
||||
|
||||
def find_all_dart_files():
|
||||
"""Find all Dart files in the mobile app"""
|
||||
mobile_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
lib_dir = os.path.join(mobile_dir, "lib")
|
||||
|
||||
if not os.path.exists(lib_dir):
|
||||
print(f"Mobile lib directory not found: {lib_dir}")
|
||||
return []
|
||||
|
||||
return glob.glob(os.path.join(lib_dir, "**", "*.dart"), recursive=True)
|
||||
|
||||
def extract_translation_keys_from_dart_files():
|
||||
"""Extract all translation keys used in the Dart code"""
|
||||
dart_files = find_all_dart_files()
|
||||
translation_keys = set()
|
||||
|
||||
for dart_file in dart_files:
|
||||
with open(dart_file, 'r') as f:
|
||||
content = f.read()
|
||||
matches = re.findall(TRANSLATION_KEY_PATTERN, content)
|
||||
translation_keys.update(matches)
|
||||
|
||||
return translation_keys
|
||||
|
||||
def check_for_unused_keys():
|
||||
"""Check for unused translation keys in the mobile app and returns a list of them."""
|
||||
mobile_i18n_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "i18n")
|
||||
en_us_path = os.path.join(mobile_i18n_dir, "en-US.json")
|
||||
|
||||
with open(en_us_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
keys_to_delete = []
|
||||
|
||||
for k in data.keys():
|
||||
sp = subprocess.run(['sh', '-c', f'grep -q -r --include="*.dart" "{k}"'])
|
||||
sp = subprocess.run(['sh', '-c', f'grep -q -r --include="*.dart" "{k}" {os.path.dirname(mobile_i18n_dir)}/lib'], capture_output=True)
|
||||
|
||||
if sp.returncode != 0:
|
||||
print("Not found in source code, key:", k)
|
||||
keys_to_delete.append(k)
|
||||
|
||||
return keys_to_delete, data
|
||||
|
||||
for k in keys_to_delete:
|
||||
del data[k]
|
||||
def migrate_translations():
|
||||
"""Migrate translations from mobile to root i18n directory WITHOUT modifying source files"""
|
||||
mobile_i18n_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "i18n")
|
||||
root_i18n_dir = find_root_i18n_dir()
|
||||
|
||||
if not root_i18n_dir:
|
||||
print("Could not find root i18n directory")
|
||||
return {}
|
||||
|
||||
# Get list of mobile translation files
|
||||
mobile_files = find_mobile_i18n_files()
|
||||
if not mobile_files:
|
||||
print("No mobile translation files found")
|
||||
return {}
|
||||
|
||||
# Dictionary to store key mappings (old_key -> new_key)
|
||||
key_mapping = {}
|
||||
|
||||
# First, find all unused keys (but don't delete them)
|
||||
unused_keys, _ = check_for_unused_keys()
|
||||
print(f"Found {len(unused_keys)} unused keys (will be migrated but marked)")
|
||||
|
||||
# Process each mobile translation file
|
||||
for mobile_file in mobile_files:
|
||||
mobile_locale = mobile_file.split('.')[0] # e.g., "en-US"
|
||||
|
||||
if mobile_locale not in LOCALE_MAPPING:
|
||||
print(f"No mapping found for locale {mobile_locale}, skipping")
|
||||
continue
|
||||
|
||||
root_locale = LOCALE_MAPPING[mobile_locale]
|
||||
root_file = f"{root_locale}.json"
|
||||
root_file_path = os.path.join(root_i18n_dir, root_file)
|
||||
|
||||
# Load mobile translations
|
||||
with open(os.path.join(mobile_i18n_dir, mobile_file), 'r') as f:
|
||||
mobile_translations = json.load(f)
|
||||
|
||||
# Load root translations if they exist
|
||||
if os.path.exists(root_file_path):
|
||||
with open(root_file_path, 'r') as f:
|
||||
root_translations = json.load(f)
|
||||
else:
|
||||
root_translations = {}
|
||||
|
||||
# Keys to migrate (including all keys)
|
||||
keys_to_migrate = {}
|
||||
|
||||
# Migrate all keys from mobile to root
|
||||
for k, v in mobile_translations.items():
|
||||
if k not in root_translations: # Only migrate if not already in root
|
||||
# Try to shorten the key
|
||||
new_key = shorten_key(k)
|
||||
|
||||
# If shortened key already exists in root, use the original key
|
||||
if new_key != k and new_key in root_translations:
|
||||
keys_to_migrate[k] = v
|
||||
key_mapping[k] = k # Map to itself (no change)
|
||||
else:
|
||||
keys_to_migrate[new_key] = v
|
||||
key_mapping[k] = new_key # Map old key to new key
|
||||
else:
|
||||
# If the key already exists in root, just record the mapping
|
||||
key_mapping[k] = k
|
||||
|
||||
# If there are keys to migrate, update the root file
|
||||
if keys_to_migrate:
|
||||
print(f"Migrating {len(keys_to_migrate)} keys from {mobile_file} to {root_file}")
|
||||
|
||||
# Update the root translations with the new keys
|
||||
root_translations.update(keys_to_migrate)
|
||||
|
||||
# Write the updated translations back to the root file
|
||||
with open(root_file_path, 'w') as f:
|
||||
json.dump(root_translations, f, indent=2, ensure_ascii=False, sort_keys=True)
|
||||
else:
|
||||
print(f"No new keys to migrate from {mobile_file} to {root_file}")
|
||||
|
||||
# IMPORTANT: We do NOT modify the source files at all, just return the key mappings
|
||||
return key_mapping
|
||||
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
json.dump(data, f, indent=4)
|
||||
def generate_migration_report(key_mapping, unused_keys):
|
||||
"""Generate a detailed report of the migration"""
|
||||
report_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "translation_migration_report.md")
|
||||
|
||||
with open(report_path, 'w') as f:
|
||||
f.write("# Translation Migration Report\n\n")
|
||||
|
||||
f.write("## Summary\n")
|
||||
f.write(f"- Total keys migrated: {len(key_mapping)}\n")
|
||||
f.write(f"- Keys shortened: {sum(1 for k, v in key_mapping.items() if k != v)}\n")
|
||||
f.write(f"- Unused keys identified: {len(unused_keys)}\n\n")
|
||||
|
||||
f.write("## Key Mappings\n")
|
||||
f.write("The following keys were migrated to the root i18n directory:\n\n")
|
||||
f.write("| Original Key | New Key | Shortened |\n")
|
||||
f.write("|-------------|---------|----------|\n")
|
||||
|
||||
for old_key, new_key in sorted(key_mapping.items()):
|
||||
shortened = "Yes" if old_key != new_key else "No"
|
||||
f.write(f"| `{old_key}` | `{new_key}` | {shortened} |\n")
|
||||
|
||||
f.write("\n\n## Unused Keys\n")
|
||||
f.write("The following keys were identified as unused in the codebase:\n\n")
|
||||
f.write("```\n")
|
||||
for key in sorted(unused_keys):
|
||||
f.write(f"{key}\n")
|
||||
f.write("```\n\n")
|
||||
|
||||
f.write("## Next Steps\n")
|
||||
f.write("1. Review the key mappings to ensure they are correct\n")
|
||||
f.write("2. Test the application thoroughly to ensure all translations work properly\n")
|
||||
f.write("3. Consider removing unused keys from the source files if they are no longer needed\n")
|
||||
|
||||
print(f"Migration report generated at {report_path}")
|
||||
return report_path
|
||||
|
||||
def update_dart_code_references(key_mapping):
|
||||
"""Update translation key references in Dart files"""
|
||||
if not key_mapping:
|
||||
print("No key mappings provided, skipping Dart code update")
|
||||
return []
|
||||
|
||||
# Get all Dart files
|
||||
dart_files = find_all_dart_files()
|
||||
updated_files = []
|
||||
|
||||
# Process each Dart file
|
||||
for dart_file in dart_files:
|
||||
with open(dart_file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
updated_content = content
|
||||
file_updated = False
|
||||
file_updates = []
|
||||
|
||||
# Replace each old key with the new key
|
||||
for old_key, new_key in key_mapping.items():
|
||||
if old_key != new_key: # Only update if the key has changed
|
||||
# Pattern to match the key used in tr() calls
|
||||
pattern = f'["\']({re.escape(old_key)})["\']\.tr\(\)'
|
||||
replacement = f'"{new_key}".tr()'
|
||||
|
||||
# Perform the replacement
|
||||
new_content = re.sub(pattern, replacement, updated_content)
|
||||
|
||||
if new_content != updated_content:
|
||||
file_updates.append(f"{old_key} -> {new_key}")
|
||||
updated_content = new_content
|
||||
file_updated = True
|
||||
|
||||
# Write the updated content back to the file if changes were made
|
||||
if file_updated:
|
||||
with open(dart_file, 'w') as f:
|
||||
f.write(updated_content)
|
||||
updated_files.append((dart_file, file_updates))
|
||||
print(f"Updated {os.path.basename(dart_file)} with new translation keys: {', '.join(file_updates)}")
|
||||
|
||||
print(f"Updated {len(updated_files)} Dart files with new translation keys")
|
||||
return updated_files
|
||||
|
||||
def check_for_duplicate_keys():
|
||||
"""Check for duplicate keys between mobile and root i18n"""
|
||||
mobile_i18n_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "i18n")
|
||||
root_i18n_dir = find_root_i18n_dir()
|
||||
|
||||
if not root_i18n_dir:
|
||||
print("Could not find root i18n directory")
|
||||
return []
|
||||
|
||||
# Load English translations as reference
|
||||
mobile_en_path = os.path.join(mobile_i18n_dir, "en-US.json")
|
||||
root_en_path = os.path.join(root_i18n_dir, "en.json")
|
||||
|
||||
if not os.path.exists(mobile_en_path) or not os.path.exists(root_en_path):
|
||||
print("Missing English translation files")
|
||||
return []
|
||||
|
||||
with open(mobile_en_path, 'r') as f:
|
||||
mobile_en = json.load(f)
|
||||
|
||||
with open(root_en_path, 'r') as f:
|
||||
root_en = json.load(f)
|
||||
|
||||
# Find duplicate keys
|
||||
duplicates = set(mobile_en.keys()) & set(root_en.keys())
|
||||
|
||||
if duplicates:
|
||||
print(f"Found {len(duplicates)} duplicate keys between mobile and root i18n:")
|
||||
for key in sorted(duplicates):
|
||||
mobile_val = mobile_en[key]
|
||||
root_val = root_en[key]
|
||||
if mobile_val == root_val:
|
||||
status = "SAME"
|
||||
else:
|
||||
status = "DIFFERENT"
|
||||
print(f" {key}: {status}")
|
||||
print(f" Mobile: {mobile_val}")
|
||||
print(f" Root: {root_val}")
|
||||
else:
|
||||
print("No duplicate keys found between mobile and root i18n")
|
||||
|
||||
return duplicates
|
||||
|
||||
def main():
|
||||
print("Checking for duplicate keys between mobile and root i18n...")
|
||||
duplicates = check_for_duplicate_keys()
|
||||
|
||||
print("\nExtracting translation keys used in Dart code...")
|
||||
translation_keys = extract_translation_keys_from_dart_files()
|
||||
print(f"Found {len(translation_keys)} translation keys in use")
|
||||
|
||||
# Get unused keys (but don't delete them)
|
||||
unused_keys, _ = check_for_unused_keys()
|
||||
|
||||
print("\nMigrating translations from mobile to root i18n...")
|
||||
key_mapping = migrate_translations()
|
||||
print(f"Created mappings for {len(key_mapping)} keys")
|
||||
|
||||
print("\nUpdating translation references in Dart code...")
|
||||
updated_files = update_dart_code_references(key_mapping)
|
||||
|
||||
# Generate a detailed report
|
||||
report_path = generate_migration_report(key_mapping, unused_keys)
|
||||
|
||||
print("\nMigration completed! See the report for details:", report_path)
|
||||
print("\nNote: Source translation files were NOT modified or deleted.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1414
mobile/scripts/translation_migration_report.md
Normal file
1414
mobile/scripts/translation_migration_report.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user