Skip to content

Commit aa5a3ed

Browse files
xtexxWerWolv
andauthored
lang: Refactored langtool, updated chinese translation (#1623)
- Better argument parsing - Allow processing all language folders at the same time - Allow an optional reference language when translating - Save translations on KeyboardInterrupt - Fixes a ooold input issues by importing readline (kovidgoyal/kitty#6560) - Add untranslate mode to remove translations by a key regex --------- Co-authored-by: Nik <[email protected]>
1 parent 6fbbf89 commit aa5a3ed

File tree

9 files changed

+478
-364
lines changed

9 files changed

+478
-364
lines changed

dist/langtool.py

100644100755
+176-72
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,208 @@
1+
#!/usr/bin/env python3
12
from pathlib import Path
2-
import sys
3+
import argparse
34
import json
45

6+
# This fixes a CJK full-width character input issue
7+
# which makes left halves of deleted characters displayed on screen
8+
# pylint: disable=unused-import
9+
import re
10+
import readline
11+
512
DEFAULT_LANG = "en_US"
13+
DEFAULT_LANG_PATH = "plugins/*/romfs/lang/"
614
INVALID_TRANSLATION = ""
715

816

9-
def handle_missing_key(command, lang_data, key, value):
10-
if command == "check":
11-
print(f"Error: Translation {lang_data['code']} is missing translation for key '{key}'")
12-
exit(2)
13-
elif command == "translate" or command == "create":
14-
print(f"Key \033[1m'{key}': '{value}'\033[0m is missing in translation '{lang_data['code']}'")
15-
new_value = input("Enter translation: ")
16-
lang_data["translations"][key] = new_value
17-
elif command == "update":
18-
lang_data["translations"][key] = INVALID_TRANSLATION
19-
20-
2117
def main():
22-
if len(sys.argv) < 3:
23-
print(f"Usage: {Path(sys.argv[0]).name} <check|translate|update|create> <lang folder path> <language>")
24-
return 1
25-
26-
command = sys.argv[1]
27-
if command not in ["check", "translate", "update", "create"]:
28-
print(f"Unknown command: {command}")
29-
return 1
30-
31-
print(f"Using langtool in {command} mode")
32-
33-
lang_folder_path = Path(sys.argv[2])
34-
if not lang_folder_path.exists():
35-
print(f"Error: {lang_folder_path} does not exist")
36-
return 1
37-
38-
if not lang_folder_path.is_dir():
39-
print(f"Error: {lang_folder_path} is not a folder")
18+
parser = argparse.ArgumentParser(
19+
prog="langtool",
20+
description="ImHex translate tool",
21+
)
22+
parser.add_argument(
23+
"command",
24+
choices=[
25+
"check",
26+
"translate",
27+
"update",
28+
"create",
29+
"retranslate",
30+
"untranslate",
31+
"fmtzh",
32+
],
33+
)
34+
parser.add_argument(
35+
"-c", "--langdir", default=DEFAULT_LANG_PATH, help="Language folder glob"
36+
)
37+
parser.add_argument("-l", "--lang", default="", help="Language to translate")
38+
parser.add_argument(
39+
"-r", "--reflang", default="", help="Language for reference when translating"
40+
)
41+
parser.add_argument(
42+
"-k", "--keys", help="Keys to re-translate (only in re/untranslate mode)"
43+
)
44+
args = parser.parse_args()
45+
46+
command = args.command
47+
lang = args.lang
48+
49+
print(f"Running in {command} mode")
50+
lang_files_glob = f"{lang}.json" if lang != "" else "*.json"
51+
52+
lang_folders = set(Path(".").glob(args.langdir))
53+
if len(lang_folders) == 0:
54+
print(f"Error: {args.langdir} matches nothing")
4055
return 1
4156

42-
lang = sys.argv[3] if len(sys.argv) > 3 else ""
43-
44-
print(f"Processing language files in {lang_folder_path}...")
45-
46-
default_lang_file_path = lang_folder_path / Path(DEFAULT_LANG + ".json")
47-
if not default_lang_file_path.exists():
48-
print(f"Error: Default language file {default_lang_file_path} does not exist")
49-
return 1
50-
51-
print(f"Using file '{default_lang_file_path.name}' as template language file")
52-
53-
with default_lang_file_path.open("r", encoding="utf-8") as default_lang_file:
54-
default_lang_data = json.load(default_lang_file)
57+
for lang_folder in lang_folders:
58+
if not lang_folder.is_dir():
59+
print(f"Error: {lang_folder} is not a folder")
60+
return 1
61+
62+
default_lang_data = {}
63+
default_lang_path = lang_folder / Path(DEFAULT_LANG + ".json")
64+
if not default_lang_path.exists():
65+
print(
66+
f"Error: Default language file {default_lang_path} does not exist in {lang_folder}"
67+
)
68+
return 1
69+
with default_lang_path.open("r", encoding="utf-8") as file:
70+
default_lang_data = json.load(file)
71+
72+
reference_lang_data = None
73+
reference_lang_path = lang_folder / Path(args.reflang + ".json")
74+
if reference_lang_path.exists():
75+
with reference_lang_path.open("r", encoding="utf-8") as file:
76+
reference_lang_data = json.load(file)
5577

5678
if command == "create" and lang != "":
57-
lang_file_path = lang_folder_path / Path(lang + ".json")
79+
lang_file_path = lang_folder / Path(lang + ".json")
5880
if lang_file_path.exists():
59-
print(f"Error: Language file {lang_file_path} already exists")
60-
return 1
81+
continue
82+
83+
exist_lang_data = None
84+
for lang_folder1 in lang_folders:
85+
lang_file_path1 = lang_folder1 / Path(lang + ".json")
86+
if lang_file_path1.exists():
87+
with lang_file_path1.open("r", encoding="utf-8") as file:
88+
exist_lang_data = json.load(file)
89+
break
6190

62-
print(f"Creating new language file '{lang_file_path.name}'")
91+
print(f"Creating new language file '{lang_file_path}'")
6392

6493
with lang_file_path.open("w", encoding="utf-8") as new_lang_file:
6594
new_lang_data = {
6695
"code": lang,
67-
"language": input("Enter language: "),
68-
"country": input("Enter country: "),
69-
"translations": {}
96+
"language": (
97+
exist_lang_data["language"]
98+
if exist_lang_data
99+
else input("Enter language name: ")
100+
),
101+
"country": (
102+
exist_lang_data["country"]
103+
if exist_lang_data
104+
else input("Enter country name: ")
105+
),
106+
"translations": {},
70107
}
71108
json.dump(new_lang_data, new_lang_file, indent=4, ensure_ascii=False)
72109

73-
for additional_lang_file_path in lang_folder_path.glob("*.json"):
74-
if not lang == "" and not additional_lang_file_path.stem == lang:
110+
lang_files = set(lang_folder.glob(lang_files_glob))
111+
if len(lang_files) == 0:
112+
print(f"Warn: Language file for '{lang}' does not exist in '{lang_folder}'")
113+
for lang_file_path in lang_files:
114+
if (
115+
lang_file_path.stem == f"{DEFAULT_LANG}.json"
116+
or lang_file_path.stem == f"{args.reflang}.json"
117+
):
75118
continue
76119

77-
if additional_lang_file_path.name.startswith(DEFAULT_LANG):
78-
continue
120+
print(f"\nProcessing '{lang_file_path}'")
121+
if not (command == "update" or command == "create"):
122+
print("\n----------------------------\n")
79123

80-
print(f"\nProcessing file '{additional_lang_file_path.name}'\n----------------------------\n")
81-
82-
with additional_lang_file_path.open("r+", encoding="utf-8") as additional_lang_file:
83-
additional_lang_data = json.load(additional_lang_file)
124+
with lang_file_path.open("r+", encoding="utf-8") as target_lang_file:
125+
lang_data = json.load(target_lang_file)
84126

85127
for key, value in default_lang_data["translations"].items():
86-
if key not in additional_lang_data["translations"] or additional_lang_data["translations"][key] == INVALID_TRANSLATION:
87-
handle_missing_key(command, additional_lang_data, key, value)
128+
has_translation = (
129+
key in lang_data["translations"]
130+
and lang_data["translations"][key] != INVALID_TRANSLATION
131+
)
132+
if not has_translation and not (
133+
(command == "retranslate" or command == "untranslate")
134+
and re.compile(args.keys).fullmatch(key)
135+
):
136+
continue
137+
if command == "check":
138+
print(
139+
f"Error: Translation {lang_data['code']} is missing translation for key '{key}'"
140+
)
141+
exit(2)
142+
elif (
143+
command == "translate"
144+
or command == "retranslate"
145+
or command == "untranslate"
146+
):
147+
if command == "untranslate" and not has_translation:
148+
continue
149+
reference_tranlsation = (
150+
" '%s'" % reference_lang_data["translations"][key]
151+
if reference_lang_data
152+
else ""
153+
)
154+
print(
155+
f"\033[1m'{key}' '{value}'{reference_tranlsation}\033[0m => {lang_data['language']}",
156+
end="",
157+
)
158+
if has_translation:
159+
translation = lang_data["translations"][key]
160+
print(f" <= \033[1m'{translation}'\033[0m")
161+
print() # for a new line
162+
if command == "untranslate":
163+
lang_data["translations"][key] = INVALID_TRANSLATION
164+
continue
165+
try:
166+
new_value = input("=> ")
167+
lang_data["translations"][key] = new_value
168+
except KeyboardInterrupt:
169+
break
170+
elif command == "update" or command == "create":
171+
lang_data["translations"][key] = INVALID_TRANSLATION
172+
elif command == "fmtzh":
173+
if has_translation:
174+
lang_data["translations"][key] = fmtzh(
175+
lang_data["translations"][key]
176+
)
88177

89178
keys_to_remove = []
90-
for key, value in additional_lang_data["translations"].items():
179+
for key, value in lang_data["translations"].items():
91180
if key not in default_lang_data["translations"]:
92181
keys_to_remove.append(key)
93-
94182
for key in keys_to_remove:
95-
additional_lang_data["translations"].pop(key)
96-
print(f"Removed unused key '{key}' from translation '{additional_lang_data['code']}'")
97-
98-
additional_lang_file.seek(0)
99-
additional_lang_file.truncate()
100-
json.dump(additional_lang_data, additional_lang_file, indent=4, sort_keys=True, ensure_ascii=False)
101-
102-
103-
if __name__ == '__main__':
183+
lang_data["translations"].pop(key)
184+
print(
185+
f"Removed unused key '{key}' from translation '{lang_data['code']}'"
186+
)
187+
188+
target_lang_file.seek(0)
189+
target_lang_file.truncate()
190+
json.dump(
191+
lang_data,
192+
target_lang_file,
193+
indent=4,
194+
sort_keys=True,
195+
ensure_ascii=False,
196+
)
197+
198+
199+
def fmtzh(text: str) -> str:
200+
text = re.sub(r"(\.{3}|\.{6})", "……", text)
201+
text = text.replace("!", "!")
202+
text = re.sub(r"([^\.\na-zA-Z\d])\.$", "\1。", text, flags=re.M)
203+
text = text.replace("?", "?")
204+
return text
205+
206+
207+
if __name__ == "__main__":
104208
exit(main())

0 commit comments

Comments
 (0)