mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
372 lines
13 KiB
Python
372 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
import json
|
|
import subprocess
|
|
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}" {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
|
|
|
|
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
|
|
|
|
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() |