# node转化多语言
# 将翻译输出成 json
执行 node merge-translations.js
会将翻译合并输出至 translations.json 中
// merge-translations.js
import fs from 'fs';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
// 获取当前文件的目录名
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 输出文件路径
const outputFile = path.resolve(__dirname, 'translations.json');
// 递归提取所有键值对,支持嵌套
function flattenObject(obj, prefix = '', result = {}) {
for (const key in obj) {
const value = obj[key];
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
flattenObject(value, fullKey, result);
} else {
result[fullKey] = value;
}
}
return result;
}
// 动态导入语言文件并提取内容
async function extractLangObject(filePath) {
try {
// 转换为 file:// URL
const fileUrl = pathToFileURL(filePath).href;
const module = await import(fileUrl);
const langObj = module.default || module;
return flattenObject(langObj);
} catch (e) {
console.error(`Failed to import ${filePath}:`, e.message);
return {};
}
}
// 合并翻译文件
async function mergeTranslations() {
const mainLangFiles = {
en: path.resolve(__dirname, './src/lang/language/en.js'),
ja: path.resolve(__dirname, './src/lang/language/ja.js'),
zh_CN: path.resolve(__dirname, './src/lang/language/zh_CN.js'),
zh_TW: path.resolve(__dirname, './src/lang/language/zh_TW.js'),
};
// 提取所有语言的翻译
const langObjects = {};
for (const lang in mainLangFiles) {
langObjects[lang] = await extractLangObject(mainLangFiles[lang]);
console.log(`Extracted ${lang} language file`);
}
// 获取所有唯一键
const allKeys = new Set();
for (const lang in langObjects) {
Object.keys(langObjects[lang]).forEach((key) => allKeys.add(key));
}
// 构建翻译条目数组
const translationEntries = [];
for (const key of allKeys) {
const entry = { key };
for (const lang in langObjects) {
entry[lang] = langObjects[lang][key] || '';
}
translationEntries.push(entry);
}
// 按键名排序以保持一致性
translationEntries.sort((a, b) => a.key.localeCompare(b.key));
// 写入文件
fs.writeFileSync(outputFile, JSON.stringify(translationEntries, null, 2), 'utf8');
console.log(`Merged translations into ${outputFile}`);
console.log(`Processed ${translationEntries.length} translation entries`);
}
// 执行合并
mergeTranslations();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 批量同步新修改的翻译
translationsChange.json 中为新修改的翻译,执行 node sync-translations.js 后会将 translationsChange.json 更改同步至 translations.json 中,然后执行 node write-translations.js 后更新多语言配置
// sync-translations.js
import fs from 'fs';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
// 获取当前文件的目录名
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 输出文件路径
const outputFile = path.resolve(__dirname, 'translations.json');
// 递归提取所有键值对,支持嵌套
function flattenObject(obj, prefix = '', result = {}) {
for (const key in obj) {
const value = obj[key];
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
flattenObject(value, fullKey, result);
} else {
result[fullKey] = value;
}
}
return result;
}
// 动态导入语言文件并提取内容
async function extractLangObject(filePath) {
try {
// 转换为 file:// URL
const fileUrl = pathToFileURL(filePath).href;
const module = await import(fileUrl);
const langObj = module.default || module;
return flattenObject(langObj);
} catch (e) {
console.error(`Failed to import ${filePath}:`, e.message);
return {};
}
}
// 合并翻译文件
async function mergeTranslations() {
const mainLangFiles = {
en: path.resolve(__dirname, './src/lang/language/en.js'),
ja: path.resolve(__dirname, './src/lang/language/ja.js'),
zh_CN: path.resolve(__dirname, './src/lang/language/zh_CN.js'),
zh_TW: path.resolve(__dirname, './src/lang/language/zh_TW.js'),
};
// 提取所有语言的翻译
const langObjects = {};
for (const lang in mainLangFiles) {
langObjects[lang] = await extractLangObject(mainLangFiles[lang]);
console.log(`Extracted ${lang} language file`);
}
// 获取所有唯一键
const allKeys = new Set();
for (const lang in langObjects) {
Object.keys(langObjects[lang]).forEach((key) => allKeys.add(key));
}
// 构建翻译条目数组
const translationEntries = [];
for (const key of allKeys) {
const entry = { key };
for (const lang in langObjects) {
entry[lang] = langObjects[lang][key] || '';
}
translationEntries.push(entry);
}
// 按键名排序以保持一致性
translationEntries.sort((a, b) => a.key.localeCompare(b.key));
// 写入文件
fs.writeFileSync(outputFile, JSON.stringify(translationEntries, null, 2), 'utf8');
console.log(`Merged translations into ${outputFile}`);
console.log(`Processed ${translationEntries.length} translation entries`);
}
// 执行合并
mergeTranslations();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 将 json 反向输出回多语言配置
执行 node write-translations.js (单语言会合并输出到一个文件中)
// write-translations
import fs from 'fs';
import path from 'path';
// 还原嵌套对象
function unflattenObject(flatObj) {
const result = {};
for (const flatKey in flatObj) {
const keys = flatKey.split('.');
let cur = result;
keys.forEach((k, idx) => {
if (idx === keys.length - 1) {
cur[k] = flatObj[flatKey];
} else {
cur[k] = cur[k] || {};
cur = cur[k];
}
});
}
return result;
}
// 读取 translations.json
const translations = JSON.parse(fs.readFileSync(path.resolve('./translations.json'), 'utf8'));
// 语言列表
const langs = ['en', 'ja', 'zh_CN', 'zh_TW'];
// 为每种语言生成对象
const langObjects = {};
langs.forEach((lang) => {
const flatObj = {};
translations.forEach((item) => {
if (item[lang] !== undefined) flatObj[item.key] = item[lang];
});
langObjects[lang] = unflattenObject(flatObj);
});
// 输出到各语言 JS 文件
langs.forEach((lang) => {
const filePath = path.resolve(`./src/lang/language/${lang}.js`);
const content = `export default ${JSON.stringify(langObjects[lang], null, 2)};\n`;
fs.writeFileSync(filePath, content, 'utf8');
console.log(`Synced translations to ${filePath}`);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 最佳实践
新增的没确认的翻译写在 translationsChange.json 里然后通过 node sync-translations.js write-translations.js 批量同步到多语言配置中
后续只要把 translationsChange.json 给翻译人员确认后,确认后的 translationsChange.json 再执行 node sync-translations.js write-translations.js 批量同步到多语言配置中,确认并同步过后就可以清空 translationsChange.json 了
也就是说日常只需要维护 translationsChange.json 文件就可以了,确保 translationsChange.json 中的都是没确认或需要修改的翻译