mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-04-28 14:29:54 +00:00
173 lines
6 KiB
Python
Executable file
173 lines
6 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Translation check script for OmniRoute.
|
|
Checks if all translation keys used in code exist in en.json.
|
|
|
|
Usage:
|
|
python scripts/check_translations.py
|
|
python scripts/check_translations.py --verbose
|
|
python scripts/check_translations.py --fix
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import os
|
|
import sys
|
|
import argparse
|
|
|
|
|
|
def get_namespaces_in_code(src_dir='src'):
|
|
"""Find all namespaces used in code via useTranslations()."""
|
|
used_ns = set()
|
|
for root, dirs, files in os.walk(src_dir):
|
|
for f in files:
|
|
if not (f.endswith('.tsx') or f.endswith('.ts')):
|
|
continue
|
|
try:
|
|
content = open(os.path.join(root, f), 'r', encoding='utf-8').read()
|
|
matches = re.findall(r'useTranslations\(["\']+([^"\']+)["\']+\)', content)
|
|
used_ns.update(matches)
|
|
except (IOError, UnicodeDecodeError) as e:
|
|
print(f"Warning: could not process file {os.path.join(root, f)}: {e}", file=sys.stderr)
|
|
return used_ns
|
|
|
|
|
|
def get_keys_in_json(json_path):
|
|
"""Get all keys (including nested) from a JSON file."""
|
|
with open(json_path, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
keys = set()
|
|
|
|
def traverse(obj, prefix=''):
|
|
if isinstance(obj, dict):
|
|
for k, v in obj.items():
|
|
key = f"{prefix}.{k}" if prefix else k
|
|
keys.add(key)
|
|
if isinstance(v, dict):
|
|
traverse(v, key)
|
|
elif isinstance(obj, list):
|
|
for item in obj:
|
|
traverse(item, prefix)
|
|
|
|
traverse(data)
|
|
return keys
|
|
|
|
|
|
def check_translations(src_dir='src', en_json_path='src/i18n/messages/en.json', verbose=False):
|
|
"""Check if all translation keys used in code exist in en.json."""
|
|
# Get namespaces used in code
|
|
used_ns = get_namespaces_in_code(src_dir)
|
|
|
|
# Get namespaces in en.json
|
|
with open(en_json_path, 'r', encoding='utf-8') as f:
|
|
en_data = json.load(f)
|
|
en_ns = set(en_data.keys())
|
|
|
|
# Find missing namespaces
|
|
missing_ns = sorted(used_ns - en_ns)
|
|
|
|
# Get all keys from en.json
|
|
en_keys = get_keys_in_json(en_json_path)
|
|
|
|
# Get all keys used in code
|
|
used_keys = set()
|
|
for root, dirs, files in os.walk(src_dir):
|
|
for f in files:
|
|
if not (f.endswith('.tsx') or f.endswith('.ts')):
|
|
continue
|
|
try:
|
|
content = open(os.path.join(root, f), 'r', encoding='utf-8').read()
|
|
matches = re.findall(r't\([\'"]+([^\'")]+)[\'"]+\)', content)
|
|
used_keys.update(matches)
|
|
except (IOError, UnicodeDecodeError) as e:
|
|
print(f"Warning: could not process file {os.path.join(root, f)}: {e}", file=sys.stderr)
|
|
|
|
# Filter out non-translation keys
|
|
# Note: check if key IS a path or ends with extension, not just contains it
|
|
# e.g., "invoice.ts.description" (ts = timestamp) should NOT be filtered
|
|
# but "components/Button.tsx" or "utils.ts" should be
|
|
def is_likely_file_path(key: str) -> bool:
|
|
if key.endswith('.ts') or key.endswith('.tsx') or key.endswith('.js') or key.endswith('.json'):
|
|
return True
|
|
if '/.ts' in key or '/.tsx' in key or '/.js' in key or '/.json' in key:
|
|
return True
|
|
if '\\.ts' in key or '\\.tsx' in key or '\\.js' in key or '\\.json' in key:
|
|
return True
|
|
return False
|
|
|
|
filtered = {k for k in used_keys if len(k) > 1
|
|
and not any(x in k for x in ['../', '@', '\\', '#', '?'])
|
|
and not is_likely_file_path(k)
|
|
and k not in ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH']
|
|
and not k.startswith('#')
|
|
and k not in [',', '-', '.', ':', '?', ' ', '']}
|
|
|
|
# Find missing keys
|
|
real_missing = []
|
|
for k in filtered:
|
|
if k in en_keys:
|
|
continue
|
|
|
|
# Check if key matches any full key in en.json
|
|
matched = False
|
|
for ek in en_keys:
|
|
if k in ek or ek.endswith(k):
|
|
matched = True
|
|
break
|
|
|
|
if matched:
|
|
continue
|
|
|
|
# Filter out paths and obvious non-translations
|
|
if k.startswith('./') or k.startswith('/') or k.startswith('x-') or k.startswith('user-'):
|
|
continue
|
|
# Use same logic - check if key IS a path or ends with extension
|
|
if is_likely_file_path(k):
|
|
continue
|
|
if k in ['Authorization', 'Content-Disposition', 'IOPlatformUUID', 'REG_SZ']:
|
|
continue
|
|
|
|
real_missing.append(k)
|
|
|
|
return missing_ns, sorted(real_missing)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Check translation keys in en.json')
|
|
parser.add_argument('--verbose', '-v', action='store_true', help='Show detailed output')
|
|
parser.add_argument('--fix', action='store_true', help='Generate fix suggestions')
|
|
parser.add_argument('--src', default='src', help='Source directory')
|
|
parser.add_argument('--json', default='src/i18n/messages/en.json', help='Path to en.json')
|
|
args = parser.parse_args()
|
|
|
|
missing_ns, missing_keys = check_translations(args.src, args.json, args.verbose)
|
|
|
|
has_issues = False
|
|
|
|
if missing_ns:
|
|
has_issues = True
|
|
print("=== MISSING NAMESPACES ===")
|
|
for ns in missing_ns:
|
|
print(f" - {ns}")
|
|
if args.fix:
|
|
print(f" → Add '{ns}' section to en.json")
|
|
|
|
if missing_keys:
|
|
has_issues = True
|
|
print("\n=== MISSING TRANSLATION KEYS ===")
|
|
for k in missing_keys:
|
|
print(f" - {k}")
|
|
if args.fix:
|
|
print(f" → Add to appropriate namespace in en.json")
|
|
|
|
if not has_issues:
|
|
print("✓ All translation namespaces and keys are present in en.json")
|
|
sys.exit(0)
|
|
else:
|
|
print(f"\nTotal: {len(missing_ns)} namespace(s), {len(missing_keys)} key(s) missing")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|