update tests
This commit is contained in:
parent
1d6c37ea79
commit
1cf4f313b3
@ -18,21 +18,24 @@ function mergeFormattersConfigs(configSources:any[]){
|
||||
return finalConfig
|
||||
},{})
|
||||
}
|
||||
|
||||
const zhMessages:VoerkaI18nLanguageMessages = {
|
||||
$config:{
|
||||
add:{a:1},
|
||||
dec:{b:1}
|
||||
},
|
||||
"1": "你好",
|
||||
"2": "你好,{name}",
|
||||
"3": "中国",
|
||||
"4": ["我没有车","我有一部车","我有两部车","我有{}部车"] ,
|
||||
"你好": "你好",
|
||||
"我叫{name},今年{age}岁": "我叫{name},今年{age}岁",
|
||||
"中国": "中国",
|
||||
"我有{}部车": ["我没有车","我有一部车","我有两部车","我有{}部车"] ,
|
||||
"我的工资是每月{}元":"我的工资是每月{}元"
|
||||
}
|
||||
const enMessages={
|
||||
"1": "hello",
|
||||
"2": "hello,{name}",
|
||||
"3": "china",
|
||||
"4": ["I don't have car","I have a car","I have two cars","I have {} cars"]
|
||||
"你好": "hello",
|
||||
"我叫{name},今年{age}岁": "My name is {name},Now {age} years old year",
|
||||
"中国": "china",
|
||||
"我有{}部车": ["I don't have car","I have a car","I have two cars","I have {} cars"],
|
||||
"我的工资是每月{}元":"My salary is {} yuan per month"
|
||||
}
|
||||
|
||||
const messages = {
|
||||
@ -42,7 +45,7 @@ const messages = {
|
||||
|
||||
const idMap={
|
||||
"你好":1,
|
||||
"你好,{name}":2,
|
||||
"你好,{name}":2,
|
||||
"中国":3,
|
||||
"我有{}部车":4
|
||||
}
|
||||
@ -67,16 +70,16 @@ const formatters ={
|
||||
x:{g:1},
|
||||
y:{g:1},
|
||||
g:{g1:1,g2:2}
|
||||
},
|
||||
add:(value:any,args?:any[],$config?:VoerkaI18nFormatterConfigs)=>'*'+ value+1,
|
||||
}
|
||||
},
|
||||
zh:{
|
||||
$config:{},
|
||||
prefix:(value:any,args:any[],config?:VoerkaI18nFormatterConfigs)=>config?.chars+value,
|
||||
first:(value:any)=>'ZH'+value[0],
|
||||
...zhFormatters
|
||||
},
|
||||
en:{
|
||||
$config:{},
|
||||
first:(value:any)=>'EN'+value[0],
|
||||
...enFormatters,
|
||||
},
|
||||
jp:()=>{}
|
||||
}
|
||||
@ -88,8 +91,7 @@ beforeAll(async ()=>{
|
||||
return new Promise((resolve)=>{
|
||||
scope = new VoerkaI18nScope({
|
||||
id: "test",
|
||||
languages,
|
||||
idMap,
|
||||
languages,
|
||||
messages,
|
||||
formatters,
|
||||
callback:()=>{
|
||||
@ -238,23 +240,79 @@ describe('翻译函数', () => {
|
||||
beforeAll(() => {
|
||||
t = scope.t
|
||||
})
|
||||
|
||||
test('基本翻译',async () => {
|
||||
expect(t("你好")).toBe("你好")
|
||||
expect(t("你好,{name}","张三")).toBe("你好,张三")
|
||||
expect(t("中国")).toBe("中国")
|
||||
expect(t("我有{}部车",0)).toBe("我没有车")
|
||||
expect(t("我有{}部车",1)).toBe("我有一部车")
|
||||
expect(t("我有{}部车",1)).toBe("我有两部车")
|
||||
expect(t("我有{}部车",3)).toBe("我有3部车")
|
||||
expect(t("我叫{name},今年{age}岁","张三",12)).toBe("我叫张三,今年12岁")
|
||||
expect(t("我叫{name},今年{age}岁",["张三",12])).toBe("我叫张三,今年12岁")
|
||||
expect(t("我叫{name},今年{age}岁",{name:"张三",age:12})).toBe("我叫张三,今年12岁")
|
||||
await scope.change("en")
|
||||
expect(t("你好")).toBe("hello")
|
||||
expect(t("你好,{name}","张三")).toBe("hello,张三")
|
||||
expect(t("我叫{name},今年{age}岁","tom",12)).toBe("My name is tom,Now 12 years old year")
|
||||
expect(t("我叫{name},今年{age}岁",["tom",12])).toBe("My name is tom,Now 12 years old year")
|
||||
expect(t("我叫{name},今年{age}岁",{name:"tom",age:12})).toBe("My name is tom,Now 12 years old year")
|
||||
expect(t("中国")).toBe("china")
|
||||
expect(t("我有{}部车",0)).toBe("I have 0 cars")
|
||||
expect(t("我有{}部车",1)).toBe("I have 1 cars")
|
||||
expect(t("我有{}部车",2)).toBe("I have many cars")
|
||||
})
|
||||
|
||||
test('基本复数翻译',async () => {
|
||||
expect(t("我有{}部车",0)).toBe("我没有车")
|
||||
expect(t("我有{}部车",1)).toBe("我有一部车")
|
||||
expect(t("我有{}部车",2)).toBe("我有两部车")
|
||||
expect(t("我有{}部车",3)).toBe("我有3部车")
|
||||
expect(t("我有{}部车",100)).toBe("我有100部车")
|
||||
await scope.change("en")
|
||||
expect(t("我有{}部车",0)).toBe("I don't have car")
|
||||
expect(t("我有{}部车",1)).toBe("I have a car")
|
||||
expect(t("我有{}部车",2)).toBe("I have two cars")
|
||||
expect(t("我有{}部车",3)).toBe("I have 3 cars")
|
||||
expect(t("我有{}部车",100)).toBe("I have 100 cars")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('插值变量格式化器', () => {
|
||||
let t:VoerkaI18nTranslate
|
||||
|
||||
|
||||
beforeAll(() => {
|
||||
t = scope.t
|
||||
// 注册格式化器,注册为所有语言
|
||||
scope.registerFormatter("add", (value,args,config) => {
|
||||
return String(Number(value) + (Number(args.length==0 ? 1 : args[0])))
|
||||
});
|
||||
scope.formatters.updateConfig("zh",{
|
||||
bookname:{
|
||||
beginChar:"《",
|
||||
endChar:"》"
|
||||
}
|
||||
});
|
||||
scope.formatters.updateConfig("en",{
|
||||
bookname:{
|
||||
beginChar:"<",
|
||||
endChar:">"
|
||||
}
|
||||
});
|
||||
// 注册格式化器,注册为所有语言
|
||||
scope.registerFormatter("bookname", (value,args,config) => {
|
||||
let { beginChar = "<",endChar=">" } = Object.assign({},(config as any)?.bookname)
|
||||
if(args.length==1){
|
||||
beginChar = endChar = args[0]
|
||||
}else if(args.length>=2){
|
||||
beginChar = args[0]
|
||||
endChar = args[1]
|
||||
}
|
||||
return beginChar + value + endChar
|
||||
})
|
||||
})
|
||||
test('格式化器',async () => {
|
||||
expect(t("我的工资是每月{|add}元",1000)).toBe("我的工资是每月1001元")
|
||||
expect(t("我的工资是每月{|add()}元",1000)).toBe("我的工资是每月1001元")
|
||||
expect(t("我的工资是每月{|add(2)}元",1000)).toBe("我的工资是每月1002元")
|
||||
expect(t("我的工资是每月{|add|add()|add(2)}元",1000)).toBe("我的工资是每月1004元")
|
||||
})
|
||||
test('bookname式化器',async () => {
|
||||
expect(t("hello {|bookname}","tom")).toBe("hello 《tom》")
|
||||
expect(t("hello {|bookname('#')}","tom")).toBe("hello #tom#")
|
||||
expect(t("hello {|bookname('#','!')}","tom")).toBe("hello #tom!")
|
||||
await scope.change("en")
|
||||
expect(t("hello {|bookname}","tom")).toBe("hello <tom>")
|
||||
})
|
||||
})
|
@ -101,7 +101,7 @@ export class VoerkaI18nFormatterRegistry{
|
||||
configSources.push(this.getConfig(language))
|
||||
// 合并当前语言的格式化器配置参数
|
||||
this.#activeFormattersConfigs = configSources.reduce((finalConfig, curConfig)=>{
|
||||
if(isPlainObject(curConfig)) deepMerge(finalConfig,curConfig,{newObject:false,array:'replace'})
|
||||
if(isPlainObject(curConfig)) finalConfig = deepMerge(finalConfig,curConfig,{array:'replace'})
|
||||
return finalConfig
|
||||
},{})
|
||||
}catch(e){
|
||||
@ -113,7 +113,7 @@ export class VoerkaI18nFormatterRegistry{
|
||||
if(language in this.#formatters ){
|
||||
let formatters = this.#formatters[language] as VoerkaI18nFormatters
|
||||
if(!("$config" in formatters)) formatters.$config = {}
|
||||
assignObject(formatters.$config as object ,config)
|
||||
assignObject(formatters.$config as any ,config)
|
||||
}
|
||||
if(language === this.#language){
|
||||
this.generateFormattersConfigs(language)
|
||||
@ -142,7 +142,8 @@ export class VoerkaI18nFormatterRegistry{
|
||||
* 也可以指定将格式化器注册到多个语言
|
||||
*
|
||||
*/
|
||||
register(name:string | SupportedDateTypes, formatter:VoerkaI18nFormatter, {language = "*"}:{ language: string | string[] } ) {
|
||||
register(name:string | SupportedDateTypes, formatter:VoerkaI18nFormatter,options?:{ language?: string | string[] } ) {
|
||||
const { language='*' } = options || {};
|
||||
if (!isFunction(formatter) || typeof name !== "string") {
|
||||
throw new TypeError("Formatter must be a function");
|
||||
}
|
||||
@ -166,21 +167,18 @@ export class VoerkaI18nFormatterRegistry{
|
||||
* @param language
|
||||
*/
|
||||
getConfig(language?:string){
|
||||
if(language== this.#language) return this.#activeFormattersConfigs
|
||||
return language ? getByPath(this.#formatters,`${language}.$config`,{defaultValue:{}}) : {}
|
||||
}
|
||||
/**
|
||||
获取指定语言中为每个数据类型指定的格式化器
|
||||
*/
|
||||
getTypes(language?:string){
|
||||
if(language== this.#language) return this.activeFormatters.$types
|
||||
return language ? getByPath(this.#formatters,`${language}.$types`,{defaultValue:{}}) : {}
|
||||
}
|
||||
/**
|
||||
获取指定语言中为每个数据类型指定的格式化器
|
||||
*/
|
||||
getFormatters(language?:string){
|
||||
if(language== this.#language) return this.activeFormatters
|
||||
return language ? getByPath(this.#formatters,language,{defaultValue:{}}) : {}
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,9 @@ import { SupportedDateTypes, VoerkaI18nFormatter, VoerkaI18nFormatterConfigs } f
|
||||
// v2: 由于一些js引擎(如react-native Hermes )不支持命名捕获组而导致运行时不能使用,所以此处移除命名捕获组
|
||||
const varWithPipeRegexp = /\{\s*(\w+)?((\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g;
|
||||
|
||||
//
|
||||
type WrapperedVoerkaI18nFormatter = (value:string,config:VoerkaI18nFormatterConfigs)=>string;
|
||||
|
||||
/**
|
||||
* 考虑到通过正则表达式进行插值的替换可能较慢
|
||||
* 因此提供一个简单方法来过滤掉那些不需要进行插值处理的字符串
|
||||
@ -141,7 +144,7 @@ function executeChecker(checker:FormatterChecker, value:any,scope:VoerkaI18nScop
|
||||
* @param {FormatterDefineChain} formatters 经过解析过的格式化器参数链 ,多个格式化器函数(经过包装过的)顺序执行,前一个输出作为下一个格式化器的输入
|
||||
* formatters [ [<格式化器名称>,[<参数>,<参数>,...],[<格式化器名称>,[<参数>,<参数>,...]],...]
|
||||
*/
|
||||
function executeFormatter(value:any, formatters:VoerkaI18nFormatter[], scope:VoerkaI18nScope, template:string) {
|
||||
function executeFormatter(value:any, formatters:WrapperedVoerkaI18nFormatter[], scope:VoerkaI18nScope, template:string) {
|
||||
if (formatters.length === 0) return value;
|
||||
let result = value;
|
||||
// 1. 空值检查
|
||||
@ -174,7 +177,7 @@ function executeFormatter(value:any, formatters:VoerkaI18nFormatter[], scope:Voe
|
||||
// 3. 分别执行格式化器函数
|
||||
for (let formatter of formatters) {
|
||||
try {
|
||||
result = formatter(result, [result],scope.formatters.config);
|
||||
result = formatter(result, scope.formatters.config);
|
||||
} catch (e:any) {
|
||||
e.formatter = (formatter as any).$name;
|
||||
if (scope.debug)
|
||||
@ -228,23 +231,23 @@ function addDefaultFormatters(formatters:FormatterDefineChain) {
|
||||
*
|
||||
*/
|
||||
function wrapperFormatters(scope:VoerkaI18nScope, activeLanguage:string, formatters:FormatterDefineChain) {
|
||||
let wrappedFormatters:VoerkaI18nFormatter[] = [];
|
||||
let wrappedFormatters:WrapperedVoerkaI18nFormatter[] = [];
|
||||
addDefaultFormatters(formatters);
|
||||
for (let [name, args] of formatters) {
|
||||
let fn = scope.formatters.get(name,{on:'scope'})
|
||||
let formatter;
|
||||
if (isFunction(fn)) {
|
||||
formatter = (value:any, args?:any[],config?:VoerkaI18nFormatterConfigs) =>{
|
||||
return (fn as Function).call(scope.formatters.config, value, args, config);
|
||||
formatter = (value:string,config:VoerkaI18nFormatterConfigs) =>{
|
||||
return String((fn as Function).call(scope.formatters.config, value, args, config))
|
||||
}
|
||||
} else {
|
||||
// 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用
|
||||
// 比如padStart格式化器是String的原型方法,不需要配置就可以直接作为格式化器调用
|
||||
formatter = (value:any) => {
|
||||
if (isFunction(value[name])) {
|
||||
return value[name](...args);
|
||||
return String(value[name](...args));
|
||||
} else {
|
||||
return value;
|
||||
return String(value)
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -271,8 +274,10 @@ function getFormattedValue(scope:VoerkaI18nScope, activeLanguage:string, formatt
|
||||
if (formatterFuncs.length == 2) {
|
||||
// 当没有格式化器时,查询是否指定了默认数据类型的格式化器,如果有则执行
|
||||
const defaultFormatter = scope.formatters.get(getDataTypeName(value),{on:'types'})
|
||||
if (defaultFormatter) {
|
||||
return executeFormatter(value, [defaultFormatter], scope, template);
|
||||
if (defaultFormatter) {
|
||||
return executeFormatter(value, [
|
||||
(value:string,config)=>defaultFormatter.call(config, value, [], config),
|
||||
], scope, template);
|
||||
}
|
||||
} else {
|
||||
value = executeFormatter(value, formatterFuncs, scope, template);
|
||||
|
@ -145,7 +145,8 @@ export class VoerkaI18nManager extends EventEmitter{
|
||||
* @param {*} formatter
|
||||
language : 声明该格式化器适用语言
|
||||
*/
|
||||
registerFormatter(name:string,formatter:VoerkaI18nFormatter,{language="*"}:{language:string | string[] | '*'}){
|
||||
registerFormatter(name:string,formatter:VoerkaI18nFormatter,options?:{language?:string | string[] | '*'}){
|
||||
const {language = "*"} = options || {}
|
||||
if (!isFunction(formatter) || typeof name !== "string") {
|
||||
throw new TypeError("Formatter must be a function");
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export interface VoerkaI18nScopeOptions {
|
||||
defaultLanguage?: string // 默认语言名称
|
||||
activeLanguage?: string // 当前语言名称
|
||||
messages: VoerkaI18nLanguageMessagePack // 当前语言包
|
||||
idMap: Voerkai18nIdMap // 消息id映射列表
|
||||
idMap?: Voerkai18nIdMap // 消息id映射列表
|
||||
formatters: VoerkaI18nLanguageFormatters // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
|
||||
callback?:(e?:Error)=>void // 当注册到全局管理器后的回调函数
|
||||
}
|
||||
@ -173,7 +173,8 @@ export class VoerkaI18nScope {
|
||||
语言名称,语言名称数组,或者使用,分割的语言名称字符串
|
||||
asGlobal : 注册到全局
|
||||
*/
|
||||
registerFormatter(name:string, formatter:VoerkaI18nFormatter, {language = "*", asGlobal= true}:{ language: string | string[] | "*", asGlobal :boolean } ) {
|
||||
registerFormatter(name:string, formatter:VoerkaI18nFormatter, options?:{ language?: string | string[] | "*", asGlobal?:boolean } ) {
|
||||
const {language = "*", asGlobal= true} = options || {}
|
||||
if(asGlobal){
|
||||
this.global.registerFormatter(name, formatter, {language});
|
||||
}else{
|
||||
|
@ -5,35 +5,6 @@ import { replaceInterpolatedVars } from "./interpolate"
|
||||
import type { VoerkaI18nScope } from "./scope"
|
||||
|
||||
|
||||
/**
|
||||
* 当传入的翻译内容不是一个字符串时,进行默认的转换
|
||||
*
|
||||
* - 对函数则执行并取返回结果()
|
||||
* - 对Array和Object使用JSON.stringify
|
||||
* - 其他类型使用toString
|
||||
*
|
||||
* @param {*} value
|
||||
* @returns
|
||||
*/
|
||||
function transformToString(value:any){
|
||||
let result = value
|
||||
try{
|
||||
if(isFunction(result)) result = value()
|
||||
if(!(typeof(result)==="string")){
|
||||
if(Array.isArray(result) || isPlainObject(result)){
|
||||
result = JSON.stringify(result)
|
||||
}else{
|
||||
result = result.toString()
|
||||
}
|
||||
}else{
|
||||
return value
|
||||
}
|
||||
}catch{
|
||||
result = result.toString()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本id必须是一个数字
|
||||
* @param {*} content
|
||||
@ -113,20 +84,28 @@ export function translate(this:VoerkaI18nScope,message:string,...args:any[]):str
|
||||
}
|
||||
|
||||
// 3. 取得翻译文本模板字符串
|
||||
if(activeLanguage === scope.defaultLanguage){
|
||||
// 2.1 从默认语言中取得翻译文本模板字符串
|
||||
// 如果当前语言就是默认语言,不需要查询加载,只需要做插值变换即可
|
||||
// 当源文件运用了babel插件后会将原始文本内容转换为msgId
|
||||
// 如果是msgId则从scope.default中读取,scope.default=默认语言包={<id>:<message>}
|
||||
if(isMessageId(result)){
|
||||
result = (scope.default as any)[result] || message
|
||||
}
|
||||
}else{
|
||||
// 2.2 从当前语言包中取得翻译文本模板字符串
|
||||
// 如果没有启用babel插件将源文本转换为msgId,需要先将文本内容转换为msgId
|
||||
let msgId = isMessageId(result) ? result : scope.idMap[result]
|
||||
result = (scope.current as any)[msgId] || result
|
||||
// if(activeLanguage === scope.defaultLanguage){
|
||||
// // 2.1 从默认语言中取得翻译文本模板字符串
|
||||
// // 如果当前语言就是默认语言,不需要查询加载,只需要做插值变换即可
|
||||
// // 当源文件运用了babel插件后会将原始文本内容转换为msgId
|
||||
// // 如果是msgId则从scope.default中读取,scope.default=默认语言包={<id>:<message>}
|
||||
// if(isMessageId(result)){
|
||||
// result = (scope.default as any)[result] || message
|
||||
// }
|
||||
// }else{
|
||||
// // 2.2 从当前语言包中取得翻译文本模板字符串
|
||||
// // 如果没有启用babel插件将源文本转换为msgId,需要先将文本内容转换为msgId
|
||||
// let msgId = isMessageId(result) ? result : scope.idMap[result]
|
||||
// result = (scope.current as any)[msgId] || result
|
||||
// }
|
||||
|
||||
if(isMessageId(message)){
|
||||
const msgId = scope.idMap[message]
|
||||
result = (scope.current as any)[msgId] || message
|
||||
}else{
|
||||
result = (scope.current as any)[message] || message
|
||||
}
|
||||
|
||||
// 2. 处理复数
|
||||
// 经过上面的处理,content可能是字符串或者数组
|
||||
// content = "原始文本内容" || 复数形式["原始文本内容","原始文本内容"....]
|
||||
|
@ -35,7 +35,7 @@ export interface VoerkaI18nLanguageDefine {
|
||||
|
||||
|
||||
export type VoerkaI18nFormatterConfigs = Record<string, any>
|
||||
export type VoerkaI18nFormatter = ((value: string,args?: any[],$config?:VoerkI18nFormatterConfigs) => string)
|
||||
export type VoerkaI18nFormatter = ((value: string,args: any[],config: VoerkI18nFormatterConfigs) => string)
|
||||
export type VoerkaI18nTypesFormatters=Partial<Record<SupportedDateTypes, VoerkaI18nFormatter>>
|
||||
export type VoerkaI18nTypesFormatterConfig= Partial<Record<string, any>>
|
||||
export type VoerkaI18nTypesFormatterConfigs= Partial<Record<SupportedDateTypes | string, Record<string,any>>>
|
||||
@ -64,7 +64,8 @@ export type VoerkaI18nDefaultMessageLoader = (this:VoerkaI18nScope,newLanguage:s
|
||||
|
||||
export type TranslateMessageVars = number | boolean | string | Function | Date
|
||||
export interface VoerkaI18nTranslate {
|
||||
(message: string, ...args: TranslateMessageVars[]): string
|
||||
(message: string, ...vars: TranslateMessageVars[]): string
|
||||
(message: string, vars: TranslateMessageVars[]): string
|
||||
(message: string, vars?: Record<string, TranslateMessageVars>): string
|
||||
}
|
||||
export interface VoerkaI18nSupportedLanguages {
|
||||
|
Loading…
x
Reference in New Issue
Block a user