234 lines
9.5 KiB
JavaScript
234 lines
9.5 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 将extract插件扫描的文件编译为语言文件
|
||
*
|
||
* 编译后的语言文件用于运行环境使用
|
||
*
|
||
* 编译原理如下:
|
||
*
|
||
*
|
||
* 编译后会在目标文件夹输出:
|
||
*
|
||
* - languages
|
||
* translates
|
||
* - en.json
|
||
* - zh.json
|
||
* - ...
|
||
* idMap.js // id映射列表
|
||
* settings.json // 配置文件
|
||
* zh.js // 中文语言包
|
||
* en.js // 英文语言包
|
||
* [lang].js // 其他语言包
|
||
*
|
||
* @param {*} opts
|
||
*/
|
||
const { Command } = require('commander');
|
||
const glob = require("glob")
|
||
const createLogger = require("logsets")
|
||
const path = require("path")
|
||
const { i18nScope,t } = require("./i18nProxy")
|
||
const fs = require("fs-extra")
|
||
const logger = createLogger()
|
||
const artTemplate = require("art-template")
|
||
const semver = require("semver")
|
||
const {
|
||
findModuleType,
|
||
getProjectSourceFolder,
|
||
getCurrentPackageJson,
|
||
getInstalledPackageInfo,
|
||
isTypeScriptProject,
|
||
getPackageReleaseInfo,
|
||
upgradePackage
|
||
} = require("@voerkai18n/utils")
|
||
|
||
|
||
function normalizeCompileOptions(opts={}) {
|
||
let options = Object.assign({
|
||
moduleType:"auto", // 指定编译后的语言文件的模块类型,取值common,cjs,esm,es
|
||
isTypeScript:false
|
||
}, opts)
|
||
options.moduleType = options.moduleType.trim()
|
||
if(options.moduleType==="es") options.moduleType = "esm"
|
||
if(options.moduleType==="cjs") options.moduleType = "commonjs"
|
||
if(!["auto","commonjs","cjs","esm","es"].includes(options.moduleType)) options.moduleType = "esm"
|
||
return options;
|
||
}
|
||
|
||
function generateFormatterFile(langName,{isTypeScript,formattersFolder,templateContext,moduleType}={}){
|
||
const formattersFile = path.join(formattersFolder,`${langName}.${isTypeScript ? 'ts' : 'js'}`)
|
||
if(!fs.existsSync(formattersFile)){
|
||
const formattersContent = artTemplate(path.join(__dirname,"templates",`formatters.${isTypeScript ? 'ts' : 'js'}`), templateContext )
|
||
fs.writeFileSync(formattersFile,formattersContent)
|
||
logger.log(t(" - 格式化器:{}"),path.basename(formattersFile))
|
||
}else{ // 格式化器如果存在,则需要更改对应的模块类型
|
||
let formattersContent = fs.readFileSync(formattersFile,"utf8").toString()
|
||
if(moduleType == "esm" || isTypeScript){
|
||
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\s*\=/gm,"export default ")
|
||
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\./gm,"export ")
|
||
}else{
|
||
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*default\s*/gm,"module.exports = ")
|
||
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*/gm,"module.exports.")
|
||
}
|
||
fs.writeFileSync(formattersFile,formattersContent)
|
||
logger.log(t(" - 更新格式化器:{}"),path.basename(formattersFile))
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 将@voerkai18n/runtime更新到最新版本
|
||
*/
|
||
async function updateRuntime(){
|
||
const task = logger.task(t("更新@voerkai18n/runtime运行时"))
|
||
try{
|
||
const packageName = "@voerkai18n/runtime"
|
||
const curVersion = getInstalledPackageInfo(packageName).version
|
||
const latestVersion = (await getPackageReleaseInfo(packageName)).latestVersion
|
||
if(semver.gt(latestVersion, curVersion)){
|
||
await upgradePackage(packageName)
|
||
task.complete(t("Updated:{}",[latestVersion]))
|
||
return
|
||
}
|
||
task.complete(t("已经是最新的"))
|
||
}catch(e){
|
||
logger.log(t("更新@voerkai18n/runtime失败,请手动更新!"))
|
||
task.error(e.message)
|
||
}
|
||
}
|
||
|
||
async function compile(langFolder,opts={}){
|
||
const options = normalizeCompileOptions(opts);
|
||
let { moduleType,isTypeScript,updateRuntime:isUpdateRuntime } = options;
|
||
|
||
if(isUpdateRuntime){
|
||
await updateRuntime()
|
||
}
|
||
|
||
// 如果自动则会从当前项目读取,如果没有指定则会是esm
|
||
if(moduleType==="auto"){
|
||
moduleType = findModuleType(langFolder)
|
||
}
|
||
const projectPackageJson = getCurrentPackageJson(langFolder)
|
||
// 加载多语言配置文件
|
||
const settingsFile = path.join(langFolder,"settings.json")
|
||
|
||
try{
|
||
|
||
// 读取多语言配置文件
|
||
const langSettings = fs.readJSONSync(settingsFile)
|
||
let { languages,defaultLanguage,activeLanguage,namespaces } = langSettings
|
||
|
||
logger.log(t("支持的语言\t: {}"),languages.map(item=>`${item.title}(${item.name})`).join(","))
|
||
logger.log(t("默认语言\t: {}"),defaultLanguage)
|
||
logger.log(t("激活语言\t: {}"),activeLanguage)
|
||
logger.log(t("名称空间\t: {}"),Object.keys(namespaces).join(","))
|
||
logger.log(t("模块类型\t: {}"),moduleType)
|
||
logger.log(t("TypeScript\t: {}"),isTypeScript)
|
||
logger.log("")
|
||
logger.log(t("编译结果输出至:{}"),langFolder)
|
||
|
||
// 1. 合并生成最终的语言文件
|
||
let messages = {} ,msgId =1
|
||
glob.sync(path.join(langFolder,"translates/*.json")).forEach(file=>{
|
||
try{
|
||
let msg = fs.readJSONSync(file)
|
||
Object.entries(msg).forEach(([msg,langs])=>{
|
||
if(msg in messages){
|
||
Object.assign(messages[msg],langs)
|
||
}else{
|
||
messages[msg] = langs
|
||
}
|
||
})
|
||
}catch(e){
|
||
logger.log(t("读取语言文件{}失败:{}"),file,e.message)
|
||
}
|
||
})
|
||
logger.log(t(" - 共合成{}条文本"),Object.keys(messages).length)
|
||
|
||
// 2. 为每一个文本内容生成一个唯一的id
|
||
let messageIds = {}
|
||
Object.entries(messages).forEach(([msg,langs])=>{
|
||
langs.$id = msgId++
|
||
messageIds[msg] = langs.$id
|
||
})
|
||
// 3. 为每一个语言生成对应的语言文件
|
||
languages.forEach(lang=>{
|
||
let langMessages = {}
|
||
Object.entries(messages).forEach(([message,translatedMsgs])=>{
|
||
langMessages[translatedMsgs.$id] = lang.name in translatedMsgs ? translatedMsgs[lang.name] : message
|
||
})
|
||
const langFile = path.join(langFolder,`${lang.name}.${isTypeScript ? 'ts' : 'js'}`)
|
||
// 为每一种语言生成一个语言文件
|
||
if(moduleType==="esm" || isTypeScript){
|
||
fs.writeFileSync(langFile,`export default ${JSON.stringify(langMessages,null,4)}`)
|
||
}else{
|
||
fs.writeFileSync(langFile,`module.exports = ${JSON.stringify(langMessages,null,4)}`)
|
||
}
|
||
logger.log(t(" - 语言包文件: {}"),path.basename(langFile))
|
||
})
|
||
|
||
// 4. 生成id映射文件
|
||
const idMapFile = path.join(langFolder,`idMap.${isTypeScript ? 'ts' : 'js'}`)
|
||
if(moduleType==="esm" || isTypeScript){
|
||
fs.writeFileSync(idMapFile,`export default ${JSON.stringify(messageIds,null,4)}`)
|
||
}else{
|
||
fs.writeFileSync(idMapFile,`module.exports = ${JSON.stringify(messageIds,null,4)}`)
|
||
}
|
||
logger.log(t(" - idMap文件: {}"),path.basename(idMapFile))
|
||
|
||
const templateContext = {
|
||
scopeId:projectPackageJson.name,
|
||
languages,
|
||
defaultLanguage,
|
||
activeLanguage,
|
||
namespaces,
|
||
moduleType,
|
||
isTypeScript,
|
||
JSON,
|
||
settings:JSON.stringify(langSettings,null,4)
|
||
}
|
||
// 5 . 生成编译后的格式化函数文件
|
||
const formattersFolder = path.join(langFolder,"formatters")
|
||
if(!fs.existsSync(formattersFolder)) fs.mkdirSync(formattersFolder)
|
||
// 为每一个语言生成一个对应的式化器
|
||
languages.forEach(lang=>{
|
||
generateFormatterFile(lang.name,{isTypeScript,formattersFolder,templateContext,moduleType})
|
||
})
|
||
|
||
// 6. 生成编译后的访问入口文件
|
||
const entryFile = path.join(langFolder,`index.${isTypeScript ? 'ts' : 'js'}`)
|
||
const entryContent = artTemplate(path.join(__dirname,"templates",`entry.${isTypeScript ? 'ts' : 'js'}`), templateContext )
|
||
fs.writeFileSync(entryFile,entryContent)
|
||
logger.log(t(" - 访问入口文件: {}"),path.basename(entryFile))
|
||
|
||
}catch(e){
|
||
logger.log(t("加载多语言配置文件<{}>失败: {} "),settingsFile,e.stack)
|
||
}
|
||
}
|
||
|
||
const program = new Command();
|
||
|
||
program
|
||
.description(t('编译指定项目的语言包'))
|
||
.option('-D, --debug', t('输出调试信息'))
|
||
.option('-t, --typescript',t("输出typescript代码"))
|
||
.option('-u, --update-runtime',t("自动更新runtime"))
|
||
.option('-m, --moduleType [types]', t('输出模块类型,取值auto,esm,cjs'), 'auto')
|
||
.argument('[location]', t('工程项目所在目录'),"./")
|
||
.hook("preAction",async function(location){
|
||
const lang= process.env.LANGUAGE || "zh"
|
||
await i18nScope.change(lang)
|
||
})
|
||
.action(async (location,options) => {
|
||
location = getProjectSourceFolder(location)
|
||
options.isTypeScript = options.typescript==undefined ? isTypeScriptProject() : options.typescript
|
||
const langFolder = path.join(location,"languages")
|
||
if(!fs.existsSync(langFolder)){
|
||
logger.error(t("语言包文件夹<{}>不存在",langFolder))
|
||
return
|
||
}
|
||
compile(langFolder,options)
|
||
});
|
||
|
||
program.parseAsync(process.argv);
|
||
|