1+ #!/usr/bin/env python3
2+ """
3+ translate_missing.py
4+
5+ This script translates missing or empty keys from the English i18n YAML
6+ file (hyrax.en.yml) into one or more target languages using a local
7+ LibreTranslate endpoint.
8+
9+ Why Python?
10+ - Preserves YAML formatting, comments, and inline/block style using ruamel.yaml
11+ - Ruby's built-in YAML tools rewrite formatting, causing unnecessary diffs
12+ in Git when only translation values change.
13+ - This script is run occasionally, so introducing a small Python helper
14+ is practical for keeping translation diffs clean.
15+
16+ Usage:
17+
18+ Set Up:
19+ 1. Make sure LibreTranslate is running locally in a separate shell:
20+ $ pip install libretranslate
21+ $ libretranslate
22+
23+ Run the script:
24+
25+ All at once (multiple languages):
26+ $ python translate_missing.py es zh it fr pt-BR
27+
28+ One at a time (single language):
29+ $ python translate_missing.py es
30+ """
31+ import json
32+ import sys
33+ import time
34+ import requests
35+ from ruamel .yaml import YAML
36+ from pathlib import Path
37+
38+ # ----------------------
39+ # Configuration
40+ # ----------------------
41+ SOURCE_LANG = "en"
42+ INPUT_FILE = Path ("config/locales/hyrax.en.yml" )
43+ LIBRETRANSLATE_URL = "http://127.0.0.1:5000/translate"
44+
45+ yaml = YAML ()
46+ yaml .explicit_start = True # Add '---' at the top
47+ yaml .preserve_quotes = True
48+ yaml .width = 4096 # avoid folding long lines
49+
50+ # ----------------------
51+ # Load source YAML once
52+ # ----------------------
53+ source_data = yaml .load (INPUT_FILE .read_text ())
54+ source = source_data [SOURCE_LANG ]
55+
56+ # ----------------------
57+ # Translation helper
58+ # ----------------------
59+ def translate_text (text , source_lang = SOURCE_LANG , target_lang = "es" ):
60+ if text is None or str (text ).strip () == "" :
61+ return text
62+ try :
63+ resp = requests .post (
64+ LIBRETRANSLATE_URL ,
65+ data = {"q" : text , "source" : source_lang , "target" : target_lang },
66+ )
67+ resp .raise_for_status ()
68+ translated = json .loads (resp .text )["translatedText" ]
69+ print (f"Translated ({ target_lang } ): { text } → { translated } " )
70+ return translated
71+ except Exception as e :
72+ print (f"⚠️ Translation error for '{ text } ': { e } " )
73+ return text
74+
75+ # ----------------------
76+ # Recursive merge & translate
77+ # ----------------------
78+ def deep_merge_translate (source_dict , target_dict , target_lang ):
79+ result = target_dict .copy ()
80+ for key , value in source_dict .items ():
81+ if isinstance (value , dict ):
82+ result [key ] = deep_merge_translate (value , result .get (key , {}), target_lang )
83+ else :
84+ if key not in result or str (result [key ]).strip () == "" :
85+ result [key ] = translate_text (value , target_lang = target_lang )
86+ return result
87+
88+ # ----------------------
89+ # Main loop over target languages
90+ # ----------------------
91+ if len (sys .argv ) < 2 :
92+ print ("Usage: python translate_locales.py <lang1> [<lang2> ...]" )
93+ sys .exit (1 )
94+
95+ for target_lang in sys .argv [1 :]:
96+ print (f"\n === Translating into { target_lang } ===" )
97+ output_file = Path (f"config/locales/hyrax.{ target_lang } .yml" )
98+
99+ # Load existing target YAML if present
100+ if output_file .exists ():
101+ target_data = yaml .load (output_file .read_text ())
102+ target = target_data .get (target_lang , {})
103+ else :
104+ target = {}
105+
106+ # Merge and translate
107+ merged = deep_merge_translate (source , target , target_lang )
108+
109+ # Write back preserving formatting
110+ output_data = {target_lang : merged }
111+ with output_file .open ("w" ) as f :
112+ yaml .dump (output_data , f )
113+
114+ print (f"✅ Updated { output_file } — existing translations preserved." )
0 commit comments