129 lines
4.6 KiB
Python
129 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Merge i18n translations from all pipeline modules into wwwroot/i18n.
|
||
|
||
模块自动发现(不再手工维护清单——2026-09-12 i18n 治理:手工清单漏了 17 个模块,
|
||
词条永远进不了字典):
|
||
1. 主应用自身 i18n/
|
||
2. pipeline_core / pipeline_ops(本地或 pkgs/)
|
||
3. build.sh clone 清单里的全部业务模块(pkgs/<mod> 或同级仓库目录)
|
||
4. pkgs/ 下任何带 i18n/ 目录的其它模块(兜底扫描)
|
||
|
||
每模块读 i18n/<lang>/msg.txt 与 i18n/<lang>/i18n.json,先到先得不覆盖。
|
||
|
||
Usage: python scripts/merge_i18n.py
|
||
"""
|
||
import os, json, re, sys
|
||
from collections import OrderedDict
|
||
|
||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
|
||
WWWROOT = os.path.join(ROOT_DIR, 'wwwroot')
|
||
I18N_OUT = os.path.join(WWWROOT, 'i18n')
|
||
LANGS = ['zh', 'en', 'ko', 'jp']
|
||
|
||
|
||
def discover_modules():
|
||
"""返回 [(name, i18n_dir), ...],顺序即合并优先级(主应用最先)。"""
|
||
mods = []
|
||
|
||
def add(name, i18n_dir):
|
||
if os.path.isdir(i18n_dir) and not any(n == name for n, _ in mods):
|
||
mods.append((name, i18n_dir))
|
||
|
||
# 1. 主应用
|
||
add('app', os.path.join(ROOT_DIR, 'i18n'))
|
||
|
||
# 2/3. build.sh clone 清单 + 本地业务模块
|
||
names = ['pipeline_core', 'pipeline_ops']
|
||
build_sh = os.path.join(ROOT_DIR, 'build.sh')
|
||
if os.path.exists(build_sh):
|
||
content = open(build_sh, encoding='utf-8').read()
|
||
for m in re.finditer(r'^for mod in ([^;]+); do\s*$', content, re.M):
|
||
for n in m.group(1).split():
|
||
if n not in names:
|
||
names.append(n)
|
||
repos_parent = os.path.dirname(ROOT_DIR)
|
||
for n in names:
|
||
for base in (os.path.join(ROOT_DIR, 'pkgs'), ROOT_DIR, repos_parent,
|
||
os.path.join(ROOT_DIR, n)):
|
||
cand = os.path.join(base, n, 'i18n') if not base.endswith(n) else os.path.join(base, 'i18n')
|
||
if os.path.isdir(cand):
|
||
add(n, cand)
|
||
break
|
||
|
||
# 4. 兜底:pkgs/ 下所有带 i18n/ 的目录
|
||
pkgs = os.path.join(ROOT_DIR, 'pkgs')
|
||
if os.path.isdir(pkgs):
|
||
for n in sorted(os.listdir(pkgs)):
|
||
add(n, os.path.join(pkgs, n, 'i18n'))
|
||
return mods
|
||
|
||
|
||
def parse_msg_txt(filepath):
|
||
"""Parse msg.txt: 'key=value' or 'key: value' format."""
|
||
result = OrderedDict()
|
||
if not os.path.exists(filepath):
|
||
return result
|
||
with open(filepath, 'r', encoding='utf-8') as f:
|
||
for line in f:
|
||
line = line.strip()
|
||
if not line or line.startswith('#'):
|
||
continue
|
||
if '=' in line:
|
||
key, val = line.split('=', 1)
|
||
result[key.strip()] = val.strip()
|
||
elif ':' in line:
|
||
key, val = line.split(':', 1)
|
||
result[key.strip()] = val.strip()
|
||
return result
|
||
|
||
|
||
def merge():
|
||
modules = discover_modules()
|
||
print(f'Discovered {len(modules)} i18n sources: {[n for n, _ in modules]}')
|
||
os.makedirs(I18N_OUT, exist_ok=True)
|
||
|
||
for lang in LANGS:
|
||
merged = OrderedDict()
|
||
out_dir = os.path.join(I18N_OUT, lang)
|
||
os.makedirs(out_dir, exist_ok=True)
|
||
|
||
# Load existing merged translations
|
||
i18n_file = os.path.join(out_dir, 'i18n.json')
|
||
if os.path.exists(i18n_file):
|
||
with open(i18n_file, 'r', encoding='utf-8') as f:
|
||
merged = json.load(f, object_pairs_hook=OrderedDict)
|
||
|
||
for mod_name, mod_i18n_dir in modules:
|
||
lang_dir = os.path.join(mod_i18n_dir, lang)
|
||
if not os.path.isdir(lang_dir):
|
||
continue
|
||
# msg.txt
|
||
msgs = parse_msg_txt(os.path.join(lang_dir, 'msg.txt'))
|
||
for k, v in msgs.items():
|
||
if k not in merged:
|
||
merged[k] = v
|
||
# i18n.json
|
||
mod_i18n = os.path.join(lang_dir, 'i18n.json')
|
||
if os.path.exists(mod_i18n):
|
||
try:
|
||
with open(mod_i18n, 'r', encoding='utf-8') as f:
|
||
extra = json.load(f, object_pairs_hook=OrderedDict)
|
||
except Exception as e:
|
||
print(f' !! {mod_name}/{lang}/i18n.json parse error: {e}', file=sys.stderr)
|
||
continue
|
||
for k, v in extra.items():
|
||
if k not in merged:
|
||
merged[k] = v
|
||
|
||
with open(i18n_file, 'w', encoding='utf-8') as f:
|
||
json.dump(merged, f, ensure_ascii=False, indent=2)
|
||
print(f' {lang}: {len(merged)} keys')
|
||
|
||
print(f'\nMerged i18n for {len(modules)} modules -> {I18N_OUT}')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
merge()
|