更新翻译函数和转码插件
This commit is contained in:
parent
f5cac613bb
commit
2bfb0fea04
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
"a1:aaaaa": 1,
|
"a1:aaaaa": 1,
|
||||||
"no aaaaa": 2,
|
"no aaaaa": 2,
|
||||||
"bbbbb": 3,
|
"bbbbb": 3,
|
7
demo/apps/lib1/languages/idMap.js
Normal file
7
demo/apps/lib1/languages/idMap.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
"a":1,
|
||||||
|
"b":2,
|
||||||
|
"c{}{}":3,
|
||||||
|
"d{a}{b}":4,
|
||||||
|
"e":5
|
||||||
|
}
|
7
demo/apps/lib2/languages/idMap.js
Normal file
7
demo/apps/lib2/languages/idMap.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
"a":1,
|
||||||
|
"b":2,
|
||||||
|
"c{}{}":3,
|
||||||
|
"d{a}{b}":4,
|
||||||
|
"e":5
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
const babel = require("@babel/core");
|
const babel = require("@babel/core");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const i18nPlugin = require("./babel-plugin-voerkai18n");
|
const i18nPlugin = require("../src/babel-plugin-voerkai18n");
|
||||||
|
|
||||||
|
|
||||||
const code = fs.readFileSync(path.join(__dirname, "../demodata/index.js"), "utf-8");
|
const code = fs.readFileSync(path.join(__dirname, "./apps/app/index.js"), "utf-8");
|
||||||
babel.transform(code, {
|
babel.transform(code, {
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
7
demo/compile.demo.js
Normal file
7
demo/compile.demo.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
const compile = require('../src/compile');
|
||||||
|
const path = require("path")
|
||||||
|
|
||||||
|
|
||||||
|
compile(path.resolve(__dirname,"./apps/app/languages"))
|
@ -1,9 +1,9 @@
|
|||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const extract = require('./extract.plugin');
|
const extract = require('../src/extract.plugin');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
|
||||||
const soucePath = path.join(__dirname,'../demoapps/app')
|
const soucePath = path.join(__dirname,'./apps/app')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ gulp.src([
|
|||||||
]).pipe(extract({
|
]).pipe(extract({
|
||||||
debug:true,
|
debug:true,
|
||||||
// output: path.join(soucePath , 'languages'),
|
// output: path.join(soucePath , 'languages'),
|
||||||
languages: [{name:'en',title:"英文"},{name:'cn',title:"中文",default:true},{name:'de',title:"德语"},{name:'jp',title:"日本語"}],
|
languages: [{name:'en',title:"英文"},{name:'cn',title:"中文",default:true},{name:'de',title:"德语"},{name:'jp',title:"日語"}],
|
||||||
// extractor:{
|
// extractor:{
|
||||||
// default:[new RegExp()], // 默认匹配器,当文件类型没有对应的提取器时使用
|
// default:[new RegExp()], // 默认匹配器,当文件类型没有对应的提取器时使用
|
||||||
// "*" : [new RegExp()], // 所有类型均会执行的提取器
|
// "*" : [new RegExp()], // 所有类型均会执行的提取器
|
||||||
@ -26,4 +26,4 @@ gulp.src([
|
|||||||
"b":"b",
|
"b":"b",
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.pipe(gulp.dest(path.join(__dirname,'../demoapps/app/languages')));
|
.pipe(gulp.dest(path.join(__dirname,'./apps/app/languages')));
|
@ -17,13 +17,26 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { getMessageId } = require('./utils');
|
|
||||||
const TRANSLATE_FUNCTION_NAME = "t"
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { isPlainObject } = require("./utils");
|
||||||
|
|
||||||
const DefaultI18nPluginOptions = {
|
const DefaultI18nPluginOptions = {
|
||||||
translateFunctionName:"t", // 默认的翻译函数名称
|
translateFunctionName:"t", // 默认的翻译函数名称
|
||||||
location:"./languages" // 默认的翻译文件存放的目录,即编译后的语言文件的文件夹
|
// 翻译文件存放的目录,即编译后的语言文件的文件夹
|
||||||
|
// 默认从当前目录下的languages文件夹中导入
|
||||||
|
// 如果不存在则会
|
||||||
|
location:"./languages",
|
||||||
|
// 自动创建import {t} from "#/languages" 或 const { t } = require("#/languages")
|
||||||
|
// 如果此值是空,则不会自动创建import语句
|
||||||
|
autoImport:"#/languages",
|
||||||
|
// 自动导入时t函数时使用require或import,取值为 auto,require,import
|
||||||
|
// auto时会判断是否存在import语句,如果存在则使用import,否则使用require
|
||||||
|
// 也可以指定为require或import,主要用于测试时使用
|
||||||
|
moduleType:"auto",
|
||||||
|
// 存放翻译函数的id和翻译内容的映射关系,此参数用于测试使用
|
||||||
|
// 正常情况下会读取<location>/idMap.js文件
|
||||||
|
idMap:{}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,39 +79,81 @@ function hasRequireTranslateFunction(path){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getIdMap(options){
|
||||||
|
const { idMap,location } = options
|
||||||
|
if(isPlainObject(idMap) && Object.keys(idMap).length>0){
|
||||||
|
return idMap
|
||||||
|
}else{
|
||||||
|
let idMapFiles = [
|
||||||
|
path.join((path.isAbsolute(location) ? location : path.join(process.cwd(),location)),"idMap.js"),
|
||||||
|
path.join((path.isAbsolute(location) ? path.join(location,"languages") : path.join(process.cwd(),location,"languages")),'idMap.js')
|
||||||
|
]
|
||||||
|
let idMapFile
|
||||||
|
for( idMapFile of idMapFiles){
|
||||||
|
// 如果不存在idMap文件,则尝试从location/languages/中导入
|
||||||
|
if(fs.existsSync(idMapFile)){
|
||||||
|
try{
|
||||||
|
// 由于idMap.js可能是esm或cjs,并且babel插件不支持异步
|
||||||
|
// 当require(idMap.js)失败时,对esm模块尝试采用直接读取的方式
|
||||||
|
return require(idMapFile)
|
||||||
|
}catch(e){
|
||||||
|
// 出错原因可能是因为无效require esm模块,
|
||||||
|
// 由于idMap.js文件格式相对简单,因此尝试直接读取解析
|
||||||
|
try{
|
||||||
|
let idMapContent = fs.readFileSync(idMapFile).toString()
|
||||||
|
idMapContent = idMapContent.trim().replace(/^\s*export\s*default\s/g,"")
|
||||||
|
return JSON.parse(idMapContent)
|
||||||
|
}catch{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 所有尝试完成后触发错误
|
||||||
|
throw new Error(`${idMapFile}文件不存在,无法对翻译文本进行转换。\n原因可能是babel-plugin-voerkai18n插件的location参数未指向有效的语言包所在的目录。`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = function voerkai18nPlugin(babel) {
|
module.exports = function voerkai18nPlugin(babel) {
|
||||||
const t = babel.types;
|
const t = babel.types;
|
||||||
const pluginOptions = Object.assign({},DefaultI18nPluginOptions);
|
const pluginOptions = Object.assign({},DefaultI18nPluginOptions);
|
||||||
|
let idMap = {}
|
||||||
return {
|
return {
|
||||||
visitor:{
|
visitor:{
|
||||||
Program(path, state){
|
Program(path, state){
|
||||||
|
// 转码插件参数可以覆盖默认参数
|
||||||
Object.assign(pluginOptions,state.opts || {});
|
Object.assign(pluginOptions,state.opts || {});
|
||||||
const { location = "./languages", translateFunctionName } = pluginOptions
|
const { location ,autoImport, translateFunctionName,moduleType } = pluginOptions
|
||||||
if(isEsModule(path)){
|
idMap = getIdMap(pluginOptions)
|
||||||
// 如果没有定义t函数,则自动导入
|
// 是否自动导入t函数
|
||||||
if(!hasImportTranslateFunction(path)){
|
if(autoImport){
|
||||||
path.node.body.unshift(t.importDeclaration([
|
let module = moduleType === 'auto' ? isEsModule(path) ? 'esm' : 'cjs' : moduleType
|
||||||
t.ImportSpecifier(t.identifier(translateFunctionName),t.identifier(translateFunctionName)
|
if(!["esm","es","cjs","commonjs"].includes(module)) module = 'esm'
|
||||||
)],t.stringLiteral(location)))
|
if(module === 'esm'){
|
||||||
}
|
// 如果没有定义t函数,则自动导入
|
||||||
}else{
|
if(!hasImportTranslateFunction(path)){
|
||||||
if(!hasRequireTranslateFunction(path)){
|
path.node.body.unshift(t.importDeclaration([
|
||||||
path.node.body.unshift(t.variableDeclaration("const",[
|
t.ImportSpecifier(t.identifier(translateFunctionName),t.identifier(translateFunctionName)
|
||||||
t.variableDeclarator(
|
)],t.stringLiteral(autoImport)))
|
||||||
t.ObjectPattern([t.ObjectProperty(t.Identifier(translateFunctionName),t.Identifier(translateFunctionName),false,true)]),
|
}
|
||||||
t.CallExpression(t.Identifier("require"),[t.stringLiteral(location)])
|
}else{
|
||||||
)
|
if(!hasRequireTranslateFunction(path)){
|
||||||
]))
|
path.node.body.unshift(t.variableDeclaration("const",[
|
||||||
|
t.variableDeclarator(
|
||||||
|
t.ObjectPattern([t.ObjectProperty(t.Identifier(translateFunctionName),t.Identifier(translateFunctionName),false,true)]),
|
||||||
|
t.CallExpression(t.Identifier("require"),[t.stringLiteral(autoImport)])
|
||||||
|
)
|
||||||
|
]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 将t函数的第一个参数转换为id
|
||||||
CallExpression(path,state){
|
CallExpression(path,state){
|
||||||
let options = state.opts
|
if( path.node.callee.name === pluginOptions.translateFunctionName ){
|
||||||
// 只对翻译函数进行转码
|
|
||||||
if(path.node.callee.name===TRANSLATE_FUNCTION_NAME){
|
|
||||||
if(path.node.arguments.length>0 && t.isStringLiteral(path.node.arguments[0])){
|
if(path.node.arguments.length>0 && t.isStringLiteral(path.node.arguments[0])){
|
||||||
let text = path.node.arguments[0].value
|
let message = path.node.arguments[0].value
|
||||||
path.node.arguments[0] = t.stringLiteral(`*${text}*`)
|
const msgId =(message in idMap) ? idMap[message] : message
|
||||||
|
path.node.arguments[0] = t.stringLiteral(String(msgId))
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
path.skip();
|
path.skip();
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
const compile = require('./compile');
|
|
||||||
const path = require("path")
|
|
||||||
|
|
||||||
|
|
||||||
compile(path.resolve(__dirname,"../demoapps/app/languages"))
|
|
@ -6,32 +6,19 @@
|
|||||||
* 编译原理如下:
|
* 编译原理如下:
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* 编译输出:
|
* 编译后会在目标文件夹输出:
|
||||||
*
|
*
|
||||||
* hashId = getMessageId()
|
* - languages
|
||||||
*
|
* translates
|
||||||
* - languages/index.js 主源码,用来引用语言文件
|
* - en.json
|
||||||
* {
|
* - cn.json
|
||||||
* languages:{},
|
* - ...
|
||||||
|
* idMap.js // id映射列表
|
||||||
* }
|
* settings.js // 配置文件
|
||||||
* - languages/messageIds.json 翻译文本的id映射表
|
* cn.js // 中文语言包
|
||||||
* {
|
* en.js // 英文语言包
|
||||||
* [msg]:"<id>"
|
* [lang].js // 其他语言包
|
||||||
* }
|
* package.json // 包信息,用来指定包类型,以便在nodejs中能够正确加载
|
||||||
* - languages/en.js 英文语言文件
|
|
||||||
* {
|
|
||||||
* [region]:{
|
|
||||||
* [namespace]:{
|
|
||||||
* [hashId]:"<message>",
|
|
||||||
* },
|
|
||||||
* [namespace]:{...},
|
|
||||||
* },
|
|
||||||
* [region]:{...}
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* - languages/[lang].js 语言文件
|
|
||||||
* - formaters.js
|
|
||||||
*
|
*
|
||||||
* @param {*} opts
|
* @param {*} opts
|
||||||
*/
|
*/
|
||||||
@ -52,8 +39,8 @@ function normalizeCompileOptions(opts={}) {
|
|||||||
moduleType:"esm" // 指定编译后的语言文件的模块类型,取值common,cjs,esm,es
|
moduleType:"esm" // 指定编译后的语言文件的模块类型,取值common,cjs,esm,es
|
||||||
}, opts)
|
}, opts)
|
||||||
if(options.moduleType==="es") options.moduleType = "esm"
|
if(options.moduleType==="es") options.moduleType = "esm"
|
||||||
if(options.moduleType==="cjs") options.moduleType = "common"
|
if(options.moduleType==="cjs") options.moduleType = "commonjs"
|
||||||
if(["common","cjs","esm","es"].includes(options.moduleType)) options.moduleType = "esm"
|
if(["commonjs","cjs","esm","es"].includes(options.moduleType)) options.moduleType = "esm"
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,5 +109,17 @@ module.exports = function compile(langFolder,opts={}){
|
|||||||
const formattersContent = artTemplate(path.join(__dirname,"templates","formatters.js"), {languages,defaultLanguage,activeLanguage,namespaces,moduleType } )
|
const formattersContent = artTemplate(path.join(__dirname,"templates","formatters.js"), {languages,defaultLanguage,activeLanguage,namespaces,moduleType } )
|
||||||
fs.writeFileSync(formattersFile,formattersContent)
|
fs.writeFileSync(formattersFile,formattersContent)
|
||||||
}
|
}
|
||||||
|
// 7. 生成package.json
|
||||||
|
const packageJsonFile = path.join(langFolder,"package.json")
|
||||||
|
let packageJson = {}
|
||||||
|
if(moduleType==="esm"){
|
||||||
|
packageJson = {
|
||||||
|
type:"module",
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
packageJson = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.writeFileSync(packageJsonFile,JSON.stringify(packageJson,null,4))
|
||||||
})
|
})
|
||||||
}
|
}
|
24
src/currency.formatters.js
Normal file
24
src/currency.formatters.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 提供处理货币格式化的功能
|
||||||
|
*
|
||||||
|
* import './languages';
|
||||||
|
* import "voerka-i18n/formatters/currency"; // 货币格式化
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if(globalThis.VoerkaI18n){
|
||||||
|
|
||||||
|
VoerkaI18n.registerFormatters({
|
||||||
|
{
|
||||||
|
currency
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
28
src/datetime.formatters.js
Normal file
28
src/datetime.formatters.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 提供日期时间格式化的功能
|
||||||
|
*
|
||||||
|
* import './languages';
|
||||||
|
* import "voerka-i18n/formatters/datetime"; // 货币格式化
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if(globalThis.VoerkaI18n){
|
||||||
|
|
||||||
|
VoerkaI18n.registerFormatters({
|
||||||
|
"*":{
|
||||||
|
|
||||||
|
},
|
||||||
|
cn:{
|
||||||
|
|
||||||
|
},
|
||||||
|
en:{
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,38 +1,26 @@
|
|||||||
/**
|
/**
|
||||||
* 默认的格式化器
|
* 内置的格式化器
|
||||||
*
|
|
||||||
* 使用方法:
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* "My birthday is {date}":{
|
|
||||||
* "zh-CN":"我的生日是{date|time}"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* t("My birthday is {date}",new Date(1975,11,25))
|
|
||||||
*
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const dayjs = require("dayjs");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
"*":{
|
||||||
|
$types:{
|
||||||
|
Date:(value)=>value.toLocaleString()
|
||||||
|
},
|
||||||
|
time:(value)=> value.toLocaleTimeString(),
|
||||||
|
date: (value)=> value.toLocaleDateString(),
|
||||||
|
},
|
||||||
cn:{
|
cn:{
|
||||||
"*":{ // 适用于所有类型的格式化器
|
$types:{
|
||||||
default:null // 默认格式化器
|
Date:(value)=> `${value.getFullYear()}年${value.getMonth()+1}月${value.getDate()}日 ${value.getHours()}点${value.getMinutes()}分${value.getSeconds()}秒`
|
||||||
},
|
},
|
||||||
Date:{
|
time:(value)=>`${value.getHours()}点${value.getMinutes()}分${value.getSeconds()}秒`,
|
||||||
default:(value)=>dayjs(value).format("YYYY年MM年DD日"), // 默认的格式化器
|
date: (value)=> `${value.getFullYear()}年${value.getMonth()+1}月${value.getDate()}日`,
|
||||||
time:(value)=>dayjs(value).format("HH:mm:ss"),
|
currency:(value)=>`${value}元`,
|
||||||
date:(value)=>dayjs(value).format("YYYY/MM/DD")
|
|
||||||
},
|
|
||||||
Number:{
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
en:{
|
en:{
|
||||||
"Date":{
|
currency:(value)=>`$${value}`,
|
||||||
short:(value)=>dayjs(value).format("YYYY/MM/DD"),
|
|
||||||
time:(value)=>dayjs(value).format("HH:mm:ss")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
214
src/index.js
214
src/index.js
@ -1,6 +1,6 @@
|
|||||||
const deepMerge = require("deepmerge")
|
const deepMerge = require("deepmerge")
|
||||||
const formatters = require("./formatters")
|
const formatters = require("./formatters")
|
||||||
|
const {isPlainObject ,isNumber , getDataTypeName} = require("./utils")
|
||||||
|
|
||||||
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
|
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
|
||||||
// 不支持参数: let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*\s*)*)\s*\}/g
|
// 不支持参数: let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*\s*)*)\s*\}/g
|
||||||
@ -8,6 +8,8 @@ const formatters = require("./formatters")
|
|||||||
// 支持参数: { var | formatter(x,x,..) | formatter }
|
// 支持参数: { var | formatter(x,x,..) | formatter }
|
||||||
let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g
|
let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g
|
||||||
|
|
||||||
|
// 有效的语言名称列表
|
||||||
|
const languages = ["af","am","ar-dz","ar-iq","ar-kw","ar-ly","ar-ma","ar-sa","ar-tn","ar","az","be","bg","bi","bm","bn","bo","br","bs","ca","cs","cv","cy","da","de-at","de-ch","de","dv","el","en-au","en-ca","en-gb","en-ie","en-il","en-in","en-nz","en-sg","en-tt","en","eo","es-do","es-mx","es-pr","es-us","es","et","eu","fa","fi","fo","fr-ca","fr-ch","fr","fy","ga","gd","gl","gom-latn","gu","he","hi","hr","ht","hu","hy-am","id","is","it-ch","it","ja","jv","ka","kk","km","kn","ko","ku","ky","lb","lo","lt","lv","me","mi","mk","ml","mn","mr","ms-my","ms","mt","my","nb","ne","nl-be","nl","nn","oc-lnc","pa-in","pl","pt-br","pt","ro","ru","rw","sd","se","si","sk","sl","sq","sr-cyrl","sr","ss","sv-fi","sv","sw","ta","te","tet","tg","th","tk","tl-ph","tlh","tr","tzl","tzm-latn","tzm","ug-cn","uk","ur","uz-latn","uz","vi","x-pseudo","yo","zh-cn","zh-hk","zh-tw","zh"]
|
||||||
|
|
||||||
|
|
||||||
// 插值变量字符串替换正则
|
// 插值变量字符串替换正则
|
||||||
@ -29,24 +31,6 @@ let varReplaceRegexp =String.raw`\{\s*{varname}\s*\}`
|
|||||||
function hasInterpolation(str){
|
function hasInterpolation(str){
|
||||||
return str.includes("{") && str.includes("}")
|
return str.includes("{") && str.includes("}")
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 获取指定变量类型名称
|
|
||||||
* getDataTypeName(1) == Number
|
|
||||||
* getDataTypeName("") == String
|
|
||||||
* getDataTypeName(null) == Null
|
|
||||||
* getDataTypeName(undefined) == Undefined
|
|
||||||
* getDataTypeName(new Date()) == Date
|
|
||||||
* getDataTypeName(new Error()) == Error
|
|
||||||
*
|
|
||||||
* @param {*} v
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function getDataTypeName(v){
|
|
||||||
if (v === null) return 'Null'
|
|
||||||
if (v === undefined) return 'Undefined'
|
|
||||||
if(typeof(v)==="function") return "Function"
|
|
||||||
return v.constructor && v.constructor.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,15 +113,26 @@ function getInterpolatedVars(str){
|
|||||||
* @param {*} callback
|
* @param {*} callback
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function forEachInterpolatedVars(str,callback){
|
function forEachInterpolatedVars(str,callback,options={}){
|
||||||
let result=str, match
|
let result=str, match
|
||||||
|
let opts = Object.assign({
|
||||||
|
replaceAll:true, // 是否替换所有插值变量,当使用命名插值时应置为true,当使用位置插值时应置为false
|
||||||
|
},options)
|
||||||
varWithPipeRegexp.lastIndex=0
|
varWithPipeRegexp.lastIndex=0
|
||||||
while ((match = varWithPipeRegexp.exec(result)) !== null) {
|
while ((match = varWithPipeRegexp.exec(result)) !== null) {
|
||||||
const varname = match.groups.varname || ""
|
const varname = match.groups.varname || ""
|
||||||
// 解析格式化器和参数 = [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]]
|
// 解析格式化器和参数 = [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]]
|
||||||
const formatters = parseFormatters(match.groups.formatters)
|
const formatters = parseFormatters(match.groups.formatters)
|
||||||
if(typeof(callback)==="function"){
|
if(typeof(callback)==="function"){
|
||||||
result=result.replaceAll(match[0],callback(varname,formatters))
|
try{
|
||||||
|
if(opts.replaceAll){
|
||||||
|
result=result.replaceAll(match[0],callback(varname,formatters))
|
||||||
|
}else{
|
||||||
|
result=result.replace(match[0],callback(varname,formatters))
|
||||||
|
}
|
||||||
|
}catch{// callback函数可能会抛出异常,如果抛出异常,则中断匹配过程
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
varWithPipeRegexp.lastIndex=0
|
varWithPipeRegexp.lastIndex=0
|
||||||
}
|
}
|
||||||
@ -156,7 +151,7 @@ function transformToString(value){
|
|||||||
let result = value
|
let result = value
|
||||||
if(typeof(result)==="function") result = value()
|
if(typeof(result)==="function") result = value()
|
||||||
if(!(typeof(result)==="string")){
|
if(!(typeof(result)==="string")){
|
||||||
if(Array.isArray(result) || typeof(result)==="object"){
|
if(Array.isArray(result) || isPlainObject(result)){
|
||||||
result = JSON.stringify(result)
|
result = JSON.stringify(result)
|
||||||
}else{
|
}else{
|
||||||
result = result.toString()
|
result = result.toString()
|
||||||
@ -304,6 +299,28 @@ function buildFormatters(scope,activeLanguage,formatters){
|
|||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将value经过格式化器处理后返回
|
||||||
|
* @param {*} scope
|
||||||
|
* @param {*} activeLanguage
|
||||||
|
* @param {*} formatters
|
||||||
|
* @param {*} value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function getFormattedValue(scope,activeLanguage,formatters,value){
|
||||||
|
// 1. 取得格式化器函数列表
|
||||||
|
const formatterFuncs = buildFormatters(scope,activeLanguage,formatters)
|
||||||
|
// 3. 查找每种数据类型默认格式化器,并添加到formatters最前面,默认数据类型格式化器优先级最高
|
||||||
|
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value))
|
||||||
|
if(defaultFormatter){
|
||||||
|
formatterFuncs.splice(0,0,defaultFormatter)
|
||||||
|
}
|
||||||
|
// 3. 执行格式化器
|
||||||
|
value = executeFormatter(value,formatterFuncs)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串可以进行变量插值替换,
|
* 字符串可以进行变量插值替换,
|
||||||
* replaceInterpolatedVars("<模板字符串>",{变量名称:变量值,变量名称:变量值,...})
|
* replaceInterpolatedVars("<模板字符串>",{变量名称:变量值,变量名称:变量值,...})
|
||||||
@ -326,52 +343,33 @@ function replaceInterpolatedVars(template,...args) {
|
|||||||
// 当前激活语言
|
// 当前激活语言
|
||||||
const activeLanguage = scope.global.activeLanguage
|
const activeLanguage = scope.global.activeLanguage
|
||||||
let result=template
|
let result=template
|
||||||
if(!hasInterpolation(template)) return template
|
|
||||||
|
// 没有变量插值则的返回原字符串
|
||||||
|
if(args.length===0 || !hasInterpolation(template)) return template
|
||||||
|
|
||||||
// ****************************变量插值****************************
|
// ****************************变量插值****************************
|
||||||
if(args.length===1 && typeof(args[0]) === "object" && !Array.isArray(args[0])){
|
if(args.length===1 && isPlainObject(args[0])){
|
||||||
// 读取模板字符串中的插值变量列表
|
// 读取模板字符串中的插值变量列表
|
||||||
// [[var1,[formatter,formatter,...],match],[var2,[formatter,formatter,...],match],...}
|
// [[var1,[formatter,formatter,...],match],[var2,[formatter,formatter,...],match],...}
|
||||||
let varValues = args[0]
|
let varValues = args[0]
|
||||||
return forEachInterpolatedVars(template,(varname,formatters)=>{
|
return forEachInterpolatedVars(template,(varname,formatters)=>{
|
||||||
// 1. 取得格式化器函数列表
|
|
||||||
const formatterFuncs = buildFormatters(scope,activeLanguage,formatters)
|
|
||||||
// 2. 取变量值
|
|
||||||
let value = (varname in varValues) ? varValues[varname] : ''
|
let value = (varname in varValues) ? varValues[varname] : ''
|
||||||
// 3. 查找每种数据类型默认格式化器,并添加到formatters最前面,默认数据类型格式化器优先级最高
|
return getFormattedValue(scope,activeLanguage,formatters,value)
|
||||||
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value))
|
|
||||||
if(defaultFormatter){
|
|
||||||
formatterFuncs.splice(0,0,defaultFormatter)
|
|
||||||
}
|
|
||||||
// 4. 执行格式化器
|
|
||||||
value = executeFormatter(value,formatterFuncs)
|
|
||||||
return value
|
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
// ****************************位置插值****************************
|
// ****************************位置插值****************************
|
||||||
// 如果只有一个Array参数,则认为是位置变量列表,进行展开
|
// 如果只有一个Array参数,则认为是位置变量列表,进行展开
|
||||||
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args
|
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args
|
||||||
|
if(params.length===0) return template // 没有变量则不需要进行插值处理,返回原字符串
|
||||||
// 取得模板字符串中的插值变量列表 , 包含命令变量和位置变量
|
let i = 0
|
||||||
let interpVars = getInterpolatedVars(template,true)
|
return forEachInterpolatedVars(template,(varname,formatters)=>{
|
||||||
if(interpVars.length===0) return template // 没有变量插值则的返回原字符串
|
if(params.length>i){
|
||||||
|
return getFormattedValue(scope,activeLanguage,formatters,params[i++])
|
||||||
let i=0
|
|
||||||
for(let match of result.match(varWithPipeRegexp) || []){
|
|
||||||
if(i<params.length){
|
|
||||||
let value = params[i]
|
|
||||||
const formatterFuncs = buildFormatters(scope,activeLanguage,interpVars[i][1])
|
|
||||||
// 执行默认的数据类型格式化器
|
|
||||||
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value))
|
|
||||||
if(defaultFormatter){
|
|
||||||
formatterFuncs.splice(0,0,defaultFormatter)
|
|
||||||
}
|
|
||||||
value = executeFormatter(value,formatterFuncs)
|
|
||||||
result = result.replace(match,transformToString(value))
|
|
||||||
i+=1
|
|
||||||
}else{
|
}else{
|
||||||
break
|
throw new Error() // 抛出异常,停止插值处理
|
||||||
}
|
}
|
||||||
}
|
},{replaceAll:false})
|
||||||
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -390,16 +388,31 @@ const defaultLanguageSettings = {
|
|||||||
function isMessageId(content){
|
function isMessageId(content){
|
||||||
return parseInt(content)>0
|
return parseInt(content)>0
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 根据值的单数和复数形式,从messages中取得相应的消息
|
||||||
|
*
|
||||||
|
* @param {*} messages 复数形式的文本内容 = [<=0时的内容>,<=1时的内容>,<=2时的内容>,...]
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
function getPluraMessage(messages,value){
|
||||||
|
try{
|
||||||
|
if(Array.isArray(messages)){
|
||||||
|
return messages.length > value ? messages[value] : messages[messages.length-1]
|
||||||
|
}else{
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
return Array.isArray(messages) ? messages[0] : messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译函数
|
* 翻译函数
|
||||||
*
|
*
|
||||||
* translate("要翻译的文本内容") 如果默认语言是中文,则不会进行翻译直接返回
|
* translate("要翻译的文本内容") 如果默认语言是中文,则不会进行翻译直接返回
|
||||||
* translate("I am {} {}","man") == I am man 位置插值
|
* translate("I am {} {}","man") == I am man 位置插值
|
||||||
* translate("I am {p}",{p:"man"}) 字典插值
|
* translate("I am {p}",{p:"man"}) 字典插值
|
||||||
* translate("I am {p}",{p:"man",ns:""}) 指定名称空间
|
* translate("total {$count} items", {$count:1}) //复数形式
|
||||||
* translate("I am {p}",{p:"man",namespace:""})
|
|
||||||
* translate("I am {p}",{p:"man",namespace:""})
|
|
||||||
* translate("total {count} items", {count:1}) //复数形式
|
|
||||||
* translate("total {} {} {} items",a,b,c) // 位置变量插值
|
* translate("total {} {} {} items",a,b,c) // 位置变量插值
|
||||||
*
|
*
|
||||||
* this===scope 当前绑定的scope
|
* this===scope 当前绑定的scope
|
||||||
@ -407,14 +420,15 @@ function isMessageId(content){
|
|||||||
*/
|
*/
|
||||||
function translate(message) {
|
function translate(message) {
|
||||||
const scope = this
|
const scope = this
|
||||||
const activeLanguage = scope.settings.activeLanguage
|
const activeLanguage = scope.global.activeLanguage
|
||||||
let vars={} // 插值变量
|
let content = message
|
||||||
let pluralVars=[] // 复数变量
|
let vars=[] // 插值变量列表
|
||||||
|
let pluralVars= [] // 复数变量
|
||||||
|
let pluraValue = null // 复数值
|
||||||
try{
|
try{
|
||||||
// 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用
|
// 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用
|
||||||
if(arguments.length === 2 && typeof(arguments[1])=='object'){
|
if(arguments.length === 2 && isPlainObject(arguments[1])){
|
||||||
Object.assign(vars,arguments[1])
|
Object.entries(arguments[1]).forEach(([name,value])=>{
|
||||||
Object.entries(vars).forEach(([name,value])=>{
|
|
||||||
if(typeof(value)==="function"){
|
if(typeof(value)==="function"){
|
||||||
try{
|
try{
|
||||||
vars[name] = value()
|
vars[name] = value()
|
||||||
@ -422,41 +436,64 @@ function translate(message) {
|
|||||||
vars[name] = value
|
vars[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 复数变量
|
// 以$开头的视为复数变量
|
||||||
if(name.startsWith("$")) pluralVars.push(name)
|
if(name.startsWith("$")) pluralVars.push(name)
|
||||||
})
|
})
|
||||||
|
vars = [arguments[1]]
|
||||||
}else if(arguments.length >= 2){
|
}else if(arguments.length >= 2){
|
||||||
vars = [...arguments].splice(1).map(arg=>typeof(arg)==="function" ? arg() : arg)
|
vars = [...arguments].splice(1).map((arg,index)=>{
|
||||||
|
try{
|
||||||
|
return typeof(arg)==="function" ? arg() : arg
|
||||||
|
}catch(e){
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
// 位置参数中以第一个数值变量为复数变量
|
||||||
|
if(isNumber(arg)) pluraValue = parseInt(arg)
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认语言,不需要查询加载,只需要做插值变换即可
|
// 2. 取得翻译文本模板字符串
|
||||||
if(activeLanguage === scope.defaultLanguage){
|
if(activeLanguage === scope.defaultLanguage){
|
||||||
|
// 2.1 从默认语言中取得翻译文本模板字符串
|
||||||
|
// 如果当前语言就是默认语言,不需要查询加载,只需要做插值变换即可
|
||||||
// 当源文件运用了babel插件后会将原始文本内容转换为msgId
|
// 当源文件运用了babel插件后会将原始文本内容转换为msgId
|
||||||
if(isMessageId(message)){
|
// 如果是msgId则从scope.default中读取,scope.default=默认语言包={<id>:<message>}
|
||||||
message = scope.default[message] || message
|
if(isMessageId(content)){
|
||||||
|
content = scope.default[content] || message
|
||||||
}
|
}
|
||||||
return replaceInterpolatedVars.call(scope,message,vars)
|
|
||||||
}else{
|
}else{
|
||||||
// 1. 获取翻译后的文本内容
|
// 2.2 从当前语言包中取得翻译文本模板字符串
|
||||||
// 如果没有启用babel插件时,需要先将文本内容转换为msgId
|
// 如果没有启用babel插件将源文本转换为msgId,需要先将文本内容转换为msgId
|
||||||
let msgId = isMessageId(message) ? message : scope.idMap[message]
|
let msgId = isMessageId(content) ? content : scope.idMap[content]
|
||||||
message = scope.messages[msgId] || msgId
|
content = scope.messages[msgId] || content
|
||||||
|
}
|
||||||
// 处理复数
|
|
||||||
if(Array.isArray(message)){
|
|
||||||
|
|
||||||
}else{ // 普通
|
|
||||||
|
|
||||||
|
// 3. 处理复数
|
||||||
|
// 经过上面的处理,content可能是字符串或者数组
|
||||||
|
// content = "原始文本内容" || 复数形式["原始文本内容","原始文本内容"....]
|
||||||
|
// 如果是数组说明要启用复数机制,需要根据插值变量中的某个变量来判断复数形式
|
||||||
|
if(Array.isArray(content) && content.length>0){
|
||||||
|
// 如果存在复数命名变量,只取第一个复数变量
|
||||||
|
if(pluraValue){ // 启用的是位置插值,pluraIndex=第一个数字变量的位置
|
||||||
|
content = getPluraMessage(content,pluraValue)
|
||||||
|
}else if(pluralVar.length>0){
|
||||||
|
content = getPluraMessage(content,parseInt(vars(pluralVar[0])))
|
||||||
|
}else{ // 如果找不到复数变量,则使用第一个内容
|
||||||
|
content = content[0]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 进行插值处理
|
||||||
|
if(vars.length==0){
|
||||||
|
return content
|
||||||
|
}else{
|
||||||
|
return replaceInterpolatedVars.call(scope,content,...vars)
|
||||||
}
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
return message
|
return content // 出错则返回原始文本
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多语言管理类
|
* 多语言管理类
|
||||||
*
|
*
|
||||||
@ -587,13 +624,20 @@ function translate(message) {
|
|||||||
scope.global = this._settings
|
scope.global = this._settings
|
||||||
this._scopes.push(scope)
|
this._scopes.push(scope)
|
||||||
}
|
}
|
||||||
}
|
/**
|
||||||
|
* 注册全局格式化器
|
||||||
|
* @param {*} formatters
|
||||||
|
*/
|
||||||
|
registerFormatters(formatters){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports ={
|
module.exports ={
|
||||||
getInterpolatedVars,
|
getInterpolatedVars,
|
||||||
replaceInterpolatedVars,
|
replaceInterpolatedVars,
|
||||||
I18n,
|
I18n,
|
||||||
translate,
|
translate,
|
||||||
|
languages,
|
||||||
defaultLanguageSettings
|
defaultLanguageSettings
|
||||||
}
|
}
|
@ -2,19 +2,19 @@
|
|||||||
import messageIds from "./idMap.js"
|
import messageIds from "./idMap.js"
|
||||||
import { translate,i18n } from "voerka-i18n"
|
import { translate,i18n } from "voerka-i18n"
|
||||||
import defaultMessages from "./{{defaultLanguage}}.js"
|
import defaultMessages from "./{{defaultLanguage}}.js"
|
||||||
import i18nSettings from "./settings.js"
|
import scopeSettings from "./settings.js"
|
||||||
import formatters from "../formatters"
|
import formatters from "../formatters"
|
||||||
{{else}}
|
{{else}}
|
||||||
const messageIds = require("./idMap")
|
const messageIds = require("./idMap")
|
||||||
const { translate,i18n } = require("voerka-i18n")
|
const { translate,i18n } = require("voerka-i18n")
|
||||||
const defaultMessages = require("./{{defaultLanguage}}.js")
|
const defaultMessages = require("./{{defaultLanguage}}.js")
|
||||||
const i18nSettings = require("./settings.js")
|
const scopeSettings = require("./settings.js")
|
||||||
const formatters = require("../formatters")
|
const formatters = require("../formatters")
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
// 自动创建全局VoerkaI18n实例
|
// 自动创建全局VoerkaI18n实例
|
||||||
if(!globalThis.VoerkaI18n){
|
if(!globalThis.VoerkaI18n){
|
||||||
globalThis.VoerkaI18n = new i18n(i18nSettings)
|
globalThis.VoerkaI18n = new i18n(scopeSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = {
|
let scope = {
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
const formatters = {
|
const formatters = {
|
||||||
"*":{ // 在所有语言下生效的格式化器
|
"*":{ // 在所有语言下生效的格式化器
|
||||||
$types:{...} // 只作用于特定数据类型的默认格式化器
|
$types:{...}, // 只作用于特定数据类型的默认格式化器
|
||||||
|
.... // 全局格式化器
|
||||||
},
|
},
|
||||||
cn:{
|
cn:{
|
||||||
// 只作用于特定数据类型的格式化器
|
// 只作用于特定数据类型的格式化器
|
||||||
|
137
src/utils.js
137
src/utils.js
@ -1,25 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
// 用来提取字符里面的插值变量参数
|
|
||||||
// let varRegexp = /\{\s*(?<var>\w*\.?\w*)\s*\}/g
|
|
||||||
let varRegexp = /\{\s*((?<varname>\w+)?(\s*\|\s*(?<formatter>\w*))?)?\s*\}/g
|
|
||||||
// 插值变量字符串替换正则
|
|
||||||
//let varReplaceRegexp =String.raw`\{\s*(?<var>{name}\.?\w*)\s*\}`
|
|
||||||
|
|
||||||
|
|
||||||
let varReplaceRegexp =String.raw`\{\s*{varname}\s*\}`
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 考虑到通过正则表达式进行插件的替换可能较慢,因此提供一个简单方法来过滤掉那些
|
|
||||||
* 不需要进行插值处理的字符串
|
|
||||||
* 原理很简单,就是判断一下是否同时具有{、}字符
|
|
||||||
* 注意:该方法只能快速判断一个字符串不包括插值变量
|
|
||||||
* @param {*} str
|
|
||||||
* @returns {boolean} true=可能包含插值变量,
|
|
||||||
*/
|
|
||||||
function hasInterpolation(str){
|
|
||||||
return str.includes("{") && str.includes("}")
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* 获取指定变量类型名称
|
* 获取指定变量类型名称
|
||||||
* getDataTypeName(1) == Number
|
* getDataTypeName(1) == Number
|
||||||
@ -38,108 +17,46 @@ function getDataTypeName(v){
|
|||||||
if(typeof(v)==="function") return "Function"
|
if(typeof(v)==="function") return "Function"
|
||||||
return v.constructor && v.constructor.name;
|
return v.constructor && v.constructor.name;
|
||||||
};
|
};
|
||||||
|
function isPlainObject(obj){
|
||||||
|
if (typeof obj !== 'object' || obj === null) return false;
|
||||||
|
var proto = Object.getPrototypeOf(obj);
|
||||||
|
if (proto === null) return true;
|
||||||
|
var baseProto = proto;
|
||||||
|
|
||||||
/**
|
while (Object.getPrototypeOf(baseProto) !== null) {
|
||||||
* 生成一个基于时间戳的文本id
|
baseProto = Object.getPrototypeOf(baseProto);
|
||||||
* performance.now()返回的是当前node进程从启动到现在时间戳
|
|
||||||
* 但是连续调用可能会导致重复id的生成,如果发现重复则会
|
|
||||||
* 在生成的id后面加上一个随机数,以保证id的唯一性
|
|
||||||
*/
|
|
||||||
function getMessageId(){
|
|
||||||
this.lastMsgId = null
|
|
||||||
let id = String(performance.now()).replace(".","")
|
|
||||||
if(this.lastMsgId === id){
|
|
||||||
id = id + parseInt(Math.random() * 1000) .toString()
|
|
||||||
}else{
|
|
||||||
this.lastMsgId = id
|
|
||||||
}
|
}
|
||||||
return id
|
return proto === baseProto;
|
||||||
}
|
}
|
||||||
/**
|
function isNumber(value){
|
||||||
* 提取字符串中的插值变量
|
return !isNaN(parseInt(value))
|
||||||
* @param {*} str
|
|
||||||
* @returns {Array} 变量名称列表
|
|
||||||
*/
|
|
||||||
function getInterpolatedVars(str){
|
|
||||||
let result = []
|
|
||||||
let match
|
|
||||||
while ((match = varRegexp.exec(str)) !== null) {
|
|
||||||
if (match.index === varRegexp.lastIndex) {
|
|
||||||
varRegexp.lastIndex++;
|
|
||||||
}
|
|
||||||
if(match.groups.varname) {
|
|
||||||
result.push(match.groups.formatter ? match.groups.varname+"|"+match.groups.formatter : match.groups.varname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformVarValue(value){
|
|
||||||
let result = value
|
/**
|
||||||
if(typeof(result)==="function") result = value()
|
* 支持导入cjs和esm模块
|
||||||
if(!(typeof(result)==="string")){
|
* @param {*} url
|
||||||
if(Array.isArray(result) || typeof(result)==="object"){
|
*/
|
||||||
result = JSON.stringify(result)
|
async function importModule(url){
|
||||||
|
try{
|
||||||
|
return require(url)
|
||||||
|
}catch(e){
|
||||||
|
// 当加载出错时,尝试加载esm模块
|
||||||
|
if(e.code === "MODULE_NOT_FOUND"){
|
||||||
|
return await import(url)
|
||||||
}else{
|
}else{
|
||||||
result = result.toString()
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 字符串可以进行变量插值替换,
|
|
||||||
- 当只有两个参数并且第2个参数是{}时,将第2个参数视为命名变量的字典
|
|
||||||
replaceVars("this is {a}+{b},{a:1,b:2}) --> this is 1+2
|
|
||||||
- 当只有两个参数并且第2个参数是[]时,将第2个参数视为位置参数
|
|
||||||
replaceVars"this is {}+{}",[1,2]) --> this is 1+2
|
|
||||||
- 普通位置参数替换
|
|
||||||
replaceVars("this is {a}+{b}",1,2) --> this is 1+2
|
|
||||||
-
|
|
||||||
|
|
||||||
* @param {*} template
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function replaceInterpolateVars(template,...args) {
|
|
||||||
let result=template
|
|
||||||
if(!hasInterpolation(template)) return
|
|
||||||
if(args.length===1 && typeof(args[0]) === "object" && !Array.isArray(args[0])){ // 变量插值
|
|
||||||
for(let name in args[0]){
|
|
||||||
// 如果变量中包括|管道符,则需要进行转换以适配更宽松的写法,比如data|time能匹配"data |time","data | time"等
|
|
||||||
let nameRegexp = name.includes("|") ? name.split("|").join("\\s*\\|\\s*") : name
|
|
||||||
result=result.replaceAll(new RegExp(varReplaceRegexp.replaceAll("{varname}",nameRegexp),"g"),transformVarValue(args[0][name]))
|
|
||||||
}
|
|
||||||
}else{ // 位置插值
|
|
||||||
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args
|
|
||||||
let i=0
|
|
||||||
for(let match of result.match(varRegexp) || []){
|
|
||||||
if(i<params.length){
|
|
||||||
let param = transformVarValue(params[i])
|
|
||||||
result=result.replace(match,param)
|
|
||||||
i+=1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// const str = "I am {name}, I am {age} years old. you are {name},Now is {date},time={date | time}?"
|
|
||||||
|
|
||||||
// console.log("vars=",getInterpolatedVars(str).join())
|
|
||||||
|
|
||||||
// console.log(replaceInterpolateVars(str,{name:"tom",age:18,date:new Date(),"date|time":new Date().getTime()}))
|
|
||||||
// console.log(replaceInterpolateVars(str,"tom",18,"jack"))
|
|
||||||
// console.log(replaceInterpolateVars(str,["tom",18,"jack",1,2]))
|
|
||||||
// console.log(replaceInterpolateVars(str,"tom",18,()=>"bob"))
|
|
||||||
// console.log(replaceInterpolateVars(str,"tom",[1,2],{a:1},1,2))
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getMessageId,
|
getDataTypeName,
|
||||||
hasInterpolation,
|
isNumber,
|
||||||
getInterpolatedVars,
|
isPlainObject,
|
||||||
replaceInterpolateVars
|
importModule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
83
test/babel.test.js
Normal file
83
test/babel.test.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
const babel = require("@babel/core");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const i18nPlugin = require("../src/babel-plugin-voerkai18n");
|
||||||
|
|
||||||
|
const code = `
|
||||||
|
function test(a,b){
|
||||||
|
t("a")
|
||||||
|
t('b')
|
||||||
|
t('c{}{}',1,2)
|
||||||
|
t('d{a}{b}',{a:1,b:2}),
|
||||||
|
t('e',()=>{})
|
||||||
|
}`
|
||||||
|
|
||||||
|
|
||||||
|
function expectBabelSuccess(result){
|
||||||
|
expect(result.includes(`"#/languages"`)).toBeTruthy()
|
||||||
|
expect(result.includes(`t("1"`)).toBeTruthy()
|
||||||
|
expect(result.includes(`t("2"`)).toBeTruthy()
|
||||||
|
expect(result.includes(`t("3"`)).toBeTruthy()
|
||||||
|
expect(result.includes(`t("4"`)).toBeTruthy()
|
||||||
|
expect(result.includes(`t("5"`)).toBeTruthy()
|
||||||
|
}
|
||||||
|
test("翻译函数转换",done=>{
|
||||||
|
babel.transform(code, {
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
i18nPlugin,
|
||||||
|
{
|
||||||
|
// location:"",
|
||||||
|
// 指定语言文件存放的目录,即保存编译后的语言文件的文件夹
|
||||||
|
// 可以指定相对路径,也可以指定绝对路径
|
||||||
|
autoImport:"#/languages",
|
||||||
|
moduleType:"esm",
|
||||||
|
// 此参数仅仅用于单元测试时使用,正常情况下,会读取location文件夹下的idMap", idMap:{
|
||||||
|
"a":1,
|
||||||
|
"b":2,
|
||||||
|
"c{}{}":3,
|
||||||
|
"d{a}{b}":4,
|
||||||
|
"e":5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}, function(err, result) {
|
||||||
|
expectBabelSuccess(result.code)
|
||||||
|
done()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
test("读取esm格式的idMap后进行翻译函数转换",done=>{
|
||||||
|
babel.transform(code, {
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
i18nPlugin,
|
||||||
|
{
|
||||||
|
location:path.join(__dirname, "../demo/apps/lib1/languages"),
|
||||||
|
autoImport:"#/languages",
|
||||||
|
moduleType:"esm",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}, function(err, result) {
|
||||||
|
expectBabelSuccess(result.code)
|
||||||
|
done()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
test("读取commonjs格式的idMap后进行翻译函数转换",done=>{
|
||||||
|
babel.transform(code, {
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
i18nPlugin,
|
||||||
|
{
|
||||||
|
location:path.join(__dirname, "../demo/apps/lib2/languages"),
|
||||||
|
autoImport:"#/languages",
|
||||||
|
moduleType:"esm",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}, function(err, result) {
|
||||||
|
expectBabelSuccess(result.code)
|
||||||
|
done()
|
||||||
|
});
|
||||||
|
})
|
@ -1,122 +0,0 @@
|
|||||||
const dayjs = require('dayjs');
|
|
||||||
const { getInterpolatedVars, replaceInterpolatedVars} = require('../src/index.js')
|
|
||||||
|
|
||||||
const scope1 ={
|
|
||||||
defaultLanguage: "cn", // 默认语言名称
|
|
||||||
default: { // 默认语言包
|
|
||||||
|
|
||||||
},
|
|
||||||
messages : { // 当前语言包
|
|
||||||
|
|
||||||
},
|
|
||||||
idMap:{ // 消息id映射列表
|
|
||||||
|
|
||||||
},
|
|
||||||
formatters:{ // 当前作用域的格式化函数列表
|
|
||||||
"*":{
|
|
||||||
$types:{
|
|
||||||
Date:(v)=>dayjs(v).format('YYYY/MM/DD'),
|
|
||||||
Boolean:(v)=>v?"True":"False",
|
|
||||||
},
|
|
||||||
sum:(v,n=1)=>v+n,
|
|
||||||
double:(v)=>v*2,
|
|
||||||
upper:(v)=>v.toUpperCase(),
|
|
||||||
lower:(v)=>v.toLowerCase()
|
|
||||||
},
|
|
||||||
cn:{
|
|
||||||
$types:{
|
|
||||||
Date:(v)=>dayjs(v).format('YYYY年MM月DD日'),
|
|
||||||
Boolean:(v)=>v?"是":"否",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
en:{
|
|
||||||
$types:{
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
loaders:{}, // 异步加载语言文件的函数列表
|
|
||||||
global:{// 引用全局VoerkaI18n配置
|
|
||||||
defaultLanguage: "cn",
|
|
||||||
activeLanguage: "cn",
|
|
||||||
languages:[
|
|
||||||
{name:"cn",title:"中文",default:true},
|
|
||||||
{name:"en",title:"英文"},
|
|
||||||
{name:"de",title:"德语"},
|
|
||||||
{name:"jp",title:"日语"}
|
|
||||||
],
|
|
||||||
formatters:{ // 当前作用域的格式化函数列表
|
|
||||||
"*":{
|
|
||||||
$types:{
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cn:{
|
|
||||||
$types:{
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
en:{
|
|
||||||
$types:{
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const replaceVars = replaceInterpolatedVars.bind(scope1)
|
|
||||||
|
|
||||||
|
|
||||||
test("获取翻译内容中的插值变量",done=>{
|
|
||||||
const results = getInterpolatedVars("中华人民共和国成立于{date | year | time }年,首都是{city}市");
|
|
||||||
expect(results.map(r=>r[0]).join(",")).toBe("date,city");
|
|
||||||
expect(results[0][0]).toEqual("date");
|
|
||||||
expect(results[0][1]).toEqual(["year","time"]);
|
|
||||||
expect(results[1][0]).toEqual("city");
|
|
||||||
expect(results[1][1]).toEqual([]);
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("获取翻译内容中定义了重复的插值变量",done=>{
|
|
||||||
const results = getInterpolatedVars("{a}{a}{a|x}{a|x}{a|x|y}{a|x|y}");
|
|
||||||
expect(results.length).toEqual(3);
|
|
||||||
expect(results[0][0]).toEqual("a");
|
|
||||||
expect(results[0][1]).toEqual([]);
|
|
||||||
expect(results[1][0]).toEqual("a");
|
|
||||||
expect(results[1][1]).toEqual(["x"]);
|
|
||||||
expect(results[2][0]).toEqual("a");
|
|
||||||
expect(results[2][1]).toEqual(["x","y"]);
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("替换翻译内容的位置插值变量",done=>{
|
|
||||||
|
|
||||||
expect(replaceVars("{}{}{}",1,2,3)).toBe("123");
|
|
||||||
expect(replaceVars("{a}{b}{c}",1,2,3)).toBe("123");
|
|
||||||
// 定义了一些无效的格式化器,直接忽略
|
|
||||||
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3)).toBe("123");
|
|
||||||
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3,4,5,6)).toBe("123");
|
|
||||||
expect(replaceVars("{ a|}{b|dd}{c|}{}",1,2,3)).toBe("123{}");
|
|
||||||
// 数据值进行
|
|
||||||
expect(replaceVars("{}{}{}",1,"2",true)).toBe("12true");
|
|
||||||
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("替换翻译内容的命名插值变量",done=>{
|
|
||||||
expect(replaceVars("{a}{b}{c}",{a:11,b:22,c:33})).toBe("112233");
|
|
||||||
expect(replaceVars("{a}{b}{c}{a}{b}{c}",{a:1,b:"2",c:3})).toBe("123123");
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("命名插值变量使用格式化器",done=>{
|
|
||||||
// 提供无效的格式化器,直接忽略
|
|
||||||
expect(replaceVars("{a|x}{b|x|y}{c|}",{a:1,b:2,c:3})).toBe("123");
|
|
||||||
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
|
|
||||||
// 默认的字符串格式化器,不需要定义使用字符串方法
|
|
||||||
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
|
|
||||||
expect(replaceVars("{a|padStart(10)}",{a:"123"})).toBe(" 123");
|
|
||||||
expect(replaceVars("{a|padStart(10)|trim}",{a:"123"})).toBe("123");
|
|
||||||
done()
|
|
||||||
})
|
|
@ -1,18 +1,225 @@
|
|||||||
|
const dayjs = require('dayjs');
|
||||||
|
const { getInterpolatedVars, replaceInterpolatedVars , translate} = require('../src/index.js')
|
||||||
|
|
||||||
import { translate } from "../src/index.js"
|
const messages = {
|
||||||
|
cn:{
|
||||||
|
1:"你好",
|
||||||
|
2:"现在是{}",
|
||||||
|
3:"我出生于{year}年,今年{age}岁",
|
||||||
|
},
|
||||||
|
en :{
|
||||||
|
1:"hello",
|
||||||
|
2:"Now is {}",
|
||||||
|
3:"I was born in {year}, now is {age} years old",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const idMap = {
|
||||||
|
"你好":1,
|
||||||
|
"现在是{}":2,
|
||||||
|
"我出生于{year}年,今年{age}岁":3
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
test("默认语言翻译",done=>{
|
|
||||||
|
|
||||||
|
let scope1 ={
|
||||||
|
defaultLanguage: "cn", // 默认语言名称
|
||||||
|
default: messages.cn,
|
||||||
|
messages :messages.cn,
|
||||||
|
idMap,
|
||||||
|
formatters:{ // 当前作用域的格式化函数列表
|
||||||
|
"*":{
|
||||||
|
$types:{
|
||||||
|
Date:(v)=>dayjs(v).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
Boolean:(v)=>v?"True":"False",
|
||||||
|
},
|
||||||
|
sum:(v,n=1)=>v+n,
|
||||||
|
double:(v)=>v*2,
|
||||||
|
upper:(v)=>v.toUpperCase(),
|
||||||
|
lower:(v)=>v.toLowerCase()
|
||||||
|
},
|
||||||
|
cn:{
|
||||||
|
$types:{
|
||||||
|
Date:(v)=>dayjs(v).format('YYYY年MM月DD日 HH点mm分ss秒'),
|
||||||
|
Boolean:(v)=>v?"是":"否",
|
||||||
|
},
|
||||||
|
book:(v)=>`《${v}》`,
|
||||||
|
},
|
||||||
|
en:{
|
||||||
|
$types:{
|
||||||
|
|
||||||
|
},
|
||||||
|
book:(v)=>`<${v}>`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loaders:{}, // 异步加载语言文件的函数列表
|
||||||
|
global:{// 引用全局VoerkaI18n配置
|
||||||
|
defaultLanguage: "cn",
|
||||||
|
activeLanguage: "cn",
|
||||||
|
languages:[
|
||||||
|
{name:"cn",title:"中文",default:true},
|
||||||
|
{name:"en",title:"英文"},
|
||||||
|
{name:"de",title:"德语"},
|
||||||
|
{name:"jp",title:"日语"}
|
||||||
|
],
|
||||||
|
formatters:{ // 当前作用域的格式化函数列表
|
||||||
|
"*":{
|
||||||
|
$types:{
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cn:{
|
||||||
|
$types:{
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
en:{
|
||||||
|
$types:{
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceVars = replaceInterpolatedVars.bind(scope1)
|
||||||
|
const t = translate.bind(scope1)
|
||||||
|
|
||||||
|
|
||||||
|
function changeLanguage(language){
|
||||||
|
scope1.global.activeLanguage = language
|
||||||
|
scope1.messages = messages[language]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scope1.global.activeLanguage = "cn" // 切换到中文
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("获取翻译内容中的插值变量",done=>{
|
||||||
|
const results = getInterpolatedVars("中华人民共和国成立于{date | year | time }年,首都是{city}市");
|
||||||
|
expect(results.map(r=>r[0]).join(",")).toBe("date,city");
|
||||||
|
expect(results[0][0]).toEqual("date");
|
||||||
|
expect(results[0][1]).toEqual(["year","time"]);
|
||||||
|
expect(results[1][0]).toEqual("city");
|
||||||
|
expect(results[1][1]).toEqual([]);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("获取翻译内容中定义了重复的插值变量",done=>{
|
||||||
|
const results = getInterpolatedVars("{a}{a}{a|x}{a|x}{a|x|y}{a|x|y}");
|
||||||
|
expect(results.length).toEqual(3);
|
||||||
|
expect(results[0][0]).toEqual("a");
|
||||||
|
expect(results[0][1]).toEqual([]);
|
||||||
|
expect(results[1][0]).toEqual("a");
|
||||||
|
expect(results[1][1]).toEqual(["x"]);
|
||||||
|
expect(results[2][0]).toEqual("a");
|
||||||
|
expect(results[2][1]).toEqual(["x","y"]);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("替换翻译内容的位置插值变量",done=>{
|
||||||
|
expect(replaceVars("{}{}{}",1,2,3)).toBe("123");
|
||||||
|
expect(replaceVars("{a}{b}{c}",1,2,3)).toBe("123");
|
||||||
|
// 定义了一些无效的格式化器,直接忽略
|
||||||
|
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3)).toBe("123");
|
||||||
|
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3,4,5,6)).toBe("123");
|
||||||
|
expect(replaceVars("{ a|}{b|dd}{c|}{}",1,2,3)).toBe("123{}");
|
||||||
|
// 中文状态下true和false被转换成中文的"是"和"否"
|
||||||
|
expect(replaceVars("{}{}{}",1,"2",true)).toBe("12是");
|
||||||
|
expect(replaceVars("{|double}{}{}",1,"2",true)).toBe("22是");
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("替换翻译内容的命名插值变量",done=>{
|
||||||
|
expect(replaceVars("{a}{b}{c}",{a:11,b:22,c:33})).toBe("112233");
|
||||||
|
expect(replaceVars("{a}{b}{c}{a}{b}{c}",{a:1,b:"2",c:3})).toBe("123123");
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("命名插值变量使用格式化器",done=>{
|
||||||
|
// 提供无效的格式化器,直接忽略
|
||||||
|
expect(replaceVars("{a|x}{b|x|y}{c|}",{a:1,b:2,c:3})).toBe("123");
|
||||||
|
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
|
||||||
|
// 默认的字符串格式化器,不需要定义使用字符串方法
|
||||||
|
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
|
||||||
|
// padStart格式化器是字符串的方法,不需要额外定义可以直接使用
|
||||||
|
expect(replaceVars("{a|padStart(10)}",{a:"123"})).toBe(" 123");
|
||||||
|
expect(replaceVars("{a|padStart(10)|trim}",{a:"123"})).toBe("123");
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("命名插值变量使用格式化器",done=>{
|
||||||
|
// 提供无效的格式化器,直接忽略
|
||||||
|
expect(replaceVars("{a|x}{b|x|y}{c|}",{a:1,b:2,c:3})).toBe("123");
|
||||||
|
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
|
||||||
|
// 默认的字符串格式化器,不需要定义使用字符串方法
|
||||||
|
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
|
||||||
|
expect(replaceVars("{a|padStart(10)}",{a:"123"})).toBe(" 123");
|
||||||
|
expect(replaceVars("{a|padStart(10)|trim}",{a:"123"})).toBe("123");
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test("切换到其他语言时的自动匹配同名格式化器",done=>{
|
||||||
|
// 默认的字符串类型的格式化器
|
||||||
|
expect(replaceVars("{a}",{a:true})).toBe("是");
|
||||||
|
expect(replaceVars("{name|book}是毛泽东思想的重要载体","毛泽东选集")).toBe("《毛泽东选集》是毛泽东思想的重要载体");
|
||||||
|
changeLanguage("en")
|
||||||
|
expect(replaceVars("{a}",{a:false})).toBe("False");
|
||||||
|
expect(replaceVars("{name|book}是毛泽东思想的重要载体","毛泽东选集")).toBe("<毛泽东选集>是毛泽东思想的重要载体");
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
test("启用位置插值变量翻译",done=>{
|
test("位置插值翻译文本内容",done=>{
|
||||||
|
const now = new Date()
|
||||||
|
expect(t("你好")).toBe("你好");
|
||||||
|
expect(t("现在是{}",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
|
||||||
|
|
||||||
|
// 经babel自动码换后,文本内容会根据idMap自动转为id
|
||||||
|
expect(t("1")).toBe("你好");
|
||||||
|
expect(t("2",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
|
||||||
|
|
||||||
|
changeLanguage("en")
|
||||||
|
expect(t("你好")).toBe("hello");
|
||||||
|
expect(t("现在是{}",now)).toBe(`Now is ${dayjs(now).format('YYYY-MM-DD HH:mm:ss')}`);
|
||||||
|
expect(t("1")).toBe("hello");
|
||||||
|
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY-MM-DD HH:mm:ss')}`);
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("命名插值翻译文本内容",done=>{
|
||||||
|
const now = new Date()
|
||||||
|
expect(t("你好")).toBe("你好");
|
||||||
|
expect(t("现在是{}",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
|
||||||
|
|
||||||
|
|
||||||
|
changeLanguage("en")
|
||||||
|
expect(t("你好")).toBe("hello");
|
||||||
|
expect(t("现在是{}",now)).toBe(`Now is ${dayjs(now).format('YYYY-MM-DD HH:mm:ss')}`);
|
||||||
|
expect(t("1")).toBe("hello");
|
||||||
|
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY-MM-DD HH:mm:ss')}`);
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("当没有对应的语言翻译时",done=>{
|
||||||
test("启用字典插值变量翻译",done=>{
|
expect(t("我是中国人")).toBe("我是中国人");
|
||||||
|
changeLanguage("en")
|
||||||
|
expect(t("我是中国人")).toBe("我是中国人");
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("切换到未知语言",done=>{
|
||||||
|
expect(t("我是中国人")).toBe("我是中国人");
|
||||||
|
changeLanguage("en")
|
||||||
|
expect(t("我是中国人")).toBe("我是中国人");
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user