#!/usr/bin/env python3 """ 合并所有Sage模块的i18n内容到 wwwroot/i18n 模块清单与 build.sh 保持一致(第29行 for m in ...), 读取每个模块的 i18n/{lang}/msg.txt, 合并输出到 wwwroot/i18n/{lang}/i18n.json + msg.txt。 冲突策略:按 build.sh 中模块顺序,后加载的覆盖先加载的。 sage 自身排在首位(优先级最低)。 """ import json import re import sys from pathlib import Path # --- 配置 --- SAGE_DIR = Path(__file__).resolve().parent # /d/ymq/repos/sage REPOS_DIR = SAGE_DIR.parent # /d/ymq/repos I18N_OUTPUT_DIR = SAGE_DIR / 'wwwroot' / 'i18n' # 输出目标 LANGS = ['zh', 'en', 'jp', 'ko'] # 支持的语言 def parse_build_sh(build_sh_path): """从 build.sh 中提取模块清单(第29行 for m in ...) build.sh 有两个 for m in 循环: - 第21行: pip基础库(apppublic, sqlor, ahserver, ...) - 第29行: Sage业务模块(appbase, rbac, accounting, ...) 我们取第二个(业务模块),与 sage 的 wwwroot 符号链接配置一致。 """ with open(build_sh_path, 'r', encoding='utf-8') as f: content = f.read() # 匹配所有 "for m in ..." 行 —— 只到行尾(不用 \s 防止跨行匹配到 do) matches = re.findall(r'^for m in ((?:[a-z][\w-]*[ \t]*)+)$', content, re.MULTILINE) if len(matches) < 2: sys.exit(f"错误: 在 build.sh 中找到 {len(matches)} 个 for m in 行,期望至少2个") # 第二个 for m in 是 Sage 业务模块清单 modules_str = matches[1] modules = [m for m in modules_str.strip().split() if '-' not in m] # sage → bricks → 业务模块(优先级递增,后者覆盖前者) # bricks 是基础框架,不在 build.sh 业务模块循环中,需手动加入 result = ['sage', 'bricks'] + modules return result def find_module_i18n_dir(module_name): """查找模块的 i18n 目录""" candidate = REPOS_DIR / module_name / 'i18n' if candidate.is_dir(): return candidate return None def parse_msg_txt(filepath): """解析 msg.txt,格式: key: value(每行一个)""" result = {} if not filepath.is_file(): return result with open(filepath, 'r', encoding='utf-8') as f: for line in f: line = line.rstrip('\n').rstrip('\r') if not line: continue # 格式: key: value idx = line.find(': ') if idx > 0: key = line[:idx] value = line[idx+2:] result[key] = value else: # 可能只有 key 没有 value,或者格式不对,跳过 idx = line.find(':') if idx > 0: key = line[:idx] value = line[idx+1:] result[key] = value return result def merge_i18n(modules): """合并所有模块的 i18n,返回 {lang: {key: value}}""" merged = {lang: {} for lang in LANGS} for module_name in modules: i18n_dir = find_module_i18n_dir(module_name) if not i18n_dir: print(f" [跳过] {module_name}: 未找到i18n目录") continue for lang in LANGS: msg_file = i18n_dir / lang / 'msg.txt' if msg_file.is_file(): translations = parse_msg_txt(msg_file) count = len(translations) before = len(merged[lang]) merged[lang].update(translations) after = len(merged[lang]) new_keys = after - before if new_keys > 0 or count > 0: print(f" {module_name}/{lang}: {count}条, 新增{new_keys}个唯一key") else: print(f" [警告] {module_name}/{lang}: msg.txt 不存在") return merged def write_output(merged): """将合并结果写入 wwwroot/i18n/{lang}/i18n.json 和 msg.txt""" for lang in LANGS: lang_dir = I18N_OUTPUT_DIR / lang lang_dir.mkdir(parents=True, exist_ok=True) data = merged[lang] # 写入 i18n.json(前端读取此文件) json_file = lang_dir / 'i18n.json' with open(json_file, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent='\t') # 同时写入 msg.txt(人类可读格式) msg_file = lang_dir / 'msg.txt' with open(msg_file, 'w', encoding='utf-8') as f: for key, value in data.items(): f.write(f"{key}: {value}\n") print(f" 写入: {json_file} ({len(data)} 条)") print(f" 写入: {msg_file} ({len(data)} 条)") def main(): build_sh_path = SAGE_DIR / 'build.sh' if not build_sh_path.is_file(): print(f"错误: 找不到 {build_sh_path}") sys.exit(1) print(f"解析 build.sh: {build_sh_path}") modules = parse_build_sh(build_sh_path) print(f"发现 {len(modules)} 个模块:") for m in modules: has_i18n = (REPOS_DIR / m / 'i18n').is_dir() status = '✓' if has_i18n else '✗' print(f" {status} {m}") print(f"\n开始合并 i18n...") merged = merge_i18n(modules) print(f"\n合并结果:") for lang in LANGS: print(f" {lang}: {len(merged[lang])} 个唯一key") print(f"\n写入 wwwroot/i18n...") write_output(merged) print(f"\n完成!") if __name__ == '__main__': main()