1+ # #!/usr/bin/env python3
2+ # """
3+ # Remove the `#, fuzzy` marker that precedes the header entry
4+ # (msgid "" / msgstr "") in .po files.
5+
6+ # Usage:
7+ # python remove_fuzzy_header.py path/to/dir_or_files...
8+ # """
9+ # import sys
10+ # import re
11+ # from pathlib import Path
12+
13+ # # Matches "#, fuzzy\n" immediately followed by the empty msgid/msgstr header
14+ # PATTERN = re.compile(
15+ # r'^#, fuzzy\n(?=msgid ""\nmsgstr "")',
16+ # re.MULTILINE
17+ # )
18+
19+ # def process_file(path: Path) -> bool:
20+ # text = path.read_text(encoding="utf-8")
21+ # new_text, count = PATTERN.subn("", text)
22+ # if count:
23+ # path.write_text(new_text, encoding="utf-8")
24+ # print(f"Updated: {path} ({count} removal)")
25+ # return True
26+ # return False
27+
28+ # def main(paths):
29+ # files = []
30+ # for p in paths:
31+ # p = Path(p)
32+ # if p.is_dir():
33+ # files.extend(p.rglob("*.po"))
34+ # elif p.suffix == ".po":
35+ # files.append(p)
36+
37+ # if not files:
38+ # print("No .po files found.")
39+ # return
40+
41+ # changed = 0
42+ # for f in files:
43+ # if process_file(f):
44+ # changed += 1
45+
46+ # print(f"\nDone. {changed}/{len(files)} file(s) modified.")
47+
48+ # if __name__ == "__main__":
49+ # if len(sys.argv) < 2:
50+ # print("Usage: python remove_fuzzy_header.py <dir_or_files...>")
51+ # sys.exit(1)
52+ # main(sys.argv[1:])
53+
54+ #!/usr/bin/env python3
55+ """
56+ Fix .po headers where the Last-Translator line is missing its trailing
57+ \\ n before the closing quote, causing it to swallow the next header
58+ line (e.g. Language-Team) into the same string.
59+
60+ Broken:
61+ "Last-Translator: NAME <EMAIL>, YEAR"
62+ "Language-Team: ...\\ n"
63+
64+ Fixed:
65+ "Last-Translator: NAME <EMAIL>, YEAR\\ n"
66+ "Language-Team: ...\\ n"
67+
68+ Usage:
69+ python fix_last_translator_newline.py --dry-run path/to/dir
70+ python fix_last_translator_newline.py path/to/dir
71+ """
72+ import re
73+ import argparse
74+ from pathlib import Path
75+
76+ # Matches a Last-Translator line whose value ends directly in a
77+ # closing quote with NO \n escape before it.
78+ PATTERN = re .compile (
79+ r'^"Last-Translator: ([^"\n]*?)"\n(?!")' , # lookahead not required but harmless
80+ re .MULTILINE
81+ )
82+ # More precise: only match if it does NOT already end with \n before the quote
83+ BROKEN = re .compile (r'^"Last-Translator: ([^"\n]*[^\\n])"\n' , re .MULTILINE )
84+
85+ def fix_text (text : str ):
86+ def repl (m ):
87+ value = m .group (1 )
88+ return f'"Last-Translator: { value } \\ n"\n '
89+ new_text , count = BROKEN .subn (repl , text )
90+ return new_text , count
91+
92+ def main ():
93+ parser = argparse .ArgumentParser ()
94+ parser .add_argument ("paths" , nargs = "+" )
95+ parser .add_argument ("--dry-run" , action = "store_true" )
96+ args = parser .parse_args ()
97+
98+ files = []
99+ for p in args .paths :
100+ p = Path (p )
101+ if p .is_dir ():
102+ files .extend (sorted (p .rglob ("*.po" )))
103+ elif p .suffix == ".po" :
104+ files .append (p )
105+
106+ changed = 0
107+ for f in files :
108+ if ".git" in f .parts :
109+ continue
110+ text = f .read_text (encoding = "utf-8" )
111+ new_text , count = fix_text (text )
112+ if count :
113+ print (f"{ 'Would fix' if args .dry_run else 'Fixed' } : { f } ({ count } line(s))" )
114+ if not args .dry_run :
115+ f .write_text (new_text , encoding = "utf-8" )
116+ changed += 1
117+
118+ print (f"\n Done. { changed } file(s) { 'would be' if args .dry_run else 'were' } fixed." )
119+
120+ if __name__ == "__main__" :
121+ main ()
0 commit comments