国际化的一次实践

其实国际化这个东西没什么好说的,原理很简单,用一个函数根据当前的语言类型取出对应的文本就 ok 了。本文的出发点也不是讲国际化怎么用的问题,而是怎么快速生成对应的文本数据。

实际开发中我们知道,通常来说在服务器会保留多个语言文件。每个语言文件都格式,key 都完全一样,只是对应都值不一样。通常情况下我们会优先完成一种语言。然后再在该语言的基础上进行其他语言的翻译。

作为一个程序员,肯定是不能每一个翻译都自己填进去。所以我们的思路是:

  1. 通过基础语言包导出为 EXCEL(仅文字,相同文字去重)
  2. 有专业翻译人员对照 EXCEL 进行翻译为对应都其他语言
  3. 将翻译之后的 EXCEL 导入数据库,供将来使用(也可以跳过该步骤)
  4. 连接数据库读取数据(读取 EXCEL)并按照基础语言包格式生成对应的语言包
  5. 日志(哪些文本没有翻译对应的值)

我们之前就是将所有的文本都存放到数据库,然后根据自己到需要读取对应到值。因为我 node 用的熟一些,所有下面的例子用 node 实现,当然其他语言都是一样的。下面上代码:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
const fs = require("fs");
const path = require("path");
const mysql = require("mysql");

const target = "en";

// 列出本地语言文件列表
const ListFiles = function () {
const AppNames = ["project-1", "project-2", "project-3", "project-4"];
return fs
.readdirSync("../")
.filter((item) => {
if (AppNames.includes(item)) {
const _path = path.join("../", item);
return fs.statSync(_path).isDirectory() && item !== "i18n";
} else {
return false;
}
})
.map((item) => path.join("../", item, "/src"))
.map((item) => {
const subPath = fs.readdirSync(item);
if (subPath.includes("i18n")) {
return path.join(item, "/i18n");
} else {
return path.join(item, "/locales");
}
})
.map((item) => {
const zh_cn = fs
.readdirSync(path.join(item, "/zh_cn"))
.map((_item) => path.join(item, "/zh_cn/", _item));
return {
dir: item,
zh_cn: zh_cn,
};
});
};

// 列出服务器所有的语言
const ListLangs = function () {
return new Promise((reslove) => {
const db = mysql.createConnection({
host: "host",
user: "user",
password: "password",
database: "database",
});
db.connect();
var lang = new Map();
db.query("SELECT * from `table`", function (err, res) {
if (err) {
console.log(err.message);
process.exit(0);
}
res.forEach((item) => {
lang.set(item.chs, item.lao);
});
reslove(lang);
});
});
};

// 获取本地所有中文文本
const getAllZhCn = function (files) {
var txt = "";
var langs = new Set();
var files = ListFiles();
files.forEach((item) => {
item.zh_cn.forEach((_item) => {
var txt = fs.readFileSync(_item, "utf8");
var matched = txt.match(/'.*?'/g);
matched.forEach((item) => {
langs.add(item.substring(1, item.length - 1));
});
});
});
Array.from(langs).forEach((item) => {
txt = txt + item + "\n";
});
fs.writeFileSync("./zh_ch.txt", txt, "utf-8");
};

// 语言文本替换并生成对应的文件
const replaceLan = function (files, lang) {
var notMatch = "";
files.forEach((item) => {
item.zh_cn.forEach((_item) => {
var txt = fs.readFileSync(_item, "utf8");
var _path = _item.replace("zh_cn", target);
var targetDir = path.join(item.dir, "/", target);
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir);
txt = txt.replace(/'.*?'/g, function (matched) {
matched = matched.substring(1, matched.length - 1);
const lan = lang.get(matched);
if (!lan) {
const log = _item + " " + matched;
notMatch = notMatch + log + "\n";
}
return "'" + (lan || matched) + "'";
});
fs.writeFileSync(_path, txt, "utf-8");
});
});
if (notMatch) fs.writeFileSync("./log.log", notMatch, "utf-8");
console.log("替换完成!");
process.exit(0);
};

// 终端操作提示
process.stdout.write("请选择执行的操作 \n");
process.stdout.write("1. 列出所有中文 \n");
process.stdout.write("2. 生成对应语言文件 \n");

// 处理终端输入
process.stdin.on("data", (input) => {
input = input.toString().trim();
switch (input) {
case "1":
getAllZhCn();
break;
case "2":
ListLangs()
.then((lang) => {
var files = ListFiles();
replaceLan(files, lang);
})
.catch((err) => {
console.log(err.message);
process.exit(0);
});
break;
}
});

[越努力,越幸运!]