feat(frontend): 控制台 next-intl 中英双语

接入无 URL 前缀的 zh-CN/en,顶栏与外观设置可切换语言;选择写入 cookie 后刷新生效。
This commit is contained in:
ryan
2026-08-16 16:49:16 +08:00
parent 64fbaa7ef1
commit 5a8722ff07
219 changed files with 21437 additions and 5212 deletions
+36
View File
@@ -0,0 +1,36 @@
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
function flatten(obj, prefix = '') {
const keys = new Set();
for (const [key, value] of Object.entries(obj)) {
const path = prefix ? `${prefix}.${key}` : key;
if (value && typeof value === 'object' && !Array.isArray(value)) {
for (const child of flatten(value, path)) keys.add(child);
} else {
keys.add(path);
}
}
return keys;
}
const zh = JSON.parse(
readFileSync(resolve(root, 'messages/zh-CN.json'), 'utf8'),
);
const en = JSON.parse(readFileSync(resolve(root, 'messages/en.json'), 'utf8'));
const zhKeys = flatten(zh);
const enKeys = flatten(en);
const onlyZh = [...zhKeys].filter((k) => !enKeys.has(k)).sort();
const onlyEnFixed = [...enKeys].filter((k) => !zhKeys.has(k)).sort();
if (onlyZh.length || onlyEnFixed.length) {
console.error('i18n key mismatch');
if (onlyZh.length) console.error('only in zh-CN:', onlyZh);
if (onlyEnFixed.length) console.error('only in en:', onlyEnFixed);
process.exit(1);
}
console.log(`i18n keys OK (${zhKeys.size} keys)`);
+43
View File
@@ -0,0 +1,43 @@
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const fragmentsDir = resolve(root, 'messages/fragments');
function deepMerge(target, source) {
for (const [key, value] of Object.entries(source)) {
if (
value &&
typeof value === 'object' &&
!Array.isArray(value) &&
target[key] &&
typeof target[key] === 'object' &&
!Array.isArray(target[key])
) {
deepMerge(target[key], value);
} else {
target[key] = value;
}
}
return target;
}
function mergeLocale(locale) {
const mainPath = resolve(root, `messages/${locale}.json`);
const data = JSON.parse(readFileSync(mainPath, 'utf8'));
const suffix = `.${locale}.json`;
for (const name of readdirSync(fragmentsDir).sort()) {
if (!name.endsWith(suffix)) continue;
const frag = JSON.parse(
readFileSync(resolve(fragmentsDir, name), 'utf8'),
);
deepMerge(data, frag);
console.log(`merged ${name}`);
}
writeFileSync(mainPath, `${JSON.stringify(data, null, 2)}\n`);
}
mergeLocale('zh-CN');
mergeLocale('en');
console.log('fragments merged');