22import os
33import xml .etree .ElementTree as ET
44import polib
5+ import langid
56
6-
7- def normalize (s : str ) -> str :
8- # Normalize strings for reliable comparison (trim, lowercase, collapse spaces)
9- return " " .join ((s or "" ).strip ().lower ().split ())
10-
7+ def normalize (s : str | None ) -> str :
8+ return " " .join ((s or "" ).strip ().lower ().split ())
119
1210# -----------------------------
13- # PO FILE CHECK
11+ # PO CHECK
1412# -----------------------------
15- def checkPo (path : str ) -> float :
16- # Parse PO file using polib
17- po = polib .pofile (path )
18-
19- translated = 0
20- total = 0
2113
22- for entry in po :
23- # Skip empty msgid entries
24- if not entry . msgid . strip ():
25- continue
14+ def checkPo ( path : str ) -> float :
15+ po = polib . pofile ( path )
16+ translated = 0
17+ total = 0
2618
27- total += 1
19+ for entry in po :
20+ if not entry .msgid .strip ():
21+ continue
2822
29- # Consider entry translated only if msgstr differs from msgid
30- if entry .msgstr and normalize (entry .msgstr ) != normalize (entry .msgid ):
31- translated += 1
23+ total += 1
3224
33- return translated / total if total else 0.0
25+ if entry .msgstr and normalize (entry .msgstr ) != normalize (entry .msgid ):
26+ translated += 1
3427
28+ return translated / total if total else 0.0
3529
3630# -----------------------------
37- # XLIFF CHECK (skeleton-safe generic parsing)
31+ # XLIFF CHECK
3832# -----------------------------
33+
3934def checkXliff (path : str ) -> float :
40- # Parse XML XLIFF file
41- tree = ET .parse (path )
42- root = tree .getroot ()
35+ tree = ET .parse (path )
36+ root = tree .getroot ()
37+ translated = 0
38+ total = 0
39+ source = None
4340
44- translated = 0
45- total = 0
41+ for elem in root .iter ():
42+ if elem .tag .endswith ("source" ):
43+ source = normalize (elem .text )
4644
47- source = None
45+ elif elem .tag .endswith ("target" ):
46+ target = normalize (elem .text )
4847
49- for elem in root .iter ():
48+ if source :
49+ total += 1
50+ if target and target != source :
51+ translated += 1
5052
51- # Capture source segments
52- if elem .tag .endswith ("source" ):
53- source = normalize (elem .text )
53+ return translated / total if total else 0.0
5454
55- # Compare with target segments
56- elif elem . tag . endswith ( "target" ):
57- target = normalize ( elem . text )
55+ # -----------------------------
56+ # MD LANGUAGE SCORE (langid)
57+ # -----------------------------
5858
59- if source :
60- total += 1
59+ def scoreMd (path : str , expected_lang : str ) -> float :
60+ try :
61+ with open (path , "r" , encoding = "utf-8" ) as f :
62+ text = f .read ()
63+ except Exception :
64+ return 0.0
6165
62- # Count as translated only if target differs from source
63- if target and target != source :
64- translated += 1
66+ if not text .strip ():
67+ return 0.0
6568
66- return translated / total if total else 0.0
69+ lang , score = langid . classify ( text )
6770
71+ # Normalize score into positive confidence
72+ confidence = 1 / (1 + abs (score ))
73+
74+ if lang == expected_lang :
75+ return confidence
76+ else :
77+ return 0.0
6878
6979# -----------------------------
70- # MAIN ENTRY POINT
80+ # COMPARE MULTIPLE MD FILES
7181# -----------------------------
72- def main ():
73- if len (sys .argv ) < 2 :
74- print ("Usage: checkTranslation.py <file>" )
75- sys .exit (2 )
7682
77- path = sys .argv [1 ]
83+ def compareMd (files : list [str ], lang : str ):
84+ results = []
7885
79- if not os . path . exists ( path ) :
80- print ( f"File not found: { path } " )
81- sys . exit ( 2 )
86+ for f in files :
87+ if not os . path . exists ( f ):
88+ continue
8289
83- ext = os .path .splitext (path )[1 ].lower ()
90+ score = scoreMd (f , lang )
91+ results .append ((f , score ))
8492
85- # Dispatch based on file type
86- if ext == ".po" :
87- ratio = checkPo ( path )
93+ if not results :
94+ print ( "winner=None" )
95+ sys . exit ( 1 )
8896
89- elif ext in [".xliff" , ".xlf" ]:
90- ratio = checkXliff (path )
97+ results .sort (key = lambda x : x [1 ], reverse = True )
9198
92- else :
93- print (f"Unsupported file type: { ext } " )
94- sys .exit (2 )
99+ winner = results [0 ]
95100
96- print (f"translation_ratio={ ratio } " )
101+ print ("comparison_results:" )
102+ for f , s in results :
103+ print (f"{ f } ={ s } " )
97104
98- # Threshold: consider file translated if above 5%
99- if ratio > 0.05 :
100- sys .exit (0 ) # translated
101- else :
102- sys .exit (1 ) # not translated
105+ print (f"winner={ winner [0 ]} " )
106+ print (f"winner_score={ winner [1 ]} " )
103107
108+ sys .exit (0 )
109+
110+ # -----------------------------
111+ # MAIN
112+ # -----------------------------
113+
114+ def main ():
115+ if len (sys .argv ) < 2 :
116+ print ("Usage:" )
117+ print (" checkTranslation.py <file>" )
118+ print (" checkTranslation.py <file> <lang>" )
119+ print (" checkTranslation.py <file1> <file2> [...] <lang>" )
120+ sys .exit (2 )
121+
122+ args = sys .argv [1 :]
123+
124+ # -------------------------
125+ # MULTI FILE MODE
126+ # -------------------------
127+ if len (args ) >= 3 :
128+ * files , lang = args
129+ compareMd (files , lang )
130+ return
131+
132+ path = args [0 ]
133+
134+ if not os .path .exists (path ):
135+ print (f"File not found: { path } " )
136+ sys .exit (2 )
137+
138+ ext = os .path .splitext (path )[1 ].lower ()
139+
140+ # -------------------------
141+ # PO
142+ # -------------------------
143+ if ext == ".po" :
144+ ratio = checkPo (path )
145+ print (f"translation_ratio={ ratio } " )
146+ sys .exit (0 if ratio > 0.05 else 1 )
147+
148+ # -------------------------
149+ # XLIFF
150+ # -------------------------
151+ elif ext in [".xliff" , ".xlf" ]:
152+ ratio = checkXliff (path )
153+ print (f"translation_ratio={ ratio } " )
154+ sys .exit (0 if ratio > 0.05 else 1 )
155+
156+ # -------------------------
157+ # MD (LANG SCORE)
158+ # -------------------------
159+ elif ext == ".md" :
160+ if len (args ) < 2 :
161+ print ("Missing language argument for MD scoring" )
162+ sys .exit (2 )
163+
164+ lang = args [1 ]
165+ score = scoreMd (path , lang )
166+
167+ print (f"md_score={ score } " )
168+ sys .exit (0 )
169+
170+ else :
171+ print (f"Unsupported file type: { ext } " )
172+ sys .exit (2 )
104173
105174if __name__ == "__main__" :
106- main ()
175+ main ()
0 commit comments