#!/usr/bin/env node // gen_l10n_dart.mjs — l10n 单源 codegen // // 真源:design/i18n/strings.json(getters + lists + methods × 6 语)。 // 生成:client/lib/l10n/strings_{zh,en,ja,ko,ru,es}.dart(concrete AppText 实现)。 // 手写保留:client/lib/l10n/app_text.dart(enum AppLang / abstract AppText 声明 / // AppLangLabel / AppLangMisc 逻辑)。Dart 编译器强制 concrete⊆abstract 兜底漂移。 // // 用法:node design/codegen/gen_l10n_dart.mjs // 输出为确定性终态格式,不跑 dart format(其结果依赖包解析状态、跨环境不稳, // 会让漂移闸误报)。漂移闸 = 纯 node 重生成 + git diff,无 dart 依赖。 // analyze 不校验格式,故手控格式即可;若单独启用 `dart format --set-exit-if-changed`, // 需让本文件输出与之对齐(当前未启用)。 import fs from 'node:fs'; import path from 'node:path'; import url from 'node:url'; const ROOT = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..', '..'); const SRC = path.join(ROOT, 'design/i18n/strings.json'); const OUT_DIR = path.join(ROOT, 'client/lib/l10n'); const LANGS = ['zh', 'en', 'ja', 'ko', 'ru', 'es']; const CLASS = { zh: 'StringsZh', en: 'StringsEn', ja: 'StringsJa', ko: 'StringsKo', ru: 'StringsRu', es: 'StringsEs' }; const HEADER_NOTE = { zh: '中文', en: 'English', ja: '日本語', ko: '한국어', ru: 'Русский', es: 'Español' }; // 字面量转义(getter / list 元素):无插值,$ 一律转义为字面量。 function litStr(s) { return s.replace(/\\/g, '\\\\').replace(/\$/g, '\\$').replace(/'/g, "\\'").replace(/\n/g, '\\n'); } // 模板转义(method):保留 $var 插值,仅转义反斜杠/引号/换行。 function tplStr(s) { return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n'); } const data = JSON.parse(fs.readFileSync(SRC, 'utf8')); const { getters, lists, methods } = data; function genFile(lang) { const L = []; L.push(`// strings_${lang}.dart — ${HEADER_NOTE[lang]}文案(自动生成 · 勿手改)`); L.push('//'); L.push('// GENERATED by design/codegen/gen_l10n_dart.mjs from design/i18n/strings.json.'); L.push('// 改文案:改 strings.json 后重跑 codegen;新增 key 需同步 app_text.dart 抽象声明。'); L.push(`import 'app_text.dart';`); L.push(''); L.push(`class ${CLASS[lang]} extends AppText {`); L.push(` const ${CLASS[lang]}();`); L.push(''); L.push(' @override'); L.push(` AppLang get lang => AppLang.${lang};`); L.push(''); for (const [k, m] of Object.entries(getters)) { L.push(' @override'); L.push(` String get ${k} => '${litStr(m[lang])}';`); } for (const [k, m] of Object.entries(lists)) { const items = m[lang].map((x) => `'${litStr(x)}'`).join(', '); L.push(' @override'); L.push(` List get ${k} => const [${items}];`); } for (const [k, m] of Object.entries(methods)) { L.push(' @override'); L.push(` String ${k}(${m.args}) => '${tplStr(m.tpl[lang])}';`); } L.push('}'); L.push(''); return L.join('\n'); } const written = []; for (const lang of LANGS) { const out = path.join(OUT_DIR, `strings_${lang}.dart`); fs.writeFileSync(out, genFile(lang)); written.push(out); } const g = Object.keys(getters).length, l = Object.keys(lists).length, me = Object.keys(methods).length; console.log(`✓ 生成 ${written.length} 份:getters=${g} lists=${l} methods=${me} × ${LANGS.length} 语`);