pipeline-app/scripts/merge_i18n.py
yumoqing 9674a2ee4b feat(ticket): 工单管理模块应用集成——load链/build.sh三清单/种子/i18n/菜单/冒烟
- app/pipeline_app.py: try-import load_ticket()+load链(pipeline_task之后,依赖pipeline_service的provider注册/llm_bridge/rag_client)
- build.sh: clone/pip install/wwwroot软链三清单加ticket(无json/CRUD故不进xls2ui清单)
- conf/config.json module_dbname: ticket→pipeline; scripts/import_init.py INIT_MODULES加ticket(码表+owner.maintainer角色种子)
- scripts/merge_i18n.py MODULES加ticket; i18n四语言+3词条(我的工单/工单管理/提交新工单)
- wwwroot/index.ui: 客户分支「我的工单」;管理分支「工单管理」(is_ticket_staff=owner.maintainer/admin/superuser Jinja门禁)
- deploy/smoke: s15/s16工单两页面用例
- rp.json不改: 权限由模块load_path.py逐个精确配(管理页壳owner三角色,业务权限服务端按current_role动态校验)
2026-09-10 16:14:39 +08:00

108 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""
Merge i18n translations from all pipeline modules into wwwroot/i18n.
Scans each module's i18n directory and merges into the app's centralized i18n.
Usage: python scripts/merge_i18n.py
"""
import os, json, sys
from collections import OrderedDict
from pathlib import Path
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')
# Modules to scan for i18n
MODULES = [
('app', os.path.join(ROOT_DIR, 'i18n')),
('pipeline_core', os.path.join(ROOT_DIR, 'pipeline_core', 'i18n')),
('pipeline_ops', os.path.join(ROOT_DIR, 'pipeline_ops', 'i18n')),
('pipeline-sdlc', os.path.join(os.path.dirname(ROOT_DIR), 'pipeline-sdlc', 'i18n')),
('showcase', os.path.join(os.path.dirname(ROOT_DIR), 'showcase', 'i18n')),
('pipeline-bidding', os.path.join(ROOT_DIR, 'pkgs', 'pipeline-bidding', 'i18n')),
('pipeline-opportunity', os.path.join(ROOT_DIR, 'pkgs', 'pipeline-opportunity', 'i18n')),
('dingdingflow', os.path.join(ROOT_DIR, 'pkgs', 'dingdingflow', 'i18n')),
('rbac', os.path.join(ROOT_DIR, 'pkgs', 'rbac', 'i18n')),
('ticket', os.path.join(ROOT_DIR, 'pkgs', 'ticket', 'i18n')),
]
LANGS = ['zh', 'en', 'ko', 'jp']
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():
os.makedirs(I18N_OUT, exist_ok=True)
stats = {}
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)
# Scan each module
for mod_name, mod_i18n_dir in MODULES:
lang_dir = os.path.join(mod_i18n_dir, lang)
if not os.path.isdir(lang_dir):
# Try app's internal i18n
alt_dir = os.path.join(ROOT_DIR, 'pipeline_core', 'i18n', lang)
if not os.path.isdir(alt_dir):
continue
lang_dir = alt_dir
# Load msg.txt
msg_file = os.path.join(lang_dir, 'msg.txt')
if os.path.exists(msg_file):
msgs = parse_msg_txt(msg_file)
for k, v in msgs.items():
if k not in merged:
merged[k] = v
stats.setdefault(mod_name, 0)
stats[mod_name] += 1
# Load i18n.json
mod_i18n = os.path.join(lang_dir, 'i18n.json')
if os.path.exists(mod_i18n):
with open(mod_i18n, 'r', encoding='utf-8') as f:
extra = json.load(f, object_pairs_hook=OrderedDict)
for k, v in extra.items():
if k not in merged:
merged[k] = v
# Write merged
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')
# Write msg.txt for the app root
print(f'\nMerged i18n for {len(MODULES)} modules -> {I18N_OUT}')
if __name__ == '__main__':
merge()