update tests

This commit is contained in:
wxzhang 2023-04-12 22:19:57 +08:00
parent 1d6c37ea79
commit 1cf4f313b3
7 changed files with 132 additions and 89 deletions

View File

@ -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>")
})
})

View File

@ -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:{}}) : {}
}

View File

@ -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);

View File

@ -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");
}

View File

@ -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{

View File

@ -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 = "原始文本内容" || 复数形式["原始文本内容","原始文本内容"....]

View File

@ -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 {