update for ts

This commit is contained in:
wxzhang 2023-04-05 16:05:56 +08:00
parent e02835fa50
commit 105a75bc59
18 changed files with 252 additions and 170 deletions

View File

@ -36,7 +36,7 @@ const loaders = {
// 语言作用域
const scope = new VoerkaI18nScope({
...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters
id : "reactapp", // 当前作用域的id自动取当前工程的package.json的name
id : "reactapp", // 当前作用域的id自动取当前工程的package.json的name
debug : false, // 是否在控制台输出高度信息
default : defaultMessages, // 默认语言包
messages : activeMessages, // 当前语言包

View File

@ -0,0 +1,64 @@
import {test,vi,describe,expect,afterAll,beforeAll} from 'vitest'
import { VoerkaI18nScope } from '../scope'
import zhFormatters from '../formatters/zh';
import enFormatters from '../formatters/en';
const zhMessages={
"1": "你好",
"2": "你好,{name}",
"3": "中国",
"4": ["我有一部车","我有很多部车"]
}
const enMessages={
"1": "hello",
"2": "hello,{name}",
"3": "china",
"4": "I have {} cars"
}
const messages = {
zh: zhMessages,
en: enMessages
}
const idMap={
"你好":1,
"你好,{name}":2,
"中国":3,
"我有{}部车":4
}
const languages = [
{ name: "zh",default:true,active:true},
{ name: "en"}
]
const formatters ={
zh:zhFormatters,
en:enFormatters
}
describe("VoerkaI18nScope", () => {
let scope = new VoerkaI18nScope({
id: "test",
languages,
idMap,
messages,
formatters
})
test("成功创建实例", () => {
expect(scope).toBeInstanceOf(VoerkaI18nScope)
expect(scope.activeLanguage).toBe("zh")
expect(scope.defaultLanguage).toBe("zh")
expect(scope.messages).toEqual(messages)
expect(scope.default).toEqual(zhMessages)
expect(scope.current).toEqual(zhMessages)
expect(scope.idMap).toEqual(idMap)
})
})
test('translate', () => {})

View File

@ -10,14 +10,14 @@ import { toChineseNumber } from "flex-tools/chinese/toChineseNumber"
import { toChineseCurrency } from "flex-tools/chinese/toChineseCurrency"
export const chineseNumberFormatter = Formatter((value: number, isBig: boolean, $config: any) => {
return toChineseNumber(value, isBig)
export const chineseNumberFormatter = Formatter((value: any, [isBig]:[isBig: boolean], $config) => {
return toChineseNumber(value, isBig) as string
}, {
params: ["isBig"]
})
export const rmbFormater = FlexFormatter((value:number | string, params : any, $config:any) => {
export const rmbFormater = FlexFormatter((value:number | string, [params] : any[], $config:any) => {
return toChineseCurrency(value, params, $config)
}, {
params: ["big", "prefix", "unit", "suffix"],

View File

@ -98,7 +98,7 @@ export const currencyFormatter = FlexFormatter((value:string | number,params:Rec
}
return toCurrency(value,params,$config)
},{
normalize: toNumber,
normalize: (value:string)=>toNumber(value),
params : ["format","unit","precision","prefix","suffix","division","symbol","radix"],
configKey: "currency"
},{

View File

@ -3,18 +3,16 @@
*/
const { isFunction,replaceAll,toDate } = require('../utils')
const { Formatter } = require('../formatter');
import { toDate } from '../utils'
import { Formatter } from '../formatter';
import { formatDateTime } from "flex-tools/misc/formatDateTime"
import { relativeTime } from "flex-tools/misc/relativeTime"
import { assignObject } from "flex-tools/object/assignObject"
import { isFunction } from "flex-tools/typecheck/isFunction"
function formatTime(value:number ,template="HH:mm:ss"){
return formatDateTime(value,template,{
})
return formatDateTime(value,template,{})
}
@ -29,9 +27,10 @@ function formatTime(value:number ,template="HH:mm:ss"){
*
**/
export type FormatterTransformer = (value:any,format:string)=>string
export function createDateTimeFormatter(options={},transformer:FormatterTransformer){
let opts = assignObject({presets:{}},options)
return Formatter(function(value,format,$config){
return Formatter(function(this:any,value:any,[format]:any[],$config:Record<string,any>){
if((format in opts.presets) && isFunction(opts.presets[format])){
return opts.presets[format](value)
}else if((format in $config)){
@ -53,17 +52,17 @@ export function createDateTimeFormatter(options={},transformer:FormatterTransfor
* - format取值local,long,short,iso,gmt,utc,<模板字符串>
* - $config.datetime.date.format指定
*/
const dateFormatter = createDateTimeFormatter({
export const dateFormatter = createDateTimeFormatter({
normalize: toDate,
params : ["format"],
configKey: "datetime.date",
presets : {
local: value=>value.toLocaleString(),
iso : value=>value.toISOString(),
utc : value=>value.toUTCString(),
gmt : value=>value.toGMTString()
local: (value:any)=>value.toLocaleString(),
iso : (value:any)=>value.toISOString(),
utc : (value:any)=>value.toUTCString(),
gmt : (value:any)=>value.toGMTString()
}
},formatDatetime)
},formatDateTime)
/**
@ -71,8 +70,8 @@ export function createDateTimeFormatter(options={},transformer:FormatterTransfor
* - format: long,short,number
* - short
*/
const quarterFormatter = createDateTimeFormatter({
normalize : value=>{
export const quarterFormatter = createDateTimeFormatter({
normalize : (value:any)=>{
const month = value.getMonth() + 1
return Math.floor( ( month % 3 == 0 ? ( month / 3 ) : (month / 3 + 1 ) ))
},
@ -85,7 +84,7 @@ const quarterFormatter = createDateTimeFormatter({
* - format: long,short,number
* - short
*/
const monthFormatter = createDateTimeFormatter({
export const monthFormatter = createDateTimeFormatter({
normalize: (value:Date)=> value.getMonth() + 1,
params : ["format"],
configKey: "datetime.month"
@ -96,7 +95,7 @@ const monthFormatter = createDateTimeFormatter({
* - format: long,short,number
* - long
*/
const weekdayFormatter = createDateTimeFormatter({
export const weekdayFormatter = createDateTimeFormatter({
normalize: (value:Date)=> value.getDay(),
params : ["format"],
configKey: "datetime.weekday"
@ -107,7 +106,7 @@ const weekdayFormatter = createDateTimeFormatter({
* - format取值local,long,short,timestamp,<模板字符串>
* - $config.datetime.time.format指定
*/
const timeFormatter = createDateTimeFormatter({
export const timeFormatter = createDateTimeFormatter({
normalize : toDate,
params : ["format"],
configKey : "datetime.time",
@ -122,7 +121,7 @@ const weekdayFormatter = createDateTimeFormatter({
* @param {*} value
* @param {*} baseTime
*/
const relativeTimeFormatter = Formatter((value:Date,baseTime:Date,$config:any)=>{
export const relativeTimeFormatter = Formatter((value:any,[baseTime]:[Date],$config:any)=>{
//const { units,now,before,base = Date.now() , after } = $config
return relativeTime(value, $config)
},{

View File

@ -8,16 +8,17 @@
* { value | number('big') }
*
*/
const { isNumber,toNumber } = require("../utils")
const { Formatter } = require("../formatter")
const { toCurrency } = require("./currency")
import { toNumber } from "../utils"
import { Formatter } from "../formatter"
import { toCurrency } from "./currency"
import { VoerkaI18nFormatter } from '../types';
const numberFormartter = Formatter(function(value,precision,division,$config){
export const numberFormartter = Formatter(function(value:any,[precision,division]:[number,number],$config:any){
return toCurrency(value, { division, precision},$config)
},{
normalize: toNumber,
params:["precision","division"],
configKey: "number"
})
}) as VoerkaI18nFormatter

View File

@ -271,8 +271,8 @@ function parseFormaterParams(strParams: string): any[] {
*/
export type Formatter = (this: any, value: string, ...args: any[]) => string
export interface FormatterOptions {
normalize?: (value: string) => string // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等需要对输入值进行处理如强制类型转换等
params?: Record<string, any>, // 可选的声明参数顺序如果是变参的则需要传入null
normalize?: (value: string) => any // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等需要对输入值进行处理如强制类型转换等
params?: Record<string, any> | null, // 可选的声明参数顺序如果是变参的则需要传入null
configKey?: string // 声明该格式化器在$config中的路径支持简单的使用.的路径语法
}
@ -285,7 +285,7 @@ export function createFormatter(fn: Formatter, options?: FormatterOptions, defau
// 最后一个参数是传入activeFormatterConfig参数
// 并且格式化器的this指向的是activeFormatterConfig
const $formatter = function (this: any, value: string, ...args: any[]) {
const $formatter = function (this: any, value: string, args: any[],$config:Record<string,any>) {
let finalValue = value
// 1. 输入值规范处理,主要是进行类型转换,确保输入的数据类型及相关格式的正确性,提高数据容错性
if (isFunction(opts.normalize)) {
@ -312,7 +312,7 @@ export function createFormatter(fn: Formatter, options?: FormatterOptions, defau
if (args[i] !== undefined) finalArgs[i] = args[i]
}
}
return fn.call(this, finalValue, ...finalArgs, formatterConfig)
return fn.call(this, finalValue, finalArgs, formatterConfig)
}
$formatter.configurable = true
return $formatter
@ -339,9 +339,7 @@ export const createFlexFormatter = function (fn: Formatter, options: FormatterOp
const opts = assignObject({
params: {}
}, options)
const $flexFormatter = Formatter(function (this: any, value: string, ...args: string[]) {
// 1. 最后一个参数总是代表格式化器的参数,不同语言不一样
let $config = args[args.length - 1] as unknown as Record<string, any>
const $flexFormatter = Formatter(function (this: any, value: string,args: string[],$config:Record<string,any> ) {
// 2. 从语言配置中读取默认参数
let finalParams = (options.params || {}).reduce((r: Record<string, any>, name: string) => {
r[name] = $config[name] == undefined ? (defaultParams || {})[name] : $config[name]
@ -361,6 +359,7 @@ export const createFlexFormatter = function (fn: Formatter, options: FormatterOp
}
export const Formatter = createFormatter
export const FlexFormatter = createFlexFormatter

View File

@ -4,9 +4,10 @@
*
*
*/
import { VoerkaI18nFormatter, VoerkaI18nFormatters, VoerkaI18nFormattersLoader, VoerkaI18nLanguageFormatters, SupportedDateTypes, VoerkaI18nFormatterConfigs, Formatter } from './types';
import { VoerkaI18nFormatter, VoerkaI18nFormatters, VoerkaI18nFormattersLoader, VoerkaI18nLanguageFormatters, SupportedDateTypes, VoerkaI18nFormatterConfigs } from './types';
import { DataTypes } from './utils';
import { get as getByPath } from "flex-tools/object/get"
import { isFunction } from 'flex-tools/typecheck/isFunction';
export interface VoerkaI18nScopeCache{
@ -41,7 +42,7 @@ export class VoerkaI18nFormatterRegistry{
set language(language:string){
this.#language = language
if(!(language in this.formatters)){
(this.formatters[language] as any) = Object.assign({},EmptyFormatters)
(this.#formatters[language] as any) = Object.assign({},EmptyFormatters)
}
this.#ready = typeof(this.#formatters[language]) != 'function'
}
@ -63,7 +64,7 @@ export class VoerkaI18nFormatterRegistry{
*
使,
*/
register(name:string, formatter:VoerkaI18nFormatter, {language = "*"}:{ language: string | string[] } ) {
register(name:string | SupportedDateTypes, formatter:VoerkaI18nFormatter, {language = "*"}:{ language: string | string[] } ) {
if (!isFunction(formatter) || typeof name !== "string") {
throw new TypeError("Formatter must be a function");
}
@ -73,7 +74,7 @@ export class VoerkaI18nFormatterRegistry{
this.#formatters[lngName] = { }
}
if(typeof(this.#formatters[lngName])!="function"){
let lngFormatters = this.#formatters[lngName] as VoerkaI18nFormatters
let lngFormatters = this.#formatters[lngName] as any
if (DataTypes.includes(name)) {
if(!lngFormatters.$types){
lngFormatters.$types = {}
@ -168,7 +169,7 @@ export class VoerkaI18nFormatterRegistry{
*/
get(name:string,dataType?:SupportedDateTypes):VoerkaI18nFormatter | undefined{
if(!this.#ready) throw new FormattersNotLoadedError(this.#language)
const lngFormatters = this.#formatters[this.#language] as VoerkaI18nFormatters
const lngFormatters = this.#formatters[this.#language] as any
if(dataType && (dataType in lngFormatters.$types!)){
return lngFormatters.$types![dataType]
}else if(name in lngFormatters){

View File

@ -1,5 +1,7 @@
const { toNumber,isFunction } = require("../utils")
const { Formatter } = require("../formatter")
import { assignObject } from 'flex-tools/object/assignObject';
import { VoerkaI18nFormatterConfigs } from '../types';
import { toNumber } from "../utils"
import { Formatter } from "../formatter"
/**
*
@ -10,11 +12,11 @@ const { Formatter } = require("../formatter")
* @param {...any} args
* @returns
*/
function dict(key, values) {
function dict(key:string, values:any) {
if(key in values){
return values[key]
}else{
return ("default" in values) ? values[key] : value
return ("default" in values) ? values[key] : values
}
}
@ -37,10 +39,8 @@ const { Formatter } = require("../formatter")
* @paran {String} next true/false,break,skip,break
* @param {*} config
*/
const empty = Formatter(function(value,escapeValue,next,$config){
if(next===false) next = 'break'
if(next===true) next = 'skip'
let opts = Object.assign({escape:"",next:'break',values:[]},$config)
const empty = Formatter(function(value:any,escapeValue:any,next: 'break' | 'ignore',$config:VoerkaI18nFormatterConfigs){
let opts = assignObject({escape:"",next:'break',values:[]},$config)
if(escapeValue!=undefined) opts.escape = escapeValue
let emptyValues = [undefined,null]
if(Array.isArray(opts.values)) emptyValues.push(...opts.values)
@ -72,10 +72,10 @@ const empty = Formatter(function(value,escapeValue,next,$config){
* @param {*} config
* @returns
*/
const error = Formatter(function(value,escapeValue,next,$config){
const error = Formatter(function(value:any,escapeValue:any,next:'break' | 'ignore',$config:VoerkaI18nFormatterConfigs){
if(value instanceof Error){
try{
let opts = Object.assign({escape:null,next:'break'},$config)
let opts = assignObject({escape:null,next:'break'},$config)
if(escapeValue!=undefined) opts.escape = escapeValue
if(next!=undefined) opts.next = next
return {
@ -100,7 +100,7 @@ const error = Formatter(function(value,escapeValue,next,$config){
* @param {*} prefix
* @returns
*/
function prefix(value,prefix="") {
export function prefix(value:any,prefix:string="") {
return prefix ? `${prefix}${value}` : value
}
/**
@ -109,7 +109,7 @@ function prefix(value,prefix="") {
* @param {*} suffix
* @returns
*/
function suffix(value,suffix="") {
export function suffix(value:any,suffix:string="") {
return suffix ? `${value}${suffix}` : value
}
@ -141,7 +141,7 @@ const FILE_SIZE_WHOLE_UNITS = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "
* @param {*} brief
* @param {*} options
*/
const filesize= Formatter((value,unit,brief=true,$config)=>{
export const filesize= Formatter((value:any,unit:string,brief:boolean=true,$config:VoerkaI18nFormatterConfigs)=>{
let v = toNumber(value)
let unitIndex
if(unit==undefined || unit=="auto"){
@ -164,13 +164,11 @@ const FILE_SIZE_WHOLE_UNITS = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "
})
module.exports = {
export default {
dict,
empty,
error,
prefix,
suffix,
filesize,
error,
empty
}
filesize
}

View File

@ -4,7 +4,6 @@
*/
import { Formatter } fom "../formatter"
import { dateFormatter,quarterFormatter,monthFormatter,weekdayFormatter,timeFormatter,relativeTimeFormatter } from "../datatypes/datetime"
import { numberFormartter } from "../datatypes/numeric"
import { currencyFormatter } from "../datatypes/currency"
@ -149,11 +148,11 @@ export default {
},
// 默认数据类型的格式化器
$types: {
Date : dateFormatter,
Null : value =>"",
Undefined: value =>"",
Error : value => "ERROR",
Boolean : value =>value ? "True":"False",
// Date : dateFormatter,
Null : (value: any) =>"",
Undefined: (value: any) =>"",
Error : (value: any) => "ERROR",
Boolean : (value: any) =>value ? "True":"False",
Number : numberFormartter
},
// 以下是格式化定义

View File

@ -5,9 +5,10 @@
import { chineseNumberFormatter,rmbFormater } from "../datatypes/chinese"
import {CN_DATETIME_UNITS, CN_MONTH_NAMES, CN_SHORT_MONTH_NAMES, CN_SHORT_WEEK_DAYS, CN_WEEK_DAYS } from "flex-tools/chinese/consts"
module.exports = {
export default {
// 配置参数: 格式化器函数的最后一个参数就是该配置参数
$config:{
datetime : {

View File

@ -1,3 +1,4 @@
// @ts-ignore
import replaceAll from "string.prototype.replaceall"
replaceAll.shim()

View File

@ -203,7 +203,9 @@ function getFormatter(scope:VoerkaI18nScope, activeLanguage:string, name:string)
}
}
}
export type FormatterChecker = (value:any,config?:VoerkaI18nFormatterConfigs)=>any;
export type FormatterChecker = ((value:any,config?:VoerkaI18nFormatterConfigs)=>any) & {
$name:string
}
/**
* Checker是一种特殊的格式化器
@ -242,7 +244,7 @@ function executeChecker(checker:FormatterChecker, value:any,scope:VoerkaI18nScop
* @param {FormatterDefineChain} formatters ()
* formatters [ [<格式化器名称>,[<参数>,<参数>,...],[<格式化器名称>,[<参数>,<参数>,...]],...]
*/
function executeFormatter(value:any, formatters:FormatterDefineChain, scope:VoerkaI18nScope, template:string) {
function executeFormatter(value:any, formatters:VoerkaI18nFormatter[], scope:VoerkaI18nScope, template:string) {
if (formatters.length === 0) return value;
let result = value;
// 1. 空值检查
@ -258,11 +260,11 @@ function executeFormatter(value:any, formatters:FormatterDefineChain, scope:Voer
}
// 2. 错误检查
const errorCheckerIndex = formatters.findIndex((func) => (func as any).$name === "error" );
let errorChecker;
let errorChecker:FormatterChecker;
if (errorCheckerIndex != -1) {
errorChecker = formatters.splice(errorCheckerIndex, 1)[0] as unknown as FormatterChecker
if (result instanceof Error) {
result.formatter = formatter.$name;
(result as any).formatter = errorChecker.$name;
const { value, next } = executeChecker(errorChecker, result,scope);
if (next == "break") {
return value;
@ -275,13 +277,13 @@ function executeFormatter(value:any, formatters:FormatterDefineChain, scope:Voer
// 3. 分别执行格式化器函数
for (let formatter of formatters) {
try {
result = formatter(result, scope.activeFormatterConfig);
result = formatter(result, [result],scope.activeFormatterConfig);
} catch (e:any) {
e.formatter = formatter.$name;
e.formatter = (formatter as any).$name;
if (scope.debug)
console.error(`Error while execute i18n formatter<${formatter.$name}> for ${template}: ${e.message} `);
if (isFunction(errorChecker)) {
const { value, next } = executeChecker(errorChecker, result);
console.error(`Error while execute i18n formatter<${(formatter as any).$name}> for ${template}: ${e.message} `);
if (isFunction(errorChecker!)) {
const { value, next } = executeChecker(errorChecker!, result,scope);
if (next == "break") {
if (value !== undefined) result = value;
break;
@ -329,13 +331,13 @@ function addDefaultFormatters(formatters:FormatterDefineChain) {
*
*/
function wrapperFormatters(scope:VoerkaI18nScope, activeLanguage:string, formatters:FormatterDefineChain) {
let wrappedFormatters = [];
let wrappedFormatters:VoerkaI18nFormatter[] = [];
addDefaultFormatters(formatters);
for (let [name, args] of formatters) {
let fn = getFormatter(scope, activeLanguage, name);
let formatter;
if (isFunction(fn)) {
formatter = (value:any, config:VoerkaI18nFormatterConfigs) =>fn.call(scope.activeFormatterConfig, value, args, config);
formatter = (value:any, args?:any[],config?:VoerkaI18nFormatterConfigs) =>fn.call(scope.activeFormatterConfig, value, args, config);
} else {
// 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用
// 比如padStart格式化器是String的原型方法不需要配置就可以直接作为格式化器调用
@ -362,7 +364,7 @@ function wrapperFormatters(scope:VoerkaI18nScope, activeLanguage:string, formatt
* @param {*} value
* @returns
*/
function getFormattedValue(scope:VoerkaI18nScope, activeLanguage:string, formatters:FormatterDefineChain, value, template) {
function getFormattedValue(scope:VoerkaI18nScope, activeLanguage:string, formatters:FormatterDefineChain, value:any, template:string) {
// 1. 取得格式化器函数列表,然后经过包装以传入当前格式化器的配置参数
const formatterFuncs = wrapperFormatters(scope, activeLanguage, formatters);
// 3. 执行格式化器

View File

@ -1,11 +1,9 @@
import { isFunction } from "flex-tools/typecheck/isFunction"
import { deepMerge } from "flex-tools/object/deepMerge"
import {DataTypes} from "./utils"
import { EventEmitter } from "./eventemitter"
import inlineFormatters from "./formatters"
import { VoerkaI18nScope } from "./scope"
import type { VoerkaI18nScope } from "./scope"
import type { VoerkaI18nLanguageDefine, VoerkaI18nLanguageFormatters, VoerkaI18nDefaultMessageLoader, VoerkaI18nFormatter, VoerkaI18nTypesFormatters } from "./types"
import { SupportedDateTypes } from './types';
import { VoerkaI18nFormatterRegistry } from "./formatterRegistry"
// 默认语言配置
@ -24,7 +22,7 @@ export interface VoerkaI18nManagerOptions {
debug?: boolean
defaultLanguage: string
activeLanguage: string
formatters: VoerkaI18nLanguageFormatters
formatters?: VoerkaI18nLanguageFormatters
languages: VoerkaI18nLanguageDefine[]
}
/**
@ -126,9 +124,9 @@ export class VoerkaI18nManager extends EventEmitter{
* @param {*} scope
*/
async register(scope:VoerkaI18nScope){
if(!(scope instanceof VoerkaI18nScope)){
throw new TypeError("Scope must be an instance of VoerkaI18nScope")
}
// if(!(scope instanceof VoerkaI18nScope)){
// throw new TypeError("Scope must be an instance of VoerkaI18nScope")
// }
this.#scopes.push(scope)
return await scope.refresh(this.activeLanguage)
}

View File

@ -1,12 +1,9 @@
import { DataTypes } from "./utils"
import { isPlainObject } from "flex-tools/typecheck/isPlainObject"
import { isFunction } from "flex-tools/typecheck/isFunction"
import { deepClone } from "flex-tools/object/deepClone"
import { get as getByPath } from "flex-tools/object/get"
import { isFunction } from "flex-tools/typecheck/isFunction"
import { translate } from "./translate"
import { deepMerge } from "flex-tools/object/deepMerge"
import { assignObject } from "flex-tools/object/assignObject"
import type {VoerkaI18nManager } from "./manager"
import {VoerkaI18nManager } from "./manager"
import type {
VoerkaI18nFormatterConfigs,
VoerkaI18nDefaultMessageLoader,
@ -18,10 +15,12 @@ import type {
VoerkaI18nLanguagePack,
VoerkaI18nScopeCache,
VoerkaI18nTranslate,
VoerkaI18nLoaders,
VoerkaI18nMessageLoaders,
VoerkaI18nTypesFormatters,
VoerkaI18nFormatters,
VoerkaI18nDynamicLanguageMessages,
VoerkaI18nLanguageMessagePack,
VoerkaI18nMessageLoader,
} from "./types"
import { VoerkaI18nFormatterRegistry } from './formatterRegistry';
import { InvalidLanguageError } from "./errors"
@ -29,14 +28,12 @@ import { InvalidLanguageError } from "./errors"
export interface VoerkaI18nScopeOptions {
id?: string
debug?: boolean
languages: VoerkaI18nLanguageDefine[] // 当前作用域支持的语言列表
defaultLanguage: string // 默认语言名称
activeLanguage: string // 当前语言名称
default: VoerkaI18nLanguageMessages // 默认语言包
messages: VoerkaI18nLanguageMessages // 当前语言包
languages: VoerkaI18nLanguageDefine[] // 当前作用域支持的语言列表
defaultLanguage?: string // 默认语言名称
activeLanguage?: string // 当前语言名称
messages: VoerkaI18nLanguageMessagePack // 当前语言包
idMap: Voerkai18nIdMap // 消息id映射列表
formatters: VoerkaI18nLanguageFormatters // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
loaders: VoerkaI18nLoaders; // 异步加载语言文件的函数列表
formatters: VoerkaI18nLanguageFormatters // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
}
export class VoerkaI18nScope {
@ -48,83 +45,99 @@ export class VoerkaI18nScope {
#activeFormatters:VoerkaI18nFormatters = {}
#activeFormatterConfig: VoerkaI18nFormatterConfigs={}
#cache:VoerkaI18nScopeCache
#messages:VoerkaI18nLanguageMessages = {}
#formatterRegistry:VoerkaI18nFormatterRegistry
constructor(options:VoerkaI18nScopeOptions, callback:(e?:Error)=>void) {
#defaultLanguage:string ='zh'
#activeLanguage:string='zh'
#currentMessages:VoerkaI18nLanguageMessages = {} // 当前语言包
constructor(options:VoerkaI18nScopeOptions, callback?:(e?:Error)=>void) {
this.#options = assignObject({
id : Date.now().toString() + parseInt(String(Math.random() * 1000)),
debug : false,
languages : {}, // 当前作用域支持的语言列表
defaultLanguage: "zh", // 默认语言名称
activeLanguage : "zh", // 当前语言名称
default : {}, // 默认语言包
messages : {}, // 当前语言包
idMap : {}, // 消息id映射列表
formatters : {}, // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
loaders : {} // 异步加载语言文件的函数列表
messages : {}, // 所有语言包={[language]:VoerkaI18nLanguageMessages}
idMap : {}, // 消息id映射列表
formatters : {}, // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
},options) as Required<VoerkaI18nScopeOptions>
this.#patchMessages = {}; // 语言包补丁信息{<language>: {....},<language>:{....}}
this.#refreshing = false; // 正在加载语言包标识
// 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历
this.#cache = {
activeLanguage : this.#options.activeLanguage,
typedFormatters: {},
formatters : {},
};
// 初始化
this._initiLanguages()
// 如果不存在全局VoerkaI18n实例说明当前Scope是唯一或第一个加载的作用域则自动创建全局VoerkaI18n实例
if (!globalThis.VoerkaI18n) {
const { VoerkaI18nManager } = require("./");
globalThis.VoerkaI18n = new VoerkaI18nManager({
debug : this.debug,
defaultLanguage: this.defaultLanguage,
activeLanguage : this.activeLanguage,
defaultLanguage: this.#defaultLanguage,
activeLanguage : this.#activeLanguage,
languages : options.languages,
});
}
this.#t = translate.bind(this)
this.#global = globalThis.VoerkaI18n as unknown as VoerkaI18nManager;
this.#formatterRegistry = new VoerkaI18nFormatterRegistry()
this.loadFormatters(this.activeLanguage) // 初始化活动的格式化器
this._mergePatchedMessages(); // 从本地缓存中读取并合并补丁语言包
this._patch(this.messages, this.activeLanguage); // 延后执行补丁命令,该命令会向远程下载补丁包
this.register(callback); // 在全局注册作用域
this._patch(this.#currentMessages, this.activeLanguage); // 延后执行补丁命令,该命令会向远程下载补丁包
if(callback) this.register(callback); // 在全局注册作用域
}
get id() {return this.#options.id;} // 作用域唯一id
get debug() {return this.#options.debug;} // 调试开关
get defaultLanguage() {return this.#options.defaultLanguage;} // 默认语言名称
get defaultLanguage() {return this.#global.defaultLanguage;} // 默认语言名称
get activeLanguage() {return this.#global.activeLanguage;} // 默认语言名称
get default() {return this.#options.default;} // 默认语言包
get messages() {return this.#options.messages; } // 当前语言包
// 默认语言包,只能静态语言包,不能是动态语言包
get default() {return this.#options.messages[this.#defaultLanguage] as VoerkaI18nLanguageMessages;}
get current() {return this.#currentMessages;} // 当前语言包
get messages() {return this.#options.messages; } // 所有语言包
get idMap() {return this.#options.idMap;} // 消息id映射列表
get languages() {return this.#options.languages;} // 当前作用域支持的语言列表[{name,title,fallback}]
get loaders() { return this.#options.loaders;} // 异步加载语言文件的函数列表
get global() { return this.#global;} // 引用全局VoerkaI18n配置注册后自动引用
get formatters() { return this.#formatterRegistry;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
get activeFormatters() {return this.#formatterRegistry.formatters} // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}
get formatters() { return this.#formatterRegistry;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
get activeFormatters() {return this.#formatterRegistry.formatters} // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}
get activeFormatterConfig(){return this.#activeFormatterConfig} // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数
get cache(){return this.#cache }
set cache(value:VoerkaI18nScopeCache){ this.#cache=value }
get translate(){return this.#t}
get t(){return this.#t}
get t(){return this.#t}
/**
*
* - en配置为默认回退语言
* -
*/
_initiLanguages(){
if(!Array.isArray(this.languages)){
console.warn("[VoerkaI18n] invalid languages config,use default languages config instead.")
this.#options.languages = [
{name: "zh",title: "中文"},
{name: "zh",title: "中文",default:true,active:true},
{name: "en",title: "英文"}
]
}
// 将en配置为默认回退语言
this.languages.forEach(language=>{
if(!language.fallback) language.fallback = "en"
if(language.default) this.#defaultLanguage = language.name
if(language.active) this.#activeLanguage = language.name
})
// 确保提供了有效的默认语言和活动语言
const lanMessages = this.#options.messages
if(!(this.#defaultLanguage in lanMessages)) {
this.#defaultLanguage = Object.keys(lanMessages)[0]
}
if(!(this.#activeLanguage in lanMessages)){
this.#activeLanguage = this.#defaultLanguage
}
if(!(this.#defaultLanguage in lanMessages)){
throw new Error("[VoerkaI18n] invalid <defaultLanguage> config, messages not specified.")
}
// 初始化时,默认和激活的语言包只能是静态语言包,不能是动态语言包
// 因为初始化时激活语言需要马上显示,如果是异步语言包,会导致显示延迟
if(isFunction(this.messages[this.#defaultLanguage])){
throw new Error("[VoerkaI18n] invalid <defaultLanguage> config, messages must be static.")
}
this.#currentMessages = this.messages[this.#activeLanguage] as VoerkaI18nLanguageMessages
}
/**
@ -132,8 +145,8 @@ export class VoerkaI18nScope {
* @param {*} callback
*/
register(callback:(e?:Error)=>void) {
if (!isFunction(callback)) callback = () => {};
this.global.register(this).then(()=>callback()).catch((e)=>callback(e));
if (!isFunction(callback)) callback = () => {};
this.global.register(this).then(()=>callback()).catch((e)=>callback(e));
}
/**
*
@ -186,10 +199,10 @@ export class VoerkaI18nScope {
* ...
* }
*/
private loadFormatters(newLanguage:string){
this.#formatterRegistry.language = newLanguage
private loadFormatters(newLanguage:string){
// 初始化格式化器
this.formatters.loadInitials(this.#options.formatters)
this.#formatterRegistry.language = newLanguage
if(this.#options.formatters)
// 将配置中的指定为全局的格式化器注册到全局
Object.entries(this.#options.formatters).forEach(([langName,formatters])=>{
@ -308,29 +321,31 @@ export class VoerkaI18nScope {
* 退
*/
private _fallback() {
this.#options.messages = this.default;
this.#options.activeLanguage = this.defaultLanguage;
this.#currentMessages = this.default;
this.#activeLanguage = this.defaultLanguage;
}
/**
* :
*
*
*
* - {}
* - Promise<VoerkaI18nLanguageMessages>
*
* -
*
*
* @param language
* @returns
*/
private async loadLanguageMessages(language:string):Promise<VoerkaI18nLanguageMessages>{
if(!this.hasLanguage(language)) throw new InvalidLanguageError(`Not found language <${language}>`)
// if(!this.hasLanguage(language)) throw new InvalidLanguageError(`Not found language <${language}>`)
// 非默认语言可以是:语言包对象,也可以是一个异步加载语言包文件,加载器是一个异步函数
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
let loader = this.loaders[language];
let loader = this.messages[language];
let messages:VoerkaI18nLanguageMessages = {}
if (isPlainObject(loader)) { // 静态语言包
messages = loader as unknown as VoerkaI18nLanguageMessages;
} else if (isFunction(loader)) { // 语言包异步chunk
messages = (await loader()).default;
messages = (await (loader as VoerkaI18nMessageLoader))().default;
} else if (isFunction(this.global.defaultMessageLoader)) {
// 从远程加载语言包:如果该语言没有指定加载器,则使用全局配置的默认加载器
const loadedMessages = (await this.global.loadMessagesFromDefaultLoader(language,this)) as unknown as VoerkaI18nDynamicLanguageMessages;
@ -344,7 +359,9 @@ export class VoerkaI18nScope {
$remote : true // 添加一个标识,表示该语言包是从远程加载的
},this.default,loadedMessages); // 合并默认语言包和动态语言包,这样就可以局部覆盖默认语言包
}
}
}else{
throw new Error(`Not found loader for language <${language}>`)
}
return messages
}
/**
@ -356,8 +373,8 @@ export class VoerkaI18nScope {
if (!newLanguage) newLanguage = this.activeLanguage;
// 默认语言:由于默认语言采用静态加载方式而不是异步块,因此只需要简单的替换即可
if (newLanguage === this.defaultLanguage) {
this.#options.messages = this.default;
await this._patch(this.#options.messages, newLanguage); // 异步补丁
this.#currentMessages = this.default;
await this._patch(this.#currentMessages, newLanguage); // 异步补丁
await this._changeFormatters(newLanguage);
this.#refreshing = false
return;
@ -365,12 +382,12 @@ export class VoerkaI18nScope {
try{
let messages = await this.loadLanguageMessages(newLanguage)
if(messages){
this.#options.messages = messages
this.#options.activeLanguage = newLanguage;
this.#currentMessages = messages
this.#activeLanguage = newLanguage;
// 打语言包补丁, 如果是从远程加载语言包则不需要再打补丁了
// 因为远程加载的语言包已经是补丁过的了
if(!messages.$remote) {
await this._patch(this.#options.messages, newLanguage);
await this._patch(this.#currentMessages, newLanguage);
}
// 切换到对应语言的格式化器
await this._changeFormatters(newLanguage);
@ -458,6 +475,7 @@ export class VoerkaI18nScope {
on(callback:Function) {return this.#global.on(callback); }
off(callback:Function) {return this.#global.off(callback); }
offAll() {return this.#global.offAll();}
async change(language:string) {
await this.#global.change(language);
}

View File

@ -125,7 +125,7 @@ export function translate(this:VoerkaI18nScope,message:string,...args:any[]):str
// 2.2 从当前语言包中取得翻译文本模板字符串
// 如果没有启用babel插件将源文本转换为msgId需要先将文本内容转换为msgId
let msgId = isMessageId(result) ? result : scope.idMap[result]
result = scope.messages[msgId] || result
result = scope.current[msgId] || result
}
// 2. 处理复数
// 经过上面的处理content可能是字符串或者数组

View File

@ -8,9 +8,13 @@ declare global {
export type SupportedDateTypes = "String" | "Number" | "Boolean" | "Object" | "Array" | "Function" | "Error" | "Symbol" | "RegExp" | "Date" | "Null" | "Undefined" | "Set" | "Map" | "WeakSet" | "WeakMap"
// 语言包
export type VoerkaI18nLanguageMessages = Record<string, string | string[]> & {
$config?: VoerkaI18nTypesFormatterConfigs
}
export type VoerkaI18nLanguageMessagePack = Record<string, VoerkaI18nLanguageMessages | VoerkaI18nMessageLoader>
export type VoerkaI18nDynamicLanguageMessages = Record<string, string | string[]> & {
$config?: VoerkaI18nTypesFormatterConfigs
}
@ -23,38 +27,36 @@ export type Voerkai18nIdMap = Record<string, number>
export interface VoerkaI18nLanguageDefine {
name: string
title?: string
default?: boolean
default?: boolean // 是否默认语言
active?: boolean
fallback?: string
}
export type VoerkaI18nFormatterConfigs = Record<string, any>
export type VoerkaI18nFormatter = (value: string,args: any[],$config:VoerkI18nFormatterConfigs) => string
export type VoerkaI18nTypesFormatters=Record<SupportedDateTypes | string, VoerkaI18nFormatter>
export type VoerkaI18nTypesFormatterConfigs= Record<SupportedDateTypes | string, Record<string,any>>
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>>>
export type VoerkaI18nFormattersLoader = (()=>Promise<VoerkaI18nFormatters>)
// 每一个语言的格式化器定义={$types:{...},$config:{...},[格式化器名称]: () => {},[格式化器名称]: () => {}
// 在formatters/xxxx.ts里面进行配置
export type VoerkaI18nFormatters = ({
export type VoerkaI18nFormatters = {
global?: boolean | Omit<VoerkaI18nFormatters,'global'> // 是否全局格式化器
$types?:VoerkaI18nTypesFormatters
$config?: VoerkaI18nTypesFormatterConfigs
} & {
[DataTypeName in SupportedDateTypes]?: VoerkaI18nFormatter
} & {
} | {
[key: string]: VoerkaI18nFormatter
})
}
// 包括语言的{"*":{...},zh:{...},en:{...}}
// 声明格式化器
export type VoerkaI18nLanguageFormatters = Record<string,VoerkaI18nFormatters | VoerkaI18nFormattersLoader>
export type VoerkaI18nLanguageFormatters = Record<string,VoerkaI18nFormatters | VoerkaI18nMessageLoader>
//
export type VoerkaI18nLoader = () => Awaited<Promise<any>>
export interface VoerkaI18nLoaders {
[key: string]: VoerkaI18nLoader
export type VoerkaI18nMessageLoader = () => Awaited<Promise<any>>
export interface VoerkaI18nMessageLoaders {
[key: string]: VoerkaI18nMessageLoader
}
export type VoerkaI18nDefaultMessageLoader = (this:VoerkaI18nScope,newLanguage:string,scope:VoerkaI18nScope)=>Promise<VoerkaI18nLanguageMessages>
@ -145,14 +147,13 @@ export interface FormatterDefine {
}
// 创建格式化器
export type CreateFormatterType = (fn: Function, options: CreateFormatterOptions, defaultParams: Record<string, any>) => FormatterDefine
export var createFormatter: CreateFormatterType
export var Formatter: CreateFormatterType
// export var createFormatter: CreateFormatterType
// export var Formatter: CreateFormatterType
export type CreateFlexFormatterType = (fn: Function, options: CreateFormatterOptions, defaultParams: Record<string, any>) => FormatterDefine
export var createFlexFormatter: CreateFlexFormatterType
export var FlexFormatter: CreateFlexFormatterType
export var getDataTypeName: (value: any) => string
export var toDate: (value: any) => Date | number
export var toNumber: (value: any, defaultValue: number) => number
export var toBoolean: (value: any) => boolean
// export var createFlexFormatter: CreateFlexFormatterType
// export var FlexFormatter: CreateFlexFormatterType
// export var getDataTypeName: (value: any) => string
// export var toDate: (value: any) => Date | number
// export var toBoolean: (value: any) => boolean

View File

@ -44,12 +44,12 @@ export const DataTypes = ["String","Number","Boolean","Object","Array","Functio
/**
*
*/
export function toNumber(value:any,defualt=0):number {
export function toNumber(value:any):number {
try {
if (isNumber(value)) {
return parseFloat(value)
} else {
return defualt
return 0
}
} catch {
return value