feat: implement translation management system and enhance localization support

- Added a new translation management system to automate synchronization, validation, and translation of internationalization files.
- Introduced scripts for running translation operations, including checking status, synchronizing keys, and auto-translating strings.
- Updated package.json with new translation-related commands for easier access.
- Enhanced localization files across multiple languages with new keys and improved translations.
- Integrated download functionality for share files in the UI, allowing users to download multiple files seamlessly.
- Refactored components to support new download features and improved user experience.
This commit is contained in:
Daniel Luiz Alves
2025-06-23 12:11:52 -03:00
parent 6af10c6f33
commit 4e841b272c
29 changed files with 2389 additions and 124 deletions

View File

@@ -0,0 +1,231 @@
#!/usr/bin/env python3
"""
Script to check translation status and identify strings that need translation.
"""
import json
from pathlib import Path
from typing import Dict, Any, List, Tuple
import argparse
def load_json_file(file_path: Path) -> Dict[str, Any]:
"""Load a JSON file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading {file_path}: {e}")
return {}
def get_all_string_values(data: Dict[str, Any], prefix: str = '') -> List[Tuple[str, str]]:
"""Extract all strings from nested JSON with their keys."""
strings = []
for key, value in data.items():
current_key = f"{prefix}.{key}" if prefix else key
if isinstance(value, str):
strings.append((current_key, value))
elif isinstance(value, dict):
strings.extend(get_all_string_values(value, current_key))
return strings
def check_untranslated_strings(file_path: Path) -> Tuple[int, int, List[str]]:
"""Check for untranslated strings in a file."""
data = load_json_file(file_path)
if not data:
return 0, 0, []
all_strings = get_all_string_values(data)
untranslated = []
for key, value in all_strings:
if value.startswith('[TO_TRANSLATE]'):
untranslated.append(key)
return len(all_strings), len(untranslated), untranslated
def compare_languages(reference_file: Path, target_file: Path) -> Dict[str, Any]:
"""Compare two language files."""
reference_data = load_json_file(reference_file)
target_data = load_json_file(target_file)
if not reference_data or not target_data:
return {}
reference_strings = dict(get_all_string_values(reference_data))
target_strings = dict(get_all_string_values(target_data))
# Find common keys
common_keys = set(reference_strings.keys()) & set(target_strings.keys())
# Check identical strings (possibly untranslated)
identical_strings = []
for key in common_keys:
if reference_strings[key] == target_strings[key] and len(reference_strings[key]) > 3:
identical_strings.append(key)
return {
'total_reference': len(reference_strings),
'total_target': len(target_strings),
'common_keys': len(common_keys),
'identical_strings': identical_strings
}
def generate_translation_report(messages_dir: Path, reference_file: str = 'en-US.json'):
"""Generate complete translation report."""
reference_path = messages_dir / reference_file
if not reference_path.exists():
print(f"Reference file not found: {reference_path}")
return
# Load reference data
reference_data = load_json_file(reference_path)
reference_strings = dict(get_all_string_values(reference_data))
total_reference_strings = len(reference_strings)
print(f"📊 TRANSLATION REPORT")
print(f"Reference: {reference_file} ({total_reference_strings} strings)")
print("=" * 80)
# Find all JSON files
json_files = [f for f in messages_dir.glob('*.json') if f.name != reference_file]
if not json_files:
print("No translation files found")
return
reports = []
for json_file in sorted(json_files):
total_strings, untranslated_count, untranslated_keys = check_untranslated_strings(json_file)
comparison = compare_languages(reference_path, json_file)
# Calculate percentages
completion_percentage = (total_strings / total_reference_strings) * 100 if total_reference_strings > 0 else 0
untranslated_percentage = (untranslated_count / total_strings) * 100 if total_strings > 0 else 0
reports.append({
'file': json_file.name,
'total_strings': total_strings,
'untranslated_count': untranslated_count,
'untranslated_keys': untranslated_keys,
'completion_percentage': completion_percentage,
'untranslated_percentage': untranslated_percentage,
'identical_strings': comparison.get('identical_strings', [])
})
# Sort by completion percentage
reports.sort(key=lambda x: x['completion_percentage'], reverse=True)
print(f"{'LANGUAGE':<15} {'COMPLETENESS':<12} {'STRINGS':<15} {'UNTRANSLATED':<15} {'POSSIBLE MATCHES'}")
print("-" * 80)
for report in reports:
language = report['file'].replace('.json', '')
completion = f"{report['completion_percentage']:.1f}%"
strings_info = f"{report['total_strings']}/{total_reference_strings}"
untranslated_info = f"{report['untranslated_count']} ({report['untranslated_percentage']:.1f}%)"
identical_count = len(report['identical_strings'])
# Choose icon based on completeness
if report['completion_percentage'] >= 100:
icon = "" if report['untranslated_count'] == 0 else "⚠️"
elif report['completion_percentage'] >= 90:
icon = "🟡"
else:
icon = "🔴"
print(f"{icon} {language:<13} {completion:<12} {strings_info:<15} {untranslated_info:<15} {identical_count}")
print("\n" + "=" * 80)
# Show details of problematic files
problematic_files = [r for r in reports if r['untranslated_count'] > 0 or r['completion_percentage'] < 100]
if problematic_files:
print("📋 DETAILS OF FILES THAT NEED ATTENTION:")
print()
for report in problematic_files:
language = report['file'].replace('.json', '')
print(f"🔍 {language.upper()}:")
if report['completion_percentage'] < 100:
missing_count = total_reference_strings - report['total_strings']
print(f" • Missing {missing_count} strings ({100 - report['completion_percentage']:.1f}%)")
if report['untranslated_count'] > 0:
print(f"{report['untranslated_count']} strings marked as [TO_TRANSLATE]")
if report['untranslated_count'] <= 10:
print(" • Untranslated keys:")
for key in report['untranslated_keys']:
print(f" - {key}")
else:
print(" • First 10 untranslated keys:")
for key in report['untranslated_keys'][:10]:
print(f" - {key}")
print(f" ... and {report['untranslated_count'] - 10} more")
if report['identical_strings']:
identical_count = len(report['identical_strings'])
print(f"{identical_count} strings identical to English (possibly untranslated)")
if identical_count <= 5:
for key in report['identical_strings']:
value = reference_strings.get(key, '')[:50]
print(f" - {key}: \"{value}...\"")
else:
for key in report['identical_strings'][:5]:
value = reference_strings.get(key, '')[:50]
print(f" - {key}: \"{value}...\"")
print(f" ... and {identical_count - 5} more")
print()
else:
print("🎉 All translations are complete!")
print("=" * 80)
print("💡 TIPS:")
print("• Use 'python3 sync_translations.py --dry-run' to see what would be added")
print("• Use 'python3 sync_translations.py' to synchronize all translations")
print("• Strings marked with [TO_TRANSLATE] need manual translation")
print("• Strings identical to English may need translation")
def main():
parser = argparse.ArgumentParser(
description='Check translation status and identify strings that need translation'
)
parser.add_argument(
'--messages-dir',
type=Path,
default=Path(__file__).parent.parent / 'messages',
help='Directory containing message files (default: ../messages)'
)
parser.add_argument(
'--reference',
default='en-US.json',
help='Reference file (default: en-US.json)'
)
args = parser.parse_args()
if not args.messages_dir.exists():
print(f"Directory not found: {args.messages_dir}")
return 1
generate_translation_report(args.messages_dir, args.reference)
return 0
if __name__ == '__main__':
exit(main())

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Main script to run all Palmr translation operations.
Makes it easy to run scripts without remembering specific names.
"""
import sys
import subprocess
from pathlib import Path
import argparse
def run_command(script_name: str, args: list) -> int:
"""Execute a script with the provided arguments."""
script_path = Path(__file__).parent / script_name
cmd = [sys.executable, str(script_path)] + args
return subprocess.run(cmd).returncode
def main():
parser = argparse.ArgumentParser(
description='Main script to manage Palmr translations',
epilog='Examples:\n'
' python3 run_translations.py check\n'
' python3 run_translations.py sync --dry-run\n'
' python3 run_translations.py translate --delay 2.0\n'
' python3 run_translations.py all # Complete workflow\n',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'command',
choices=['check', 'sync', 'translate', 'all', 'help'],
help='Command to execute:\n'
'check - Check translation status\n'
'sync - Synchronize missing keys\n'
'translate - Automatically translate strings\n'
'all - Run complete workflow\n'
'help - Show detailed help'
)
# Capture remaining arguments to pass to scripts
args, remaining_args = parser.parse_known_args()
if args.command == 'help':
print("🌍 PALMR TRANSLATION MANAGER")
print("=" * 50)
print()
print("📋 AVAILABLE COMMANDS:")
print()
print("🔍 check - Check translation status")
print(" python3 run_translations.py check")
print(" python3 run_translations.py check --reference pt-BR.json")
print()
print("🔄 sync - Synchronize missing keys")
print(" python3 run_translations.py sync")
print(" python3 run_translations.py sync --dry-run")
print(" python3 run_translations.py sync --no-mark-untranslated")
print()
print("🌐 translate - Automatically translate")
print(" python3 run_translations.py translate")
print(" python3 run_translations.py translate --dry-run")
print(" python3 run_translations.py translate --delay 2.0")
print(" python3 run_translations.py translate --skip-languages pt-BR.json")
print()
print("⚡ all - Complete workflow (sync + translate)")
print(" python3 run_translations.py all")
print(" python3 run_translations.py all --dry-run")
print()
print("📁 STRUCTURE:")
print(" apps/web/scripts/ - Management scripts")
print(" apps/web/messages/ - Translation files")
print()
print("💡 TIPS:")
print("• Use --dry-run on any command to test")
print("• Use --help on any command for specific options")
print("• Read https://docs.palmr.dev/docs/3.0-beta/translation-management for complete documentation")
return 0
elif args.command == 'check':
print("🔍 Checking translation status...")
return run_command('check_translations.py', remaining_args)
elif args.command == 'sync':
print("🔄 Synchronizing translation keys...")
return run_command('sync_translations.py', remaining_args)
elif args.command == 'translate':
print("🌐 Automatically translating strings...")
return run_command('translate_missing.py', remaining_args)
elif args.command == 'all':
print("⚡ Running complete translation workflow...")
print()
# Determine if it's dry-run based on arguments
is_dry_run = '--dry-run' in remaining_args
# 1. Initial check
print("1⃣ Checking initial status...")
result = run_command('check_translations.py', remaining_args)
if result != 0:
print("❌ Error in initial check")
return result
print("\n" + "="*50)
# 2. Sync
print("2⃣ Synchronizing missing keys...")
result = run_command('sync_translations.py', remaining_args)
if result != 0:
print("❌ Error in synchronization")
return result
if not is_dry_run:
print("\n" + "="*50)
# 3. Translate
print("3⃣ Automatically translating strings...")
result = run_command('translate_missing.py', remaining_args)
if result != 0:
print("❌ Error in translation")
return result
print("\n" + "="*50)
# 4. Final check
print("4⃣ Final check...")
result = run_command('check_translations.py', remaining_args)
if result != 0:
print("❌ Error in final check")
return result
print("\n🎉 Complete workflow executed successfully!")
if is_dry_run:
print("💡 Run without --dry-run to apply changes")
return 0
return 0
if __name__ == '__main__':
exit(main())

View File

@@ -0,0 +1,267 @@
#!/usr/bin/env python3
"""
Script to synchronize translations using en-US.json as reference.
Adds missing keys to other language files.
"""
import json
import os
from pathlib import Path
from typing import Dict, Any, Set, List
import argparse
def load_json_file(file_path: Path) -> Dict[str, Any]:
"""Load a JSON file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading {file_path}: {e}")
return {}
def save_json_file(file_path: Path, data: Dict[str, Any], indent: int = 2) -> bool:
"""Save a JSON file with consistent formatting."""
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=indent, separators=(',', ': '))
f.write('\n') # Add newline at the end
return True
except Exception as e:
print(f"Error saving {file_path}: {e}")
return False
def get_all_keys(data: Dict[str, Any], prefix: str = '') -> Set[str]:
"""Extract all keys from nested JSON recursively."""
keys = set()
for key, value in data.items():
current_key = f"{prefix}.{key}" if prefix else key
keys.add(current_key)
if isinstance(value, dict):
keys.update(get_all_keys(value, current_key))
return keys
def get_nested_value(data: Dict[str, Any], key_path: str) -> Any:
"""Get a nested value using a key with dots as separator."""
keys = key_path.split('.')
current = data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return None
return current
def set_nested_value(data: Dict[str, Any], key_path: str, value: Any) -> None:
"""Set a nested value using a key with dots as separator."""
keys = key_path.split('.')
current = data
# Navigate to the second-to-last level, creating dictionaries as needed
for key in keys[:-1]:
if key not in current:
current[key] = {}
elif not isinstance(current[key], dict):
current[key] = {}
current = current[key]
# Set the value at the last level
current[keys[-1]] = value
def find_missing_keys(reference_data: Dict[str, Any], target_data: Dict[str, Any]) -> List[str]:
"""Find keys that are in reference but not in target."""
reference_keys = get_all_keys(reference_data)
target_keys = get_all_keys(target_data)
missing_keys = reference_keys - target_keys
return sorted(list(missing_keys))
def add_missing_keys(reference_data: Dict[str, Any], target_data: Dict[str, Any],
missing_keys: List[str], mark_as_untranslated: bool = True) -> Dict[str, Any]:
"""Add missing keys to target_data using reference values."""
updated_data = target_data.copy()
for key_path in missing_keys:
reference_value = get_nested_value(reference_data, key_path)
if reference_value is not None:
# If marking as untranslated, add prefix
if mark_as_untranslated and isinstance(reference_value, str):
translated_value = f"[TO_TRANSLATE] {reference_value}"
else:
translated_value = reference_value
set_nested_value(updated_data, key_path, translated_value)
return updated_data
def sync_translations(messages_dir: Path, reference_file: str = 'en-US.json',
mark_as_untranslated: bool = True, dry_run: bool = False) -> None:
"""Synchronize all translations using a reference file."""
# Load reference file
reference_path = messages_dir / reference_file
if not reference_path.exists():
print(f"Reference file not found: {reference_path}")
return
print(f"Loading reference file: {reference_file}")
reference_data = load_json_file(reference_path)
if not reference_data:
print("Error loading reference file")
return
# Find all JSON files in the folder
json_files = [f for f in messages_dir.glob('*.json') if f.name != reference_file]
if not json_files:
print("No translation files found")
return
total_keys_reference = len(get_all_keys(reference_data))
print(f"Reference file contains {total_keys_reference} keys")
print(f"Processing {len(json_files)} translation files...\n")
summary = []
for json_file in sorted(json_files):
print(f"Processing: {json_file.name}")
# Load translation file
translation_data = load_json_file(json_file)
if not translation_data:
print(f" ❌ Error loading {json_file.name}")
continue
# Find missing keys
missing_keys = find_missing_keys(reference_data, translation_data)
current_keys = len(get_all_keys(translation_data))
if not missing_keys:
print(f" ✅ Complete ({current_keys}/{total_keys_reference} keys)")
summary.append({
'file': json_file.name,
'status': 'complete',
'missing': 0,
'total': current_keys
})
continue
print(f" 🔍 Found {len(missing_keys)} missing keys")
if dry_run:
print(f" 📝 [DRY RUN] Keys that would be added:")
for key in missing_keys[:5]: # Show only first 5
print(f" - {key}")
if len(missing_keys) > 5:
print(f" ... and {len(missing_keys) - 5} more")
else:
# Add missing keys
updated_data = add_missing_keys(reference_data, translation_data,
missing_keys, mark_as_untranslated)
# Save updated file
if save_json_file(json_file, updated_data):
print(f" ✅ Updated successfully ({current_keys + len(missing_keys)}/{total_keys_reference} keys)")
summary.append({
'file': json_file.name,
'status': 'updated',
'missing': len(missing_keys),
'total': current_keys + len(missing_keys)
})
else:
print(f" ❌ Error saving {json_file.name}")
summary.append({
'file': json_file.name,
'status': 'error',
'missing': len(missing_keys),
'total': current_keys
})
print()
# Show summary
print("=" * 60)
print("SUMMARY")
print("=" * 60)
if dry_run:
print("🔍 DRY RUN MODE - No changes were made\n")
for item in summary:
status_icon = {
'complete': '',
'updated': '🔄',
'error': ''
}.get(item['status'], '')
print(f"{status_icon} {item['file']:<15} - {item['total']}/{total_keys_reference} keys", end='')
if item['missing'] > 0:
print(f" (+{item['missing']} added)" if item['status'] == 'updated' else f" ({item['missing']} missing)")
else:
print()
def main():
parser = argparse.ArgumentParser(
description='Synchronize translations using en-US.json as reference'
)
parser.add_argument(
'--messages-dir',
type=Path,
default=Path(__file__).parent.parent / 'messages',
help='Directory containing message files (default: ../messages)'
)
parser.add_argument(
'--reference',
default='en-US.json',
help='Reference file (default: en-US.json)'
)
parser.add_argument(
'--no-mark-untranslated',
action='store_true',
help='Don\'t mark added keys as [TO_TRANSLATE]'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Only show what would be changed without making modifications'
)
args = parser.parse_args()
if not args.messages_dir.exists():
print(f"Directory not found: {args.messages_dir}")
return 1
print(f"Directory: {args.messages_dir}")
print(f"Reference: {args.reference}")
print(f"Mark untranslated: {not args.no_mark_untranslated}")
print(f"Dry run: {args.dry_run}")
print("-" * 60)
sync_translations(
messages_dir=args.messages_dir,
reference_file=args.reference,
mark_as_untranslated=not args.no_mark_untranslated,
dry_run=args.dry_run
)
return 0
if __name__ == '__main__':
exit(main())

View File

@@ -0,0 +1,342 @@
#!/usr/bin/env python3
"""
Script to automatically translate strings marked with [TO_TRANSLATE]
using free Google Translate.
"""
import json
import time
import re
from pathlib import Path
from typing import Dict, Any, List, Tuple, Optional
import argparse
import sys
# Language code mapping from file names to Google Translate codes
LANGUAGE_MAPPING = {
'pt-BR.json': 'pt', # Portuguese (Brazil) -> Portuguese
'es-ES.json': 'es', # Spanish (Spain) -> Spanish
'fr-FR.json': 'fr', # French (France) -> French
'de-DE.json': 'de', # German -> German
'it-IT.json': 'it', # Italian -> Italian
'ru-RU.json': 'ru', # Russian -> Russian
'ja-JP.json': 'ja', # Japanese -> Japanese
'ko-KR.json': 'ko', # Korean -> Korean
'zh-CN.json': 'zh-cn', # Chinese (Simplified) -> Simplified Chinese
'ar-SA.json': 'ar', # Arabic -> Arabic
'hi-IN.json': 'hi', # Hindi -> Hindi
'nl-NL.json': 'nl', # Dutch -> Dutch
'tr-TR.json': 'tr', # Turkish -> Turkish
'pl-PL.json': 'pl', # Polish -> Polish
}
# Prefix to identify untranslated strings
TO_TRANSLATE_PREFIX = '[TO_TRANSLATE] '
def load_json_file(file_path: Path) -> Dict[str, Any]:
"""Load a JSON file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"❌ Error loading {file_path}: {e}")
return {}
def save_json_file(file_path: Path, data: Dict[str, Any], indent: int = 2) -> bool:
"""Save a JSON file with consistent formatting."""
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=indent, separators=(',', ': '))
f.write('\n') # Add newline at the end
return True
except Exception as e:
print(f"❌ Error saving {file_path}: {e}")
return False
def get_nested_value(data: Dict[str, Any], key_path: str) -> Any:
"""Get a nested value using a key with dots as separator."""
keys = key_path.split('.')
current = data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return None
return current
def set_nested_value(data: Dict[str, Any], key_path: str, value: Any) -> None:
"""Set a nested value using a key with dots as separator."""
keys = key_path.split('.')
current = data
# Navigate to the second-to-last level, creating dictionaries as needed
for key in keys[:-1]:
if key not in current:
current[key] = {}
elif not isinstance(current[key], dict):
current[key] = {}
current = current[key]
# Set the value at the last level
current[keys[-1]] = value
def find_untranslated_strings(data: Dict[str, Any], prefix: str = '') -> List[Tuple[str, str]]:
"""Find all strings marked with [TO_TRANSLATE] recursively."""
untranslated = []
for key, value in data.items():
current_key = f"{prefix}.{key}" if prefix else key
if isinstance(value, str) and value.startswith(TO_TRANSLATE_PREFIX):
# Remove prefix to get original text
original_text = value[len(TO_TRANSLATE_PREFIX):].strip()
untranslated.append((current_key, original_text))
elif isinstance(value, dict):
untranslated.extend(find_untranslated_strings(value, current_key))
return untranslated
def install_googletrans():
"""Install the googletrans library if not available."""
try:
import googletrans
return True
except ImportError:
print("📦 'googletrans' library not found. Attempting to install...")
import subprocess
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "googletrans==4.0.0rc1"])
print("✅ googletrans installed successfully!")
return True
except subprocess.CalledProcessError:
print("❌ Failed to install googletrans. Install manually with:")
print("pip install googletrans==4.0.0rc1")
return False
def translate_text(text: str, target_language: str, max_retries: int = 3) -> Optional[str]:
"""Translate text using free Google Translate."""
try:
from googletrans import Translator
translator = Translator()
for attempt in range(max_retries):
try:
# Translate from English to target language
result = translator.translate(text, src='en', dest=target_language)
if result and result.text:
return result.text.strip()
except Exception as e:
if attempt < max_retries - 1:
print(f" ⚠️ Attempt {attempt + 1} failed: {str(e)[:50]}... Retrying in 2s...")
time.sleep(2)
else:
print(f" ❌ Failed after {max_retries} attempts: {str(e)[:50]}...")
return None
except ImportError:
print("❌ googletrans library not available")
return None
def translate_file(file_path: Path, target_language: str, dry_run: bool = False,
delay_between_requests: float = 1.0) -> Tuple[int, int, int]:
"""
Translate all [TO_TRANSLATE] strings in a file.
Returns: (total_found, successful_translations, failed_translations)
"""
print(f"🔍 Processing: {file_path.name}")
# Load file
data = load_json_file(file_path)
if not data:
return 0, 0, 0
# Find untranslated strings
untranslated_strings = find_untranslated_strings(data)
if not untranslated_strings:
print(f" ✅ No strings to translate")
return 0, 0, 0
print(f" 📝 Found {len(untranslated_strings)} strings to translate")
if dry_run:
print(f" 🔍 [DRY RUN] Strings that would be translated:")
for key, text in untranslated_strings[:3]:
print(f" - {key}: \"{text[:50]}{'...' if len(text) > 50 else ''}\"")
if len(untranslated_strings) > 3:
print(f" ... and {len(untranslated_strings) - 3} more")
return len(untranslated_strings), 0, 0
# Translate each string
successful = 0
failed = 0
updated_data = data.copy()
for i, (key_path, original_text) in enumerate(untranslated_strings, 1):
print(f" 📍 ({i}/{len(untranslated_strings)}) Translating: {key_path}")
# Translate text
translated_text = translate_text(original_text, target_language)
if translated_text and translated_text != original_text:
# Update in dictionary
set_nested_value(updated_data, key_path, translated_text)
successful += 1
print(f"\"{original_text[:30]}...\"\"{translated_text[:30]}...\"")
else:
failed += 1
print(f" ❌ Translation failed")
# Delay between requests to avoid rate limiting
if i < len(untranslated_strings): # Don't wait after the last one
time.sleep(delay_between_requests)
# Save updated file
if successful > 0:
if save_json_file(file_path, updated_data):
print(f" 💾 File saved with {successful} translations")
else:
print(f" ❌ Error saving file")
failed += successful # Count as failure if couldn't save
successful = 0
return len(untranslated_strings), successful, failed
def translate_all_files(messages_dir: Path, delay_between_requests: float = 1.0,
dry_run: bool = False, skip_languages: List[str] = None) -> None:
"""Translate all language files that have [TO_TRANSLATE] strings."""
if not install_googletrans():
return
skip_languages = skip_languages or []
# Find language JSON files
language_files = []
for file_name, lang_code in LANGUAGE_MAPPING.items():
file_path = messages_dir / file_name
if file_path.exists() and file_name not in skip_languages:
language_files.append((file_path, lang_code))
if not language_files:
print("❌ No language files found")
return
print(f"🌍 Translating {len(language_files)} languages...")
print(f"⏱️ Delay between requests: {delay_between_requests}s")
if dry_run:
print("🔍 DRY RUN MODE - No changes will be made")
print("-" * 60)
total_found = 0
total_successful = 0
total_failed = 0
for i, (file_path, lang_code) in enumerate(language_files, 1):
print(f"\n[{i}/{len(language_files)}] 🌐 Language: {lang_code.upper()}")
found, successful, failed = translate_file(
file_path, lang_code, dry_run, delay_between_requests
)
total_found += found
total_successful += successful
total_failed += failed
# Pause between files (except the last one)
if i < len(language_files) and not dry_run:
print(f" ⏸️ Pausing {delay_between_requests * 2}s before next language...")
time.sleep(delay_between_requests * 2)
# Final summary
print("\n" + "=" * 60)
print("📊 FINAL SUMMARY")
print("=" * 60)
if dry_run:
print(f"🔍 DRY RUN MODE:")
print(f"{total_found} strings would be translated")
else:
print(f"✅ Translations performed:")
print(f"{total_successful} successes")
print(f"{total_failed} failures")
print(f"{total_found} total processed")
if total_successful > 0:
success_rate = (total_successful / total_found) * 100
print(f" • Success rate: {success_rate:.1f}%")
print("\n💡 TIPS:")
print("• Run 'python3 check_translations.py' to verify results")
print("• Strings that failed translation keep the [TO_TRANSLATE] prefix")
print("• Consider reviewing automatic translations to ensure quality")
def main():
parser = argparse.ArgumentParser(
description='Automatically translate strings marked with [TO_TRANSLATE]'
)
parser.add_argument(
'--messages-dir',
type=Path,
default=Path(__file__).parent.parent / 'messages',
help='Directory containing message files (default: ../messages)'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Only show what would be translated without making changes'
)
parser.add_argument(
'--delay',
type=float,
default=1.0,
help='Delay in seconds between translation requests (default: 1.0)'
)
parser.add_argument(
'--skip-languages',
nargs='*',
default=[],
help='List of languages to skip (ex: pt-BR.json fr-FR.json)'
)
args = parser.parse_args()
if not args.messages_dir.exists():
print(f"❌ Directory not found: {args.messages_dir}")
return 1
print(f"📁 Directory: {args.messages_dir}")
print(f"🔍 Dry run: {args.dry_run}")
print(f"⏱️ Delay: {args.delay}s")
if args.skip_languages:
print(f"⏭️ Skipping: {', '.join(args.skip_languages)}")
print("-" * 60)
translate_all_files(
messages_dir=args.messages_dir,
delay_between_requests=args.delay,
dry_run=args.dry_run,
skip_languages=args.skip_languages
)
return 0
if __name__ == '__main__':
exit(main())