check-translations.py (4105B)
1 #!/usr/bin/env python3 2 """ 3 check-translations.py 4 5 Finds all Android string resources by first locating the git root (.git folder), allowing to 6 run it reliably from sub folders. 7 8 Usage: 9 python3 check-translations.py # Overview of all languages, across all repo modules 10 python3 check-translations.py de # Show concrete missing German strings per-module 11 """ 12 13 import argparse 14 import xml.etree.ElementTree as ET 15 from pathlib import Path 16 from collections import defaultdict 17 18 19 def find_git_root(start: Path) -> Path | None: 20 """Walk upwards until we find a .git directory.""" 21 current = start.resolve() 22 while current != current.parent: 23 if (current / ".git").exists(): 24 return current 25 current = current.parent 26 return None 27 28 29 def get_string_names(xml_path: Path) -> set[str]: 30 if not xml_path.exists(): 31 return set() 32 try: 33 tree = ET.parse(xml_path) 34 return { 35 elem.get("name") 36 for elem in tree.findall(".//string") 37 # Only propose string names which are thought to be translated. 38 if elem.get("name") and elem.get("translatable") != "false" 39 } 40 except ET.ParseError: 41 return set() 42 43 44 def find_all_res_directories(git_root: Path) -> list[Path]: 45 """Find all res/ folders inside the git repository.""" 46 res_dirs = [] 47 for values_dir in git_root.rglob("values"): 48 if (values_dir / "strings.xml").exists(): 49 res_dirs.append(values_dir.parent) 50 return sorted(set(res_dirs)) 51 52 53 def main(): 54 parser = argparse.ArgumentParser() 55 parser.add_argument("lang", nargs="?", help="Language code (e.g., de or it)") 56 args = parser.parse_args() 57 58 git_root = find_git_root(Path.cwd()) 59 60 if not git_root: 61 print("Could not find a .git directory. Please run from inside a git repository.") 62 return 63 64 print(f"Git root: {git_root}\n") 65 66 res_dirs = find_all_res_directories(git_root) 67 68 if not res_dirs: 69 print("No Android string resources found in this repository.") 70 return 71 72 print(f"Found {len(res_dirs)} resource folder(s)\n") 73 74 lang_data = defaultdict(list) 75 76 for res_dir in res_dirs: 77 ref_file = res_dir / "values" / "strings.xml" 78 if not ref_file.exists(): 79 continue 80 81 ref_names = get_string_names(ref_file) 82 if not ref_names: 83 continue 84 85 for values_dir in res_dir.glob("values-*"): 86 lang_code = values_dir.name.removeprefix("values-") 87 strings_file = values_dir / "strings.xml" 88 if not strings_file.exists(): 89 continue 90 91 present = get_string_names(strings_file) 92 missing = ref_names - present 93 94 lang_data[lang_code].append({ 95 "res_dir": res_dir, 96 "missing_count": len(missing), 97 "missing_names": sorted(missing) 98 }) 99 100 # Overview 101 print("Language Folders Total Missing") 102 print("-" * 35) 103 for lang in sorted(lang_data.keys()): 104 entries = lang_data[lang] 105 total_missing = sum(e["missing_count"] for e in entries) 106 print(f"{lang:<10} {len(entries):>7} {total_missing:>6}") 107 108 print() 109 110 if args.lang: 111 lang = args.lang 112 if lang not in lang_data: 113 print(f"No '{lang}' translations found.") 114 return 115 116 print(f"=== Missing strings in '{lang}' ===\n") 117 118 for entry in lang_data[lang]: 119 res_dir = entry["res_dir"] 120 missing_names = entry["missing_names"] 121 122 # Show actual module names (merchant-terminal, cashier, wallet, ...) 123 try: 124 module = res_dir.relative_to(git_root).parts[0] 125 except Exception: 126 module = str(res_dir) 127 128 print(f"→ {module} ({entry['missing_count']} missing)") 129 130 if missing_names: 131 for name in missing_names: 132 print(f" {name}") 133 else: 134 print(" (complete)") 135 print() 136 137 138 if __name__ == "__main__": 139 main()