feat: typescript重构

This commit is contained in:
wxzhang 2023-04-01 22:17:24 +08:00
parent d5dcae4c8a
commit f59c165e71
7 changed files with 3939 additions and 3539 deletions

View File

@ -1,7 +1,7 @@
{
"name": "@voerkai18n/runtime",
"version": "1.1.32",
"description": "核心运行时",
"description": "runtime of voerkai18n ",
"main": "./dist/index.cjs",
"module": "./dist/index.esm.js",
"typings": "./index.d.ts",
@ -21,26 +21,15 @@
"author": "wxzhang",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.7",
"@babel/runtime-corejs3": "latest",
"core-js": "^3.27.1",
"flex-tools": "^1.0.72",
"string.prototype.replaceall": "^1.0.7"
},
"devDependencies": {
"@babel/cli": "^7.17.6",
"@babel/core": "^7.17.5",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/runtime": "^7.17.8",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-commonjs": "^21.0.2",
"@rollup/plugin-node-resolve": "^13.1.3",
"deepmerge": "^4.2.2",
"@swc/core": "^1.3.44",
"@types/node": "^18.15.11",
"jest": "^27.5.1",
"rollup": "^2.69.0",
"rollup-plugin-clear": "^2.0.7",
"rollup-plugin-terser": "^7.0.2",
"tsup": "^6.7.0",
"typescript": "^4.9.3",
"vitest": "^0.29.8"
},
"lastPublish": "2023-03-30T08:53:24+08:00"

View File

@ -34,13 +34,14 @@ import { isPlainObject } from "flex-tools/typecheck/isPlainObject"
import { isFunction } from "flex-tools/typecheck/isFunction"
import { parseFormatters } from "./formatter"
import { VoerkaI18nScope } from "./scope"
import { SupportedDateTypes } from "./types"
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
// 支持参数: { var | formatter(x,x,..) | formatter }
// v1 采用命名捕获组
//let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g;
// v2: 由于一些js引擎(如react-native Hermes )不支持命名捕获组而导致运行时不能使用,所以此处移除命名捕获组
let varWithPipeRegexp = /\{\s*(\w+)?((\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g;
const varWithPipeRegexp = /\{\s*(\w+)?((\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g;
/**
*
@ -51,7 +52,7 @@ let varWithPipeRegexp = /\{\s*(\w+)?((\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g;
* @param {*} str
* @returns {boolean} true=
*/
function hasInterpolation(str) {
function hasInterpolation(str:string):boolean {
return str.includes("{") && str.includes("}");
}
@ -80,7 +81,7 @@ function hasInterpolation(str) {
* ...
* ]
*/
function getInterpolatedVars(str) {
function getInterpolatedVars(str:string) {
let vars = [];
forEachInterpolatedVars(str, (varName, formatters, match) => {
let varItem = {
@ -120,7 +121,7 @@ function forEachInterpolatedVars(str:string, replacer, options = {}) {
try {
const finalValue = replacer(varname, formatters, matched[0]);
if (opts.replaceAll) {
result = replaceAll(result,matched[0], finalValue);
result = (result as any).replaceAll(matched[0], finalValue);
} else {
result = result.replace(matched[0], finalValue);
}
@ -139,7 +140,7 @@ function forEachInterpolatedVars(str:string, replacer, options = {}) {
* @param {*} activeLanguage
*/
function resetScopeCache(scope:VoerkaI18nScope, activeLanguage:string | null) {
scope.$cache = { activeLanguage, typedFormatters: {}, formatters: {} };
scope.cache = { activeLanguage, typedFormatters: {}, formatters: {} };
}
/**
@ -166,30 +167,32 @@ function resetScopeCache(scope:VoerkaI18nScope, activeLanguage:string | null) {
* @param {*} dataType
* @returns {Function}
*/
function getDataTypeDefaultFormatter(scope, activeLanguage, dataType) {
function getDataTypeDefaultFormatter(scope:VoerkaI18nScope, activeLanguage:string , dataType:SupportedDateTypes) {
// 当指定数据类型的的默认格式化器的缓存处理
if (!scope.$cache) resetScopeCache(scope);
if (scope.$cache.activeLanguage === activeLanguage) {
if (dataType in scope.$cache.typedFormatters)
return scope.$cache.typedFormatters[dataType];
if (!scope.cache) resetScopeCache(scope,activeLanguage);
if (scope.cache.activeLanguage === activeLanguage) {
if (scope.cache.typedFormatters && dataType in scope.cache.typedFormatters)
return scope.cache.typedFormatters[dataType];
} else {
// 当语言切换时清空缓存
resetScopeCache(scope, activeLanguage);
}
const fallbackLanguage = scope.getLanguage(activeLanguage).fallback;
const fallbackLanguage = scope.getLanguage(activeLanguage)?.fallback;
// 先在当前作用域中查找,再在全局查找
const targets = [
scope.activeFormatters,
scope.formatters[fallbackLanguage], // 如果指定了回退语言时,也在该回退语言中查找
fallbackLanguage ? scope.formatters[fallbackLanguage] : null, // 如果指定了回退语言时,也在该回退语言中查找
scope.global.formatters[activeLanguage],
scope.global.formatters["*"],
];
for (const target of targets) {
if (!target) continue;
if (isPlainObject(target.$types) &&isFunction(target.$types[dataType])) {
return (scope.$cache.typedFormatters[dataType] =
target.$types[dataType]);
}
if(target){
if (isPlainObject(target.$types) && isFunction(target.$types?.[dataType])) {
return (scope.cache.typedFormatters[dataType] = target.$types[dataType]);
}
}
}
}

View File

@ -3,8 +3,9 @@ import { deepMerge } from "flex-tools/object/deepMerge"
import {DataTypes} from "./utils"
import { EventEmitter } from "./eventemitter"
import inlineFormatters from "./formatters"
import { VoerkaI18nLanguage, VoerkaI18nScope, VoerkaI18nFormatters, VoerkI18nMessageLoader, VoerkaI18nFormatter } from "./scope"
import { VoerkaI18nScope } from "./scope"
import type { VoerkaI18nLanguage, VoerkaI18nFormatters, VoerkaI18nDefaultMessageLoader, VoerkaI18nFormatter, VoerkaI18nTypesFormatters } from "./types"
import { SupportedDateTypes } from './types';
// 默认语言配置
const defaultLanguageSettings = {
@ -45,7 +46,7 @@ export class VoerkaI18nManager extends EventEmitter{
static instance?:VoerkaI18nManager
#options?:Required<VoerkaI18nManagerOptions>
#scopes:VoerkaI18nScope[] = []
#defaultMessageLoader?:VoerkI18nMessageLoader
#defaultMessageLoader?:VoerkaI18nDefaultMessageLoader
constructor(options?:VoerkaI18nManagerOptions){
super()
if(VoerkaI18nManager.instance){
@ -115,7 +116,7 @@ export class VoerkaI18nManager extends EventEmitter{
throw new TypeError("Scope must be an instance of VoerkaI18nScope")
}
this.#scopes.push(scope)
await scope.refresh(this.activeLanguage)
return await scope.refresh(this.activeLanguage)
}
/**
*
@ -136,10 +137,10 @@ export class VoerkaI18nManager extends EventEmitter{
if(!isFunction(formatter) || typeof(name)!=="string"){
throw new TypeError("Formatter must be a function")
}
language = Array.isArray(language) ? language : (language ? language.split(",") : [])
language.forEach(lng=>{
const languages = Array.isArray(language) ? language : (language ? language.split(",") : [])
languages.forEach((lng:string)=>{
if(DataTypes.includes(name)){
this.formatters[lng].$types![name] = formatter
(this.formatters[lng].$types as VoerkaI18nTypesFormatters)[name] = formatter
}else{
this.formatters[lng][name] = formatter
}
@ -148,7 +149,7 @@ export class VoerkaI18nManager extends EventEmitter{
/**
*
*/
registerDefaultLoader(fn:VoerkI18nMessageLoader){
registerDefaultLoader(fn:VoerkaI18nDefaultMessageLoader){
if(!isFunction(fn)) throw new Error("The default loader must be a async function or promise returned")
this.#defaultMessageLoader = fn
this.refresh()

View File

@ -7,19 +7,33 @@ import { translate } from "./translate"
import { deepMerge } from "flex-tools/object/deepMerge"
import { assignObject } from "flex-tools/object/assignObject"
import type {VoerkaI18nManager } from "./manager"
import type { VoerkaI18nFormatterConfigs, VoerkaI18nFormatters, Voerkai18nIdMap, VoerkaI18nLanguage, VoerkaI18nLanguageMessages, VoerkaI18nLanguagePack, VoerkaI18nScopeCache, VoerkaI18nTranslate, VoerkI18nLoaders } from "./types"
import type {
VoerkaI18nFormatterConfigs,
VoerkaI18nDefaultMessageLoader,
VoerkaI18nFormatter,
VoerkaI18nFormatters,
Voerkai18nIdMap,
VoerkaI18nLanguage,
VoerkaI18nLanguageMessages,
VoerkaI18nLanguagePack,
VoerkaI18nScopeCache,
VoerkaI18nTranslate,
VoerkaI18nLoaders,
VoerkaI18nTypesFormatters,
VoerkaI18nLanguageFormatters
} from "./types"
export interface VoerkaI18nScopeOptions {
id?: string
debug?: boolean
languages: VoerkaI18nLanguage[] // 当前作用域支持的语言列表
defaultLanguage: string // 默认语言名称
activeLanguage: string // 当前语言名称
languages: VoerkaI18nLanguage[] // 当前作用域支持的语言列表
defaultLanguage: string // 默认语言名称
activeLanguage: string // 当前语言名称
default: VoerkaI18nLanguageMessages // 默认语言包
messages: VoerkaI18nLanguageMessages // 当前语言包
idMap: Voerkai18nIdMap // 消息id映射列表
formatters: VoerkaI18nFormatters // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
loaders: VoerkI18nLoaders; // 异步加载语言文件的函数列表
idMap: Voerkai18nIdMap // 消息id映射列表
formatters: VoerkaI18nLanguageFormatters // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
loaders: VoerkaI18nLoaders; // 异步加载语言文件的函数列表
}
export class VoerkaI18nScope {
@ -28,11 +42,11 @@ export class VoerkaI18nScope {
#refreshing:boolean = false
#patchMessages:VoerkaI18nLanguagePack = {}
#t:VoerkaI18nTranslate
#activeFormatters:VoerkaI18nFormatters ={}
#activeFormatters:VoerkaI18nLanguageFormatters = {}
#activeFormatterConfig: VoerkaI18nFormatterConfigs={}
#cache:VoerkaI18nScopeCache ={}
#messages:VoerkaI18nLanguageMessages
constructor(options:VoerkaI18nScopeOptions, callback:Function) {
#cache:VoerkaI18nScopeCache
#messages:VoerkaI18nLanguageMessages = {}
constructor(options:VoerkaI18nScopeOptions, callback:(e?:Error)=>void) {
this.#options = assignObject({
id : Date.now().toString() + parseInt(String(Math.random() * 1000)),
debug : false,
@ -50,7 +64,7 @@ export class VoerkaI18nScope {
this.#refreshing = false; // 正在加载语言包标识
// 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历
this.#cache = {
activeLanguage : null,
activeLanguage : this.#options.activeLanguage,
typedFormatters: {},
formatters : {},
};
@ -86,6 +100,7 @@ export class VoerkaI18nScope {
get activeFormatters() {return this.#activeFormatters} // 当前作用域激活的格式化器定义 {$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}
@ -100,8 +115,9 @@ export class VoerkaI18nScope {
{name: "zh",title: "中文"},
{name: "en",title: "英文"}
]
}
Object.entries(this.languages).forEach(([name,language])=>{
}
// 将en配置为默认回退语言
this.languages.forEach(language=>{
if(!language.fallback) language.fallback = "en"
})
}
@ -110,9 +126,9 @@ export class VoerkaI18nScope {
*
* @param {*} callback
*/
register(callback:Function) {
register(callback:(e?:Error)=>void) {
if (!isFunction(callback)) callback = () => {};
this.global.register(this).then(callback).catch(callback);
this.global.register(this).then(()=>callback()).catch((e)=>callback(e));
}
/**
*
@ -131,35 +147,35 @@ export class VoerkaI18nScope {
使,
asGlobal : 注册到全局
*/
registerFormatter(name:string, formatter:VoerkaI18nFormatter, { language = "*", global : asGlobal } = {}) {
registerFormatter(name:string, formatter:VoerkaI18nFormatter, {language = "*", asGlobal= true}:{ language: string | string[] | "*", asGlobal :boolean } ) {
if (!isFunction(formatter) || typeof name !== "string") {
throw new TypeError("Formatter must be a function");
}
language = Array.isArray(language) ? language: language ? language.split(","): [];
const languages = Array.isArray(language) ? language: language ? language.split(","): [];
if (asGlobal) {
this.global.registerFormatter(name, formatter, { language });
} else {
language.forEach((lng) => {
if(!(lng in this._formatters)) this._formatters[lng] = {}
languages.forEach((lng) => {
if(!(lng in this.formatters)) this.formatters[lng] = {}
if (DataTypes.includes(name)) {
this._formatters[lng].$types[name] = formatter;
(this.formatters[lng].$types as VoerkaI18nTypesFormatters)[name] = formatter
} else {
this._formatters[lng][name] = formatter;
this.formatters[lng][name] = formatter;
}
});
}
}
/**
*
* registerFormatters(={"*",zh:{...},en:{...}})
* registerFormatters(={"*",zh:{...},en:{...}},true)
* @param {*} formatters ={"*",zh:{...},en:{...}}
* registerFormatters({"*":{...},zh:{...},en:{...}})
* registerFormatters({"*":{...},zh:{...},en:{...}},true)
* @param {*} formatters ={"*":{...},zh:{...},en:{...}}
* @returns
*/
registerFormatters(formatters:VoerkaI18nFormatters,asGlobal=false) {
Object.entries(formatters).forEach(([language,fns])=>{
Object.entries(fns).forEach(([name,formatter])=>{
this.registerFormatter(name,formatter,{language,global:asGlobal})
this.registerFormatter(name,formatter,{language,asGlobal})
})
})
}
@ -174,13 +190,12 @@ export class VoerkaI18nScope {
if(formatters.global===true){
this.registerFormatters({[langName]:formatters},true)
}else if(isPlainObject(formatters.global)){
this.registerFormatters({[langName]:formatters.global},true)
this.registerFormatters({[langName]:formatters.global as any},true)
}
})
this.activeFormatters = {}
})
try {
if (newLanguage in this._formatters) {
this.#activeFormatters = this._formatters[newLanguage];
if (newLanguage in this.formatters) {
this.#activeFormatters = this.formatters[newLanguage];
} else {
if (this.debug) console.warn(`Not initialize <${newLanguage}> formatters.`);
}
@ -252,7 +267,7 @@ export class VoerkaI18nScope {
*
* @param {Function} Promise
*/
registerDefaultLoader(fn:Function) {
registerDefaultLoader(fn:VoerkaI18nDefaultMessageLoader) {
this.global.registerDefaultLoader(fn);
}
/**
@ -260,7 +275,7 @@ export class VoerkaI18nScope {
* @param {*} language
* @returns
*/
getLanguage(language:string) {
getLanguage(language:string):VoerkaI18nLanguage | undefined{
let index = this.languages.findIndex((lng) => lng.name == language);
if (index !== -1) return this.languages[index];
}

View File

@ -6,7 +6,7 @@ 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[]>
export interface VoerkaI18nLanguagePack {
@ -23,27 +23,40 @@ export interface VoerkaI18nLanguage {
}
export type VoerkaI18nFormatter = (value: string, ...args: any[]) => string
export type VoerkaI18nFormatterConfigs = Record<string, any>
export type VoerkaI18nFormatters = Record<string, ({
$types?: Record<string, VoerkaI18nFormatter>
$config?: Record<string, string>
export type VoerkaI18nFormatter = (value: string, ...args: any[]) => string
export type VoerkaI18nTypesFormatters=Record<SupportedDateTypes | string, VoerkaI18nFormatter>
export type VoerkaI18nTypesFormatterConfigs= Record<SupportedDateTypes | string, Record<string,any>>
//
export type VoerkaI18nLanguageFormatters = {
global?: true | Exclude<VoerkaI18nFormatters,'global'> // 是否全局格式化器
$types?: VoerkaI18nTypesFormatters
$config?: VoerkaI18nTypesFormatterConfigs
} & {
[DataTypeName in SupportedDateTypes]?: VoerkaI18nFormatter
} & {
[key: string]: VoerkaI18nFormatter
})> //| (() => Promise<any>)>
export type VoerkI18nLoader = () => Awaited<Promise<any>>
export interface VoerkI18nLoaders {
[key: string]: VoerkI18nLoader
}
export type VoerkI18nMessageLoader = (this:VoerkaI18nScope,newLanguage:string,scope:VoerkaI18nScope)=>Promise<VoerkaI18nLanguageMessages>
// 包括语言的{"*":{...},zh:{...},en:{...}}
// 声明格式化器
export type VoerkaI18nFormatters = Record<string,VoerkaI18nLanguageFormatters>
//
export type VoerkaI18nLoader = () => Awaited<Promise<any>>
export interface VoerkaI18nLoaders {
[key: string]: VoerkaI18nLoader
}
export type VoerkaI18nDefaultMessageLoader = (this:VoerkaI18nScope,newLanguage:string,scope:VoerkaI18nScope)=>Promise<VoerkaI18nLanguageMessages>
export interface VoerkaI18nScopeCache{
activeLanguage? : null,
typedFormatters?: {},
formatters? : {},
activeLanguage :string | null,
typedFormatters: VoerkaI18nFormatters,
formatters : VoerkaI18nFormatters,
}
export type TranslateMessageVars = number | boolean | string | Function | Date
export interface VoerkaI18nTranslate {
(message: string, ...args: TranslateMessageVars[]): string
@ -64,7 +77,7 @@ export interface VoerkaI18nSupportedLanguages {
// get messages(): VoerkaI18nLanguageMessages // 当前语言包
// get idMap(): Voerkai18nIdMap // 消息id映射列表
// get languages(): VoerkaI18nSupportedLanguages // 当前作用域支持的语言列表[{name,title,fallback}]
// get loaders(): VoerkI18nLoaders // 异步加载语言文件的函数列表
// get loaders(): VoerkaI18nLoaders // 异步加载语言文件的函数列表
// get global(): VoerkaI18nManager // 引用全局VoerkaI18n配置注册后自动引用
// get formatters(): VoerkI18nFormatters // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
// get activeFormatters(): VoerkI18nFormatters // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}

View File

@ -10,7 +10,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
//"suppressImplicitAnyIndexErrors":true,
/* Language and Environment */
"target": "ES2015", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */

7277
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff