pipeline-app/scripts/merge_i18n.py

129 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()