Union of hero bots.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

226 lines
6.0 KiB

/**
* 多国语言管理
*
* node i18n.mjs init [默认语言代号]
* node i18n.mjs build [语言代号]
*/
import fs from 'node:fs';
import { readdir, readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import common from './common.mjs';
class I18N {
//构造函数,设置默认配置
constructor(defaultLang, templateDir, langDir, buildDir) {
this.defaultLang = typeof(defaultLang) != 'undefined' && defaultLang ? defaultLang : 'en';
this.templateDir = typeof(templateDir) != 'undefined' && templateDir ? templateDir : './public/template/';
this.langDir = typeof(langDir) != 'undefined' && langDir ? langDir : './i18n/';
this.buildDir = typeof(buildDir) != 'undefined' && buildDir ? buildDir : './public/';
}
//从模板文件中解析语言占位变量,并生成语言包文件
async init() {
const _self = this;
try {
const files = await readdir(_self.templateDir);
let parseLangRes = null;
for (const file of files) {
parseLangRes = await _self.parseLangFromTemplate(_self.templateDir + file);
if (parseLangRes) {
console.log('Template file [%s] parse lang config done', file);
}
}
} catch (err) {
console.error('Read dir in function init failed', err);
return false;
}
return true;
}
//根据语言包文件以及模板文件,生成对应语言的html文件
async build(lang) {
const _self = this;
const langFiles = await _self.getLangFiles(lang);
console.log('Lang json files', langFiles);
if (langFiles && langFiles.length > 0) {
try {
let langData = null;
let newHtml = '', saved = false;
for (const langFile of langFiles) {
const langJson = await readFile(_self.langDir + langFile, { encoding: 'utf8' });
if (!langJson) {
continue;
}
langData = JSON.parse(langJson);
const files = await readdir(_self.templateDir);
for (const file of files) {
newHtml = await _self.replaceLangToTemplate(_self.templateDir + file, langData);
if (newHtml) {
saved = await _self.saveBuildHtml(langFile.replace('.json', ''), file, newHtml);
console.log('Template file [%s] lang data replace with [%s] done, html build [%s].', file, langFile, (saved ? 'success' : 'failed'));
}
}
}
} catch (err) {
console.error('Read dir in function build failed', err);
return false;
}
}
return true;
}
//判断语言代码格式是否符合国际标准
isIosLangCode(lang) {
return /^[a-z]{2}(\-[a-z]{2})?$/i.test(lang);
}
//判断是否是语言包文件
isIosLangFile(filename) {
return /^[a-z]{2}(\-[a-z]{2})?\.json$/i.test(filename);
}
//更新语言包文件内容,合并新的数据到已有内容中
async updateLangFile(langFile, langJson) {
const _self = this;
let updated = false;
try {
let json = await readFile(langFile, { encoding: 'utf8'});
if (json) {
let data = JSON.parse(json);
for (const key in langJson) {
data[key] = langJson[key];
}
await writeFile(langFile, JSON.stringify(data, null, 4));
}else {
await writeFile(langFile, JSON.stringify(langJson, null, 4));
}
updated = true;
} catch (err) {
console.error('updateLangFile failed', err);
}
return updated;
}
//解析单个模板文件,并生成语言包文件
async parseLangFromTemplate(templateFilepath) {
const _self = this;
let langJson = {},
total = 0;
try {
const html_template = await readFile(templateFilepath, { encoding: 'utf8' });
const regHtmlLang = /[\s\S]*<html lang="([^"]+)">[\s\S]*/i;
let htmlLang = html_template.replace(regHtmlLang, "$1");
if (htmlLang == html_template || _self.isIosLangCode(htmlLang) == false) {
htmlLang = _self.defaultLang;
}else {
htmlLang = htmlLang.toLowerCase();
}
//模版语法不能包含:换行符、英文冒号、英文分号
const regLang = /\{([^\}\r\n:;]+)\}/ig;
const matches = html_template.matchAll(regLang);
for (const match of matches) {
langJson[match[1]] = match[1];
total ++;
}
//更新语言包文件
if (total > 0) {
let langFile = _self.langDir + `${htmlLang}.json`;
const saved = await _self.updateLangFile(langFile, langJson);
if (!saved) {
return false;
}
}
} catch (err) {
console.error('parseLangFromTemplate failed', err);
return false;
}
return langJson;
}
//获取所有语言包json文件
async getLangFiles(lang) {
const _self = this;
let langFiles = [];
try {
const files = await readdir(_self.langDir);
let parseLangRes = null;
for (const file of files) {
if (_self.isIosLangFile(file)) {
if (typeof(lang) == 'undefined') {
langFiles.push(file);
}else if (file.indexOf(lang) > -1) {
langFiles.push(file);
}
}
}
} catch (err) {
console.error('Read dir in function getLangFiles failed', err);
return false;
}
return langFiles;
}
//根据语言包替换单个模板文件,返回新的html代码
async replaceLangToTemplate(templateFilepath, langData) {
const _self = this;
let html = '';
try {
const html_template = await readFile(templateFilepath, { encoding: 'utf8' });
html = html_template;
for (let key in langData) {
html = html.replaceAll(`{${key}}`, langData[key]);
}
} catch (err) {
console.error('replaceLangToTemplate failed', err);
return false;
}
return html;
}
//保存替换了语言包的html文件
async saveBuildHtml(lang, htmlFilename, htmlContent) {
const _self = this;
let saved = false;
try {
let langDir = _self.buildDir + lang;
if (!fs.existsSync(langDir)) {
fs.mkdirSync(langDir);
}
let htmlFile = `${langDir}/${htmlFilename}`;
await writeFile(htmlFile, htmlContent);
saved = true;
} catch (err) {
console.error('saveBuildHtml failed', err);
}
return saved;
}
}
export default I18N;