3 changed files with 170 additions and 0 deletions
@ -0,0 +1,121 @@
@@ -0,0 +1,121 @@
|
||||
/** |
||||
* 多国语言管理 |
||||
* |
||||
* 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) { |
||||
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/'; |
||||
} |
||||
|
||||
//从模板文件中解析语言占位变量,并生成语言包文件
|
||||
async init(lang) { |
||||
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文件
|
||||
build(lang) { |
||||
const _self = this; |
||||
|
||||
|
||||
} |
||||
|
||||
//判断语言代码格式是否符合国际标准
|
||||
isIosLangCode(lang) { |
||||
return /^[a-z]{2}(?:\-[a-z]{2})$/i.test(lang); |
||||
} |
||||
|
||||
//更新语言包文件内容,合并新的数据到已有内容中
|
||||
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]; |
||||
} |
||||
|
||||
updated = await writeFile(langFile, JSON.stringify(data, null, 4)); |
||||
}else { |
||||
updated = await writeFile(langFile, JSON.stringify(langJson, null, 4)); |
||||
} |
||||
} 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
export default I18N; |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
{ |
||||
"HeroUnion - Open source web crawler union.": "HeroUnion - Open source web crawler union.", |
||||
"HeroUnion.website": "HeroUnion.website", |
||||
"HeroUnion<small> - Open source web crawler union</small>": "HeroUnion<small> - Open source web crawler union</small>", |
||||
"HeroUnion Stats": "HeroUnion Stats", |
||||
"It's running": "It's running", |
||||
"Tasks Stats": "Tasks Stats", |
||||
"Last": "Last", |
||||
"Waiting": "Waiting", |
||||
"Running": "Running", |
||||
"Total": "Total", |
||||
"Done": "Done", |
||||
"Failed": "Failed", |
||||
"Notify Stats": "Notify Stats", |
||||
"Bot Stats": "Bot Stats", |
||||
"Idle": "Idle", |
||||
"Busy": "Busy", |
||||
"Offline": "Offline", |
||||
"JSON Data": "JSON Data", |
||||
"Covenant of the Alliance": "Covenant of the Alliance", |
||||
"Please abide by the following conventions and stick to it for a better tomorrow for yourself and the whole society!": "Please abide by the following conventions and stick to it for a better tomorrow for yourself and the whole society!", |
||||
"Comply with local/national laws and regulations": "Comply with local/national laws and regulations", |
||||
"Data that requires login or VIP status to access will not be crawled": "Data that requires login or VIP status to access will not be crawled", |
||||
"Data that is explicitly prohibited from being collected by the target website will not be crawled": "Data that is explicitly prohibited from being collected by the target website will not be crawled", |
||||
"The commercial core data of the target website is not crawled": "The commercial core data of the target website is not crawled", |
||||
"Low concurrency, low frequency, does not affect the normal operation of the target website": "Low concurrency, low frequency, does not affect the normal operation of the target website", |
||||
"Bots": "Bots", |
||||
"HeroUnion App": "HeroUnion App", |
||||
"HeroUnion download": "HeroUnion download", |
||||
"HeroBot download": "HeroBot download", |
||||
"HeroUnion<strong> is only responsible for the scheduling of crawlers and tasks</strong>.": "HeroUnion<strong> is only responsible for the scheduling of crawlers and tasks</strong>.", |
||||
"The contracts supported by crawlers and the specific content of tasks have nothing to do with the alliance.": "The contracts supported by crawlers and the specific content of tasks have nothing to do with the alliance." |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
/** |
||||
* i18n测试用例 |
||||
*/ |
||||
|
||||
import test from 'node:test'; |
||||
import assert from 'node:assert'; |
||||
import common from '../common.mjs'; |
||||
import I18N from '../i18n.mjs'; |
||||
|
||||
|
||||
test('Init test', async (t) => { |
||||
const i18n = new I18N(); |
||||
const res = await i18n.init(); |
||||
|
||||
assert.ok(res); |
||||
}); |
Loading…
Reference in new issue