update typescript for react

This commit is contained in:
wxzhang 2023-01-11 18:10:59 +08:00
parent 5b12aaa7f4
commit 5904297e35
23 changed files with 455 additions and 1061 deletions

View File

@ -8,8 +8,11 @@
"preview": "vite preview"
},
"dependencies": {
"@babel/runtime-corejs3": "^7.20.7",
"@voerkai18n/react": "workspace:^1.0.0",
"@voerkai18n/runtime": "workspace:^1.1.9",
"@voerkai18n/vite": "workspace:^1.0.7",
"core-js": "^3.27.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},

View File

@ -4,11 +4,11 @@ import './App.css'
import "./main"
import Banner from './components/Banner'
import LanguageConfigurator from "./components/LanguageConfigurator.jsx"
import { useVoerkaI18n } from "@voerkai18n/react";
import { VoerkaI18nProvider,useVoerkaI18n } from "@voerkai18n/react";
import { i18nScope } from "./languages"
function App() {
const { activeLanguage, changeLanguage, languages } = useVoerkaI18n();
function RootLayout(){
const { t } = useVoerkaI18n();
return (
<div className="App">
<header className="App-header">
@ -25,9 +25,18 @@ function App() {
学习VoerkaI18n
</a>
</div>
<button onClick={()=>setLanguage('en')}>ddddd</button>
</header>
</div>
)
}
function App() {
return (
<VoerkaI18nProvider scope={i18nScope}>
<RootLayout/>
</VoerkaI18nProvider>
)
}
export default App

View File

@ -1,6 +1,8 @@
import { useState } from 'react'
import { useVoerkaI18n } from '@voerkai18n/react'
import React,{ useState } from 'react'
function Banner( props ) {
const { t } = useVoerkaI18n()
return (
<div>
<h2>{t("一字一世界,一笔一乾坤,汉字是这个星球上最美的语言")}</h2>
@ -9,4 +11,4 @@ function Banner( props ) {
)
}
export default Banner
export default React.memo(Banner)

View File

@ -2,10 +2,10 @@ import { useState } from "react";
import { useVoerkaI18n } from "@voerkai18n/react"
function LanguageConfigurator(props) {
const { activeLanguage, changeLanguage, languages } = useVoerkaI18n();
const { language, changeLanguage, languages,t } = useVoerkaI18n();
return (
<div>
<div>{t("当前语言")}{ activeLanguage }</div>
<div>{t("当前语言")}{ language }</div>
<div>
{languages.map((lang) => {
return (

View File

@ -0,0 +1,113 @@
/**
格式化器用来对翻译文本内容中的插值变量进行处理
如何编写格式器请参阅官网
*/
// import { Formatter,FlexFormatter } from "./runtime"
export default {
// global : true, // 简单地设置为true,代表当前所有格式化器均注册到全局false只在当前scope生效
// global : { // 仅将里面的格式化器注册到全局
// $config:{... }
// xxxx : value => { ... },
// xxxx : (value,$config) => { ... },
// xxxx : (value,...args,$config) => { ... },
// xxxx : Formatter(value,...args,$config) => { ... },
// xxxx : FlexFormatter(value,params,$config) => { ... },
//}, // 是否注册到全局false只在当前scope生效
// 直接对内置格式化器进行配置,请参阅官网文档
// $config:{
// datetime : {
// units : ["Year","Quarter","Month","Week","Day","Hour","Minute","Second","Millisecond","Microsecond"],
// date :{
// long : 'YYYY/MM/DD HH:mm:ss',
// short : "YYYY/MM/DD",
// format : "local"
// },
// quarter : {
// long : ["First Quarter","Second Quarter","Third Quarter","Fourth Quarter"],
// short : ["Q1","Q2","Q3","Q4"],
// format : "short"
// },
// month:{
// long : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
// short : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"],
// format : "long" // 0-长名称1-短名称2-数字
// },
// weekday:{
// long : ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
// short : ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"],
// format : "long", // 0-长名称1-短名称2-数字
// },
// time : {
// long : "HH:mm:ss",
// short : "HH:mm:ss",
// format : 'local'
// },
// timeSlots : {
// slots : [12],
// lowerCases : ["am","pm"],
// upperCases : ["AM","PM"]
// },
// relativeTime : {
// units : ["seconds","minutes","hours","days","weeks","months","years"],
// now : "Now",
// before : "{value} {unit} ago",
// after : "after {value} {unit}"
// }
// },
// currency : {
// default : "{symbol}{value}{unit}",
// long : "{prefix} {symbol}{value}{unit}{suffix}",
// short : "{symbol}{value}{unit}",
// custom : "{prefix} {symbol}{value}{unit}{suffix}",
// format : "default",
// //--
// units : [""," thousands"," millions"," billions"," trillions"], //千,百万,十亿,万亿
// radix : 3, // 进制即三位一进中文是4位一进
// symbol : "$", // 符号
// prefix : "USD", // 前缀
// suffix : "", // 后缀
// division : 3, // ,分割位
// precision : 2, // 精度
// },
// number : {
// division : 3, // , 分割位3代表每3位添加一个,
// precision : 0 // 精度,即保留小数点位置,0代表不限
// },
// empty:{
// //values : [], // 可选定义空值如果想让0,''也为空值可以指定values=[0,'']
// escape : "", // 当空值时显示的备用值
// next : 'break' // 当空值时下一步的行为: break=中止;skip=跳过
// },
// error : {
// //当错误时显示的内容支持的插值变量有message=错误信息,error=错误类名,也可以是一个返回上面内容的同步函数
// escape : null, // 默认当错误时显示空内容
// next : 'break' // 当出错时下一步的行为: break=中止;skip=忽略
// },
// fileSize:{
// brief : ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"],
// whole : ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "TeraBytes", "PetaBytes", "ExaBytes", "ZetaBytes", "YottaBytes","DoggaBytes"],
// precision: 2 // 小数精度
// }
// },
// 改变特定数据类型的默认格式化器
// $types:{
// Date : dateFormatter,
// Null : value =>"",
// Undefined: value =>"",
// Error : value => "ERROR",
// Boolean : value =>value ? "True":"False",
// Number : numberFormartter
// }
// 以下可以自定义编写格式化器
// xxxx : value => { ... },
// xxxx : (value,$config) => { ... },
// xxxx : (value,...args,$config) => { ... },
// xxxx : Formatter(value,...args,$config) => { ... },
// xxxx : FlexFormatter(value,params,$config) => { ... },
}

View File

@ -0,0 +1,113 @@
/**
格式化器用来对翻译文本内容中的插值变量进行处理
如何编写格式器请参阅官网
*/
// import { Formatter,FlexFormatter } from "./runtime"
export default {
// global : true, // 简单地设置为true,代表当前所有格式化器均注册到全局false只在当前scope生效
// global : { // 仅将里面的格式化器注册到全局
// $config:{... }
// xxxx : value => { ... },
// xxxx : (value,$config) => { ... },
// xxxx : (value,...args,$config) => { ... },
// xxxx : Formatter(value,...args,$config) => { ... },
// xxxx : FlexFormatter(value,params,$config) => { ... },
//}, // 是否注册到全局false只在当前scope生效
// 直接对内置格式化器进行配置,请参阅官网文档
// $config:{
// datetime : {
// units : ["Year","Quarter","Month","Week","Day","Hour","Minute","Second","Millisecond","Microsecond"],
// date :{
// long : 'YYYY/MM/DD HH:mm:ss',
// short : "YYYY/MM/DD",
// format : "local"
// },
// quarter : {
// long : ["First Quarter","Second Quarter","Third Quarter","Fourth Quarter"],
// short : ["Q1","Q2","Q3","Q4"],
// format : "short"
// },
// month:{
// long : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
// short : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"],
// format : "long" // 0-长名称1-短名称2-数字
// },
// weekday:{
// long : ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
// short : ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"],
// format : "long", // 0-长名称1-短名称2-数字
// },
// time : {
// long : "HH:mm:ss",
// short : "HH:mm:ss",
// format : 'local'
// },
// timeSlots : {
// slots : [12],
// lowerCases : ["am","pm"],
// upperCases : ["AM","PM"]
// },
// relativeTime : {
// units : ["seconds","minutes","hours","days","weeks","months","years"],
// now : "Now",
// before : "{value} {unit} ago",
// after : "after {value} {unit}"
// }
// },
// currency : {
// default : "{symbol}{value}{unit}",
// long : "{prefix} {symbol}{value}{unit}{suffix}",
// short : "{symbol}{value}{unit}",
// custom : "{prefix} {symbol}{value}{unit}{suffix}",
// format : "default",
// //--
// units : [""," thousands"," millions"," billions"," trillions"], //千,百万,十亿,万亿
// radix : 3, // 进制即三位一进中文是4位一进
// symbol : "$", // 符号
// prefix : "USD", // 前缀
// suffix : "", // 后缀
// division : 3, // ,分割位
// precision : 2, // 精度
// },
// number : {
// division : 3, // , 分割位3代表每3位添加一个,
// precision : 0 // 精度,即保留小数点位置,0代表不限
// },
// empty:{
// //values : [], // 可选定义空值如果想让0,''也为空值可以指定values=[0,'']
// escape : "", // 当空值时显示的备用值
// next : 'break' // 当空值时下一步的行为: break=中止;skip=跳过
// },
// error : {
// //当错误时显示的内容支持的插值变量有message=错误信息,error=错误类名,也可以是一个返回上面内容的同步函数
// escape : null, // 默认当错误时显示空内容
// next : 'break' // 当出错时下一步的行为: break=中止;skip=忽略
// },
// fileSize:{
// brief : ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"],
// whole : ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "TeraBytes", "PetaBytes", "ExaBytes", "ZetaBytes", "YottaBytes","DoggaBytes"],
// precision: 2 // 小数精度
// }
// },
// 改变特定数据类型的默认格式化器
// $types:{
// Date : dateFormatter,
// Null : value =>"",
// Undefined: value =>"",
// Error : value => "ERROR",
// Boolean : value =>value ? "True":"False",
// Number : numberFormartter
// }
// 以下可以自定义编写格式化器
// xxxx : value => { ... },
// xxxx : (value,$config) => { ... },
// xxxx : (value,...args,$config) => { ... },
// xxxx : Formatter(value,...args,$config) => { ... },
// xxxx : FlexFormatter(value,params,$config) => { ... },
}

View File

@ -1,9 +1,9 @@
import messageIds from "./idMap.js"
import runtime from "./runtime.js"
const { translate,i18nScope } = runtime
import formatters from "./formatters.js"
import messageIds from "./idMap.js" // 语言ID映射文件
import runtime from "@voerkai18n/runtime"
const { translate,VoerkaI18nScope } = runtime
import defaultFormatters from "./formatters/zh"
const activeFormatters = defaultFormatters
import defaultMessages from "./zh.js"
const activeMessages = defaultMessages
@ -24,18 +24,25 @@ const scopeSettings = {
"activeLanguage": "zh",
"namespaces": {}
}
const formatters = {
'zh' : defaultFormatters,
'en' : ()=>import("./formatters/en.js")
}
// 语言包加载器
const loaders = {
"en" : ()=>import("./en.js")
}
// 语言作用域
const scope = new i18nScope({
const scope = new VoerkaI18nScope({
...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters
id : "reactapp", // 当前作用域的id自动取当前工程的package.json的name
debug : false, // 是否在控制台输出高度信息
default : defaultMessages, // 默认语言包
messages : activeMessages, // 当前语言包
idMap : messageIds, // 消息id映射列表
formatters, // 当前作用域的格式化函数列表
loaders:{
"en" : ()=>import("./en.js")
}
formatters, // 扩展自定义格式化器
loaders // 语言包加载器
})
// 翻译函数
const scopedTtranslate = translate.bind(scope)

View File

@ -1,936 +0,0 @@
/**
* 判断是否是JSON对象
* @param {*} obj
* @returns
*/
function isPlainObject$1(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) {
baseProto = Object.getPrototypeOf(baseProto);
}
return proto === baseProto;
}
function isNumber$1(value){
return !isNaN(parseInt(value))
}
/**
* 简单进行对象合并
*
* options={
* array:0 , // 数组合并策略0-替换1-合并2-去重合并
* }
*
* @param {*} toObj
* @param {*} formObj
* @returns 合并后的对象
*/
function deepMerge$1(toObj,formObj,options={}){
let results = Object.assign({},toObj);
Object.entries(formObj).forEach(([key,value])=>{
if(key in results){
if(typeof value === "object" && value !== null){
if(Array.isArray(value)){
if(options.array === 0){
results[key] = value;
}else if(options.array === 1){
results[key] = [...results[key],...value];
}else if(options.array === 2){
results[key] = [...new Set([...results[key],...value])];
}
}else {
results[key] = deepMerge$1(results[key],value,options);
}
}else {
results[key] = value;
}
}else {
results[key] = value;
}
});
return results
}
/**
* 获取指定变量类型名称
* getDataTypeName(1) == Number
* getDataTypeName("") == String
* getDataTypeName(null) == Null
* getDataTypeName(undefined) == Undefined
* getDataTypeName(new Date()) == Date
* getDataTypeName(new Error()) == Error
*
* @param {*} v
* @returns
*/
function getDataTypeName$1(v){
if (v === null) return 'Null'
if (v === undefined) return 'Undefined'
if(typeof(v)==="function") return "Function"
return v.constructor && v.constructor.name;
}
var utils ={
isPlainObject: isPlainObject$1,
isNumber: isNumber$1,
deepMerge: deepMerge$1,
getDataTypeName: getDataTypeName$1
};
/**
*
* 简单的事件触发器
*
*/
var eventemitter = class EventEmitter{
constructor(){
this._callbacks = [];
}
on(callback){
if(this._callbacks.includes(callback)) return
this._callbacks.push(callback);
}
off(callback){
for(let i=0;i<this._callbacks.length;i++){
if(this._callbacks[i]===callback ){
this._callbacks.splice(i,1);
}
}
}
offAll(){
this._callbacks = [];
}
async emit(...args){
if(Promise.allSettled){
await Promise.allSettled(this._callbacks.map(cb=>cb(...args)));
}else {
await Promise.all(this._callbacks.map(cb=>cb(...args)));
}
}
};
var scope = class i18nScope {
constructor(options={},callback){
// 每个作用域都有一个唯一的id
this._id = options.id || (new Date().getTime().toString()+parseInt(Math.random()*1000));
this._languages = options.languages; // 当前作用域的语言列表
this._defaultLanguage = options.defaultLanguage || "zh"; // 默认语言名称
this._activeLanguage = options.activeLanguage; // 当前语言名称
this._default = options.default; // 默认语言包
this._messages = options.messages; // 当前语言包
this._idMap = options.idMap; // 消息id映射列表
this._formatters = options.formatters; // 当前作用域的格式化函数列表
this._loaders = options.loaders; // 异步加载语言文件的函数列表
this._global = null; // 引用全局VoerkaI18n配置注册后自动引用
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
this.$cache={
activeLanguage : null,
typedFormatters: {},
formatters : {},
};
// 如果不存在全局VoerkaI18n实例说明当前Scope是唯一或第一个加载的作用域
// 则使用当前作用域来初始化全局VoerkaI18n实例
if(!globalThis.VoerkaI18n){
const { I18nManager } = runtime;
globalThis.VoerkaI18n = new I18nManager({
defaultLanguage: this.defaultLanguage,
activeLanguage : this.activeLanguage,
languages: options.languages,
});
}
this.global = globalThis.VoerkaI18n;
// 正在加载语言包标识
this._loading=false;
// 在全局注册作用域
this.register(callback);
}
// 作用域
get id(){return this._id}
// 默认语言名称
get defaultLanguage(){return this._defaultLanguage}
// 默认语言名称
get activeLanguage(){return this._activeLanguage}
// 默认语言包
get default(){return this._default}
// 当前语言包
get messages(){return this._messages}
// 消息id映射列表
get idMap(){return this._idMap}
// 当前作用域的格式化函数列表
get formatters(){return this._formatters}
// 异步加载语言文件的函数列表
get loaders(){return this._loaders}
// 引用全局VoerkaI18n配置注册后自动引用
get global(){return this._global}
set global(value){this._global = value;}
/**
* 在全局注册作用域
* @param {*} callback 当注册
*/
register(callback){
if(!typeof(callback)==="function") callback = ()=>{};
this.global.register(this).then(callback).catch(callback);
}
registerFormatter(name,formatter,{language="*"}={}){
if(!typeof(formatter)==="function" || typeof(name)!=="string"){
throw new TypeError("Formatter must be a function")
}
if(DataTypes.includes(name)){
this.formatters[language].$types[name] = formatter;
}else {
this.formatters[language][name] = formatter;
}
}
/**
* 回退到默认语言
*/
_fallback(){
this._messages = this._default;
this._activeLanguage = this.defaultLanguage;
}
/**
* 刷新当前语言包
* @param {*} newLanguage
*/
async refresh(newLanguage){
this._loading = Promise.resolve();
if(!newLanguage) newLanguage = this.activeLanguage;
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
if(newLanguage === this.defaultLanguage){
this._messages = this._default;
return
}
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
const loader = this.loaders[newLanguage];
if(typeof(loader) === "function"){
try{
this._messages = (await loader()).default;
this._activeLanguage = newLanguage;
}catch(e){
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
this._fallback();
}
}else {
this._fallback();
}
}
// 以下方法引用全局VoerkaI18n实例的方法
get on(){return this.global.on.bind(this.global)}
get off(){return this.global.off.bind(this.global)}
get offAll(){return this.global.offAll.bind(this.global)}
get change(){
return this.global.change.bind(this.global)
}
};
/**
* 内置的格式化器
*
*/
/**
* 字典格式化器
* 根据输入data的值返回后续参数匹配的结果
* dict(data,<value1>,<result1>,<value2>,<result1>,<value3>,<result1>,...)
*
*
* dict(1,1,"one",2,"two",3,"three"4,"four") == "one"
* dict(2,1,"one",2,"two",3,"three"4,"four") == "two"
* dict(3,1,"one",2,"two",3,"three"4,"four") == "three"
* dict(4,1,"one",2,"two",3,"three"4,"four") == "four"
* // 无匹配时返回原始值
* dict(5,1,"one",2,"two",3,"three"4,"four") == 5
* // 无匹配时并且后续参数个数是奇数,则返回最后一个参数
* dict(5,1,"one",2,"two",3,"three"4,"four","more") == "more"
*
* 在翻译中使用
* I have { value | dict(1,"one",2,"two",3,"three",4,"four")} apples
*
* @param {*} value
* @param {...any} args
* @returns
*/
function dict(value,...args){
for(let i=0;i<args.length;i+=2){
if(args[i]===value){
return args[i+1]
}
}
if(args.length >0 && (args.length % 2!==0)) return args[args.length-1]
return value
}
var formatters = {
"*":{
$types:{
Date:(value)=>value.toLocaleString()
},
time:(value)=> value.toLocaleTimeString(),
shorttime:(value)=> value.toLocaleTimeString(),
date: (value)=> value.toLocaleDateString(),
dict, //字典格式化器
},
zh:{
$types:{
Date:(value)=> `${value.getFullYear()}${value.getMonth()+1}${value.getDate()}${value.getHours()}${value.getMinutes()}${value.getSeconds()}`
},
shortime:(value)=> value.toLocaleTimeString(),
time:(value)=>`${value.getHours()}${value.getMinutes()}${value.getSeconds()}`,
date: (value)=> `${value.getFullYear()}${value.getMonth()+1}${value.getDate()}`,
shortdate: (value)=> `${value.getFullYear()}-${value.getMonth()+1}-${value.getDate()}`,
currency:(value)=>`${value}`,
},
en:{
currency:(value)=>{
return `$${value}`
}
}
};
const { getDataTypeName,isNumber,isPlainObject,deepMerge } = utils;
const EventEmitter = eventemitter;
const i18nScope = scope;
let inlineFormatters = formatters; // 内置格式化器
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
// 不支持参数: let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*\s*)*)\s*\}/g
// 支持参数: { var | formatter(x,x,..) | formatter }
let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g;
/**
* 考虑到通过正则表达式进行插件的替换可能较慢因此提供一个简单方法来过滤掉那些
* 不需要进行插值处理的字符串
* 原理很简单就是判断一下是否同时具有{}字符如果有则认为可能有插值变量如果没有则一定没有插件变量则就不需要进行正则匹配
* 从而可以减少不要的正则匹配
* 注意该方法只能快速判断一个字符串不包括插值变量
* @param {*} str
* @returns {boolean} true=可能包含插值变量,
*/
function hasInterpolation(str){
return str.includes("{") && str.includes("}")
}
const DataTypes$1 = ["String","Number","Boolean","Object","Array","Function","Error","Symbol","RegExp","Date","Null","Undefined","Set","Map","WeakSet","WeakMap"];
/**
通过正则表达式对原始文本内容进行解析匹配后得到的
formatters="| aaa(1,1) | bbb "
需要统一解析为
[
[aaa,[1,1]], // [formatter'name,[args,...]]
[bbb,[]],
]
formatters="| aaa(1,1,"dddd") | bbb "
目前对参数采用简单的split(",")来解析因为无法正确解析aaa(1,1,"dd,,dd")形式的参数
在此场景下基本够用了如果需要支持更复杂的参数解析可以后续考虑使用正则表达式来解析
@returns [[<formatterName>,[<arg>,<arg>,...]]]
*/
function parseFormatters(formatters){
if(!formatters) return []
// 1. 先解析为 ["aaa()","bbb"]形式
let result = formatters.trim().substr(1).trim().split("|").map(r=>r.trim());
// 2. 解析格式化器参数
return result.map(formatter=>{
let firstIndex = formatter.indexOf("(");
let lastIndex = formatter.lastIndexOf(")");
if(firstIndex!==-1 && lastIndex!==-1){ // 带参数的格式化器
const argsContent = formatter.substr(firstIndex+1,lastIndex-firstIndex-1).trim();
let args = argsContent=="" ? [] : argsContent.split(",").map(arg=>{
arg = arg.trim();
if(!isNaN(parseInt(arg))){
return parseInt(arg) // 数字
}else if((arg.startsWith('\"') && arg.endsWith('\"')) || (arg.startsWith('\'') && arg.endsWith('\'')) ){
return arg.substr(1,arg.length-2) // 字符串
}else if(arg.toLowerCase()==="true" || arg.toLowerCase()==="false"){
return arg.toLowerCase()==="true" // 布尔值
}else if((arg.startsWith('{') && arg.endsWith('}')) || (arg.startsWith('[') && arg.endsWith(']'))){
try{
return JSON.parse(arg)
}catch(e){
return String(arg)
}
}else {
return String(arg)
}
});
return [formatter.substr(0,firstIndex),args]
}else {// 不带参数的格式化器
return [formatter,[]]
}
})
}
/**
* 提取字符串中的插值变量
* // [
// {
name:<变量名称>,formatters:[{name:<格式化器名称>,args:[<参数>,<参数>,....]]],<匹配字符串>],
// ....
//
* @param {*} str
* @param {*} isFull =true 保留所有插值变量 =false 进行去重
* @returns {Array}
* [
* {
* name:"<变量名称>",
* formatters:[
* {name:"<格式化器名称>",args:[<参数>,<参数>,....]},
* {name:"<格式化器名称>",args:[<参数>,<参数>,....]},
* ],
* match:"<匹配字符串>"
* },
* ...
* ]
*/
function getInterpolatedVars(str){
let vars = [];
forEachInterpolatedVars(str,(varName,formatters,match)=>{
let varItem = {
name:varName,
formatters:formatters.map(([formatter,args])=>{
return {
name:formatter,
args:args
}
}),
match:match
};
if(vars.findIndex(varDef=>((varDef.name===varItem.name) && (varItem.formatters.toString() == varDef.formatters.toString())))===-1){
vars.push(varItem);
}
return ""
});
return vars
}
/**
* 遍历str中的所有插值变量传递给callback将callback返回的结果替换到str中对应的位置
* @param {*} str
* @param {Function(<变量名称>,[formatters],match[0])} callback
* @returns 返回替换后的字符串
*/
function forEachInterpolatedVars(str,callback,options={}){
let result=str, match;
let opts = Object.assign({
replaceAll:true, // 是否替换所有插值变量当使用命名插值时应置为true当使用位置插值时应置为false
},options);
varWithPipeRegexp.lastIndex=0;
while ((match = varWithPipeRegexp.exec(result)) !== null) {
const varname = match.groups.varname || "";
// 解析格式化器和参数 = [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]]
const formatters = parseFormatters(match.groups.formatters);
if(typeof(callback)==="function"){
try{
if(opts.replaceAll){
result=result.replaceAll(match[0],callback(varname,formatters,match[0]));
}else {
result=result.replace(match[0],callback(varname,formatters,match[0]));
}
}catch{// callback函数可能会抛出异常如果抛出异常则中断匹配过程
break
}
}
varWithPipeRegexp.lastIndex=0;
}
return result
}
function resetScopeCache(scope,activeLanguage=null){
scope.$cache = {activeLanguage,typedFormatters:{},formatters:{}};
}
/**
* 取得指定数据类型的默认格式化器
*
* 可以为每一个数据类型指定一个默认的格式化器,当传入插值变量时
* 会自动调用该格式化器来对值进行格式化转换
const formatters = {
"*":{
$types:{...} // 在所有语言下只作用于特定数据类型的格式化器
}, // 在所有语言下生效的格式化器
zh:{
$types:{
[数据类型]:(value)=>{...},
},
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
},
}
* @param {*} scope
* @param {*} activeLanguage
* @param {*} dataType 数字类型
* @returns {Function} 格式化函数
*/
function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){
if(!scope.$cache) resetScopeCache(scope);
if(scope.$cache.activeLanguage === activeLanguage) {
if(dataType in scope.$cache.typedFormatters) return scope.$cache.typedFormatters[dataType]
}else {// 当语言切换时清空缓存
resetScopeCache(scope,activeLanguage);
}
// 先在当前作用域中查找,再在全局查找
const targets = [scope.formatters,scope.global.formatters];
for(const target of targets){
if(!target) continue
// 优先在当前语言的$types中查找
if((activeLanguage in target) && isPlainObject(target[activeLanguage].$types)){
let formatters = target[activeLanguage].$types;
if(dataType in formatters && typeof(formatters[dataType])==="function"){
return scope.$cache.typedFormatters[dataType] = formatters[dataType]
}
}
// 在所有语言的$types中查找
if(("*" in target) && isPlainObject(target["*"].$types)){
let formatters = target["*"].$types;
if(dataType in formatters && typeof(formatters[dataType])==="function"){
return scope.$cache.typedFormatters[dataType] = formatters[dataType]
}
}
}
}
/**
* 获取指定名称的格式化器函数
* @param {*} scope
* @param {*} activeLanguage
* @param {*} name 格式化器名称
* @returns {Function} 格式化函数
*/
function getFormatter(scope,activeLanguage,name){
// 缓存格式化器引用,避免重复检索
if(!scope.$cache) resetScopeCache(scope);
if(scope.$cache.activeLanguage === activeLanguage) {
if(name in scope.$cache.formatters) return scope.$cache.formatters[name]
}else {// 当语言切换时清空缓存
resetScopeCache(scope,activeLanguage);
}
// 先在当前作用域中查找,再在全局查找
const targets = [scope.formatters,scope.global.formatters];
for(const target of targets){
// 优先在当前语言查找
if(activeLanguage in target){
let formatters = target[activeLanguage] || {};
if((name in formatters) && typeof(formatters[name])==="function") return scope.$cache.formatters[name] = formatters[name]
}
// 在所有语言的$types中查找
let formatters = target["*"] || {};
if((name in formatters) && typeof(formatters[name])==="function") return scope.$cache.formatters[name] = formatters[name]
}
}
/**
* 执行格式化器并返回结果
* @param {*} value
* @param {*} formatters 多个格式化器顺序执行前一个输出作为下一个格式化器的输入
*/
function executeFormatter(value,formatters){
if(formatters.length===0) return value
let result = value;
try{
for(let formatter of formatters){
if(typeof(formatter) === "function") {
result = formatter(result);
}else {// 如果碰到无效的格式化器,则跳过过续的格式化器
return result
}
}
}catch(e){
console.error(`Error while execute i18n formatter for ${value}: ${e.message} ` );
}
return result
}
/**
* [[格式化器名称,[参数,参数,...]][格式化器名称,[参数,参数,...]]]格式化器转化为
*
*
*
* @param {*} scope
* @param {*} activeLanguage
* @param {*} formatters
*/
function buildFormatters(scope,activeLanguage,formatters){
let results = [];
for(let formatter of formatters){
if(formatter[0]){
const func = getFormatter(scope,activeLanguage,formatter[0]);
if(typeof(func)==="function"){
results.push((v)=>{
return func(v,...formatter[1])
});
}else {
// 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用
// 比如padStart格式化器是String的原型方法不需要配置就可以直接作为格式化器调用
results.push((v)=>{
if(typeof(v[formatter[0]])==="function"){
return v[formatter[0]].call(v,...formatter[1])
}else {
return v
}
});
}
}
}
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);
// 2. 查找每种数据类型默认格式化器,并添加到formatters最前面默认数据类型格式化器优先级最高
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value));
if(defaultFormatter){
formatterFuncs.splice(0,0,defaultFormatter);
}
// 3. 执行格式化器
value = executeFormatter(value,formatterFuncs);
return value
}
/**
* 字符串可以进行变量插值替换
* replaceInterpolatedVars("<模板字符串>",{变量名称:变量值,变量名称:变量值,...})
* replaceInterpolatedVars("<模板字符串>",[变量值,变量值,...])
* replaceInterpolatedVars("<模板字符串>",变量值,变量值,...])
*
- 当只有两个参数并且第2个参数是{}将第2个参数视为命名变量的字典
replaceInterpolatedVars("this is {a}+{b},{a:1,b:2}) --> this is 1+2
- 当只有两个参数并且第2个参数是[]将第2个参数视为位置参数
replaceInterpolatedVars"this is {}+{}",[1,2]) --> this is 1+2
- 普通位置参数替换
replaceInterpolatedVars("this is {a}+{b}",1,2) --> this is 1+2
-
this == scope == { formatters: {}, ... }
* @param {*} template
* @returns
*/
function replaceInterpolatedVars(template,...args) {
const scope = this;
// 当前激活语言
const activeLanguage = scope.global.activeLanguage;
// 没有变量插值则的返回原字符串
if(args.length===0 || !hasInterpolation(template)) return template
// ****************************变量插值****************************
if(args.length===1 && isPlainObject(args[0])){
// 读取模板字符串中的插值变量列表
// [[var1,[formatter,formatter,...],match],[var2,[formatter,formatter,...],match],...}
let varValues = args[0];
return forEachInterpolatedVars(template,(varname,formatters)=>{
let value = (varname in varValues) ? varValues[varname] : '';
return getFormattedValue(scope,activeLanguage,formatters,value)
})
}else {
// ****************************位置插值****************************
// 如果只有一个Array参数则认为是位置变量列表进行展开
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args;
if(params.length===0) return template // 没有变量则不需要进行插值处理,返回原字符串
let i = 0;
return forEachInterpolatedVars(template,(varname,formatters)=>{
if(params.length>i){
return getFormattedValue(scope,activeLanguage,formatters,params[i++])
}else {
throw new Error() // 抛出异常,停止插值处理
}
},{replaceAll:false})
}
}
// 默认语言配置
const defaultLanguageSettings = {
defaultLanguage: "zh",
activeLanguage: "zh",
languages:[
{name:"zh",title:"中文",default:true},
{name:"en",title:"英文"}
],
formatters:inlineFormatters
};
function isMessageId(content){
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
}
}
function escape(str){
return str.replaceAll(/\\(?![trnbvf'"]{1})/g,"\\\\")
.replaceAll("\t","\\t")
.replaceAll("\n","\\n")
.replaceAll("\b","\\b")
.replaceAll("\r","\\r")
.replaceAll("\f","\\f")
.replaceAll("\'","\\'")
.replaceAll('\"','\\"')
.replaceAll('\v','\\v')
}
function unescape(str){
return str
.replaceAll("\\t","\t")
.replaceAll("\\n","\n")
.replaceAll("\\b","\b")
.replaceAll("\\r","\r")
.replaceAll("\\f","\f")
.replaceAll("\\'","\'")
.replaceAll('\\"','\"')
.replaceAll('\\v','\v')
.replaceAll(/\\\\(?![trnbvf'"]{1})/g,"\\")
}
/**
* 翻译函数
*
* translate("要翻译的文本内容") 如果默认语言是中文则不会进行翻译直接返回
* translate("I am {} {}","man") == I am man 位置插值
* translate("I am {p}",{p:"man"}) 字典插值
* translate("total {$count} items", {$count:1}) //复数形式
* translate("total {} {} {} items",a,b,c) // 位置变量插值
*
* this===scope 当前绑定的scope
*
*/
function translate(message) {
const scope = this;
const activeLanguage = scope.global.activeLanguage;
let content = message;
let vars=[]; // 插值变量列表
let pluralVars= []; // 复数变量
let pluraValue = null; // 复数值
if(!typeof(message)==="string") return message
try{
// 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用
if(arguments.length === 2 && isPlainObject(arguments[1])){
Object.entries(arguments[1]).forEach(([name,value])=>{
if(typeof(value)==="function"){
try{
vars[name] = value();
}catch(e){
vars[name] = value;
}
}
// 以$开头的视为复数变量
if(name.startsWith("$") && typeof(vars[name])==="number") pluralVars.push(name);
});
vars = [arguments[1]];
}else if(arguments.length >= 2){
vars = [...arguments].splice(1).map((arg,index)=>{
try{
arg = typeof(arg)==="function" ? arg() : arg;
// 位置参数中以第一个数值变量为复数变量
if(isNumber(arg)) pluraValue = parseInt(arg);
}catch(e){ }
return arg
});
}
// 3. 取得翻译文本模板字符串
if(activeLanguage === scope.defaultLanguage){
// 2.1 从默认语言中取得翻译文本模板字符串
// 如果当前语言就是默认语言,不需要查询加载,只需要做插值变换即可
// 当源文件运用了babel插件后会将原始文本内容转换为msgId
// 如果是msgId则从scope.default中读取,scope.default=默认语言包={<id>:<message>}
if(isMessageId(content)){
content = scope.default[content] || message;
}
}else {
// 2.2 从当前语言包中取得翻译文本模板字符串
// 如果没有启用babel插件将源文本转换为msgId需要先将文本内容转换为msgId
// JSON.stringify在进行转换时会将\t\n\r转换为\\t\\n\\r,这样在进行匹配时就出错
let msgId = isMessageId(content) ? content : scope.idMap[escape(content)];
content = scope.messages[msgId] || content;
content = Array.isArray(content) ? content.map(v=>unescape(v)) : unescape(content);
}
// 2. 处理复数
// 经过上面的处理content可能是字符串或者数组
// content = "原始文本内容" || 复数形式["原始文本内容","原始文本内容"....]
// 如果是数组说明要启用复数机制,需要根据插值变量中的某个变量来判断复数形式
if(Array.isArray(content) && content.length>0){
// 如果存在复数命名变量,只取第一个复数变量
if(pluraValue!==null){ // 启用的是位置插值,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){
return content // 出错则返回原始文本
}
}
/**
* 多语言管理类
*
* 当导入编译后的多语言文件时(import("./languages"))会自动生成全局实例VoerkaI18n
*
* VoerkaI18n.languages // 返回支持的语言列表
* VoerkaI18n.defaultLanguage // 默认语言
* VoerkaI18n.language // 当前语言
* VoerkaI18n.change(language) // 切换到新的语言
*
*
* VoerkaI18n.on("change",(language)=>{}) // 注册语言切换事件
* VoerkaI18n.off("change",(language)=>{})
*
* */
class I18nManager extends EventEmitter{
constructor(settings={}){
super();
if(I18nManager.instance!=null){
return I18nManager.instance;
}
I18nManager.instance = this;
this._settings = deepMerge(defaultLanguageSettings,settings);
this._scopes=[];
return I18nManager.instance;
}
get settings(){ return this._settings }
get scopes(){ return this._scopes }
// 当前激活语言
get activeLanguage(){ return this._settings.activeLanguage}
// 默认语言
get defaultLanguage(){ return this._settings.defaultLanguage}
// 支持的语言列表
get languages(){ return this._settings.languages}
// 内置格式化器
get formatters(){ return inlineFormatters }
/**
* 切换语言
*/
async change(value){
value=value.trim();
if(this.languages.findIndex(lang=>lang.name === value)!==-1){
// 通知所有作用域刷新到对应的语言包
await this._refreshScopes(value);
this._settings.activeLanguage = value;
/// 触发语言切换事件
await this.emit(value);
}else {
throw new Error("Not supported language:"+value)
}
}
/**
* 当切换语言时调用此方法来加载更新语言包
* @param {*} newLanguage
*/
async _refreshScopes(newLanguage){
// 并发执行所有作用域语言包的加载
try{
const scopeRefreshers = this._scopes.map(scope=>{
return scope.refresh(newLanguage)
});
if(Promise.allSettled){
await Promise.allSettled(scopeRefreshers);
}else {
await Promise.all(scopeRefreshers);
}
}catch(e){
console.warn("Error while refreshing i18n scopes:",e.message);
}
}
/**
*
* 注册一个新的作用域
*
* 每一个库均对应一个作用域每个作用域可以有多个语言包且对应一个翻译函数
* 除了默认语言外其他语言采用动态加载的方式
*
* @param {*} scope
*/
async register(scope){
if(!(scope instanceof i18nScope)){
throw new TypeError("Scope must be an instance of I18nScope")
}
this._scopes.push(scope);
await scope.refresh(this.activeLanguage);
}
/**
* 注册全局格式化器
* 格式化器是一个简单的同步函数value=>{...}用来对输入进行格式化后返回结果
*
* registerFormatters(name,value=>{...}) // 适用于所有语言
* registerFormatters(name,value=>{...},{langauge:"zh"}) // 适用于cn语言
* registerFormatters(name,value=>{...},{langauge:"en"}) // 适用于en语言
* @param {*} formatters
*/
registerFormatter(name,formatter,{language="*"}={}){
if(!typeof(formatter)==="function" || typeof(name)!=="string"){
throw new TypeError("Formatter must be a function")
}
if(DataTypes$1.includes(name)){
this.formatters[language].$types[name] = formatter;
}else {
this.formatters[language][name] = formatter;
}
}
}
var runtime ={
getInterpolatedVars,
replaceInterpolatedVars,
I18nManager,
translate,
i18nScope,
defaultLanguageSettings,
getDataTypeName,
isNumber,
isPlainObject
};
export { runtime as default };

View File

@ -1,3 +1,2 @@
console.log(t("中华民族伟大复兴"))

View File

@ -2,8 +2,7 @@ import React,{ } from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import "./languages"
// import "./languages"
ReactDOM.render((
<React.StrictMode>

View File

@ -7,7 +7,7 @@ import Voerkai18nPlugin from "@voerkai18n/vite"
export default defineConfig({
plugins: [
Inspect(), // localhost:3000/__inspect/
Voerkai18nPlugin({ debug: true }),
// Voerkai18nPlugin({ debug: true }),
react()
]
})

View File

@ -1,6 +1,8 @@
{{if moduleType === "esm"}}
import messageIds from "./idMap.js" // 语言ID映射文件
import { translate,VoerkaI18nScope } from "@voerkai18n/runtime"
import runtime from "@voerkai18n/runtime"
const { translate,VoerkaI18nScope } = runtime
import defaultFormatters from "./formatters/{{defaultLanguage}}"
{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}import activeFormatters from "@voerkai18n/runtime/formatters/{{activeLanguage}}.js"{{/if}}
import defaultMessages from "./{{defaultLanguage}}.js"
{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages{{else}}import activeMessages from "./{{activeLanguage}}.js"{{/if}}

View File

@ -1,5 +1,6 @@
import messageIds from "./idMap" // 语言ID映射文件
import { translate,VoerkaI18nScope } from "@voerkai18n/runtime"
import runtime from "@voerkai18n/runtime"
const { translate,VoerkaI18nScope } = runtime
import defaultFormatters from "./formatters/{{defaultLanguage}}" // 默认语言格式化器
{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}import activeFormatters from "@voerkai18n/runtime/formatters/{{activeLanguage}}"{{/if}}
import defaultMessages from "./{{defaultLanguage}}"

View File

@ -1,7 +1,14 @@
import type {VoerkaI18nSupportedLanguages,VoerkaI18nTranslate,VoerkaI18nScope} from "@voerkai18n/runtime"
import type React from "react"
export var useVoerkaI18n:()=>{
activeLanguage:string
export type useVoerkaI18n = ()=>{
language:string
changeLanguage:(newLanguage:string)=>Promise<void>
languages:VoerkaI18nSupportedLanguages
languages:VoerkaI18nSupportedLanguages,
t:VoerkaI18nTranslate
}
export type VoerkaI18nProvider = React.FC<React.PropsWithChildren & {
scope:VoerkaI18nScope
}>

View File

@ -1,39 +0,0 @@
import React, { useState, useEffect } from 'react';
/**
* MyComponent(){
* const { activeLanguage, changeLanguage } = useVoerkaI18n()
*
*
* }
*
*
*/
export function useVoerkaI18n() {
if(!globalThis.VoerkaI18n){
console.warn("useI18nContext is not provided, use default i18nContext")
}
const [activeLanguage, setLanguage ] = useState(VoerkaI18n.activeLanguage);
async function changeLanguage(newLanguage) {
return VoerkaI18n.change(newLanguage)
}
useEffect(() => {
function onChangeLanguage(newLanguage) {
setLanguage(newLanguage)
}
VoerkaI18n.on(onChangeLanguage)
return () => {
VoerkaI18n.off(onChangeLanguage)
};
});
return {
activeLanguage,
changeLanguage,
languages:VoerkaI18n.languages,
}
}

84
packages/react/index.jsx Normal file
View File

@ -0,0 +1,84 @@
import React, { useState, useEffect,useContext,useCallback} from 'react';
export const VoerkaI18nContext = React.createContext({
languages:null,
language:'zh',
changeLanguage:() => {},
t:()=>{}
})
VoerkaI18nContext.displayName = 'VoerkaI18nProvider'
export function VoerkaI18nProvider(props){
const { scope } = props
const [language, setLanguage ] = useState(VoerkaI18n.activeLanguage);
useEffect(() => {
function onChangeLanguage(newLanguage) {
setLanguage(newLanguage)
}
VoerkaI18n.on(onChangeLanguage)
return () => {
VoerkaI18n.off(onChangeLanguage)
};
});
const changeLanguage = useCallback((newLanguage) => {
VoerkaI18n.change(newLanguage).then((lng) => {
setLanguage(lng)
})
},[language])
return (
<VoerkaI18nContext.Provider value={{
language,
changeLanguage,
languages:VoerkaI18n.languages,
t:scope.t
}}>
{props.children}
</VoerkaI18nContext.Provider>
)
}
export function useVoerkaI18n() {
return useContext(VoerkaI18nContext)
}
/**
* MyComponent(){
* const { activeLanguage, changeLanguage } = useVoerkaI18n()
*
*
* }
*
*
*/
// export function useVoerkaI18n() {
// if(!globalThis.VoerkaI18n){
// console.warn("useI18nContext is not provided, use default i18nContext")
// }
// const [activeLanguage, setLanguage ] = useState(VoerkaI18n.activeLanguage);
// async function changeLanguage(newLanguage) {
// return VoerkaI18n.change(newLanguage)
// }
// useEffect(() => {
// function onChangeLanguage(newLanguage) {
// setLanguage(newLanguage)
// }
// VoerkaI18n.on(onChangeLanguage)
// return () => {
// VoerkaI18n.off(onChangeLanguage)
// };
// });
// return {
// activeLanguage,
// changeLanguage,
// languages:VoerkaI18n.languages,
// }
// }

View File

@ -2,7 +2,7 @@
"name": "@voerkai18n/react",
"version": "1.0.5",
"description": "React支持,提供语言切换等功能",
"main": "index.js",
"main": "index.jsx",
"typings": "index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
@ -11,10 +11,13 @@
"author": "wxzhang",
"license": "ISC",
"dependencies": {
"react": "^17.0.2"
"@voerkai18n/runtime": "latest"
},
"devDependencies": {
"@voerkai18n/autopublish": "workspace:^1.0.2"
},
"peerDependencies": {
"react": "^17.0.2"
},
"lastPublish": "2023-01-10T17:31:25+08:00"
}

View File

@ -38,9 +38,7 @@ declare global {
fallback?: string
}
export interface VoerkaI18nSupportedLanguages {
[key: string]: VoerkaI18nLanguage
}
export type VoerkI18nFormatter = (value: string, ...args: any[]) => string
export type VoerkI18nFormatterConfigs = Record<string, any>
@ -73,7 +71,13 @@ declare global {
export var VoerkaI18n: VoerkaI18nManager
}
export interface VoerkaI18nTranslate {
(message: string, ...args: (string | Function)[]): string
(message: string, vars?: Record<string, any>): string
}
export interface VoerkaI18nSupportedLanguages {
[key: string]: VoerkaI18nLanguage
}
export class VoerkaI18nScope {
constructor(options: VoerkaVoerkaI18nScopeOptions, callback?: Function)
get id(): string // 作用域唯一id
@ -89,7 +93,8 @@ export class VoerkaI18nScope {
get formatters(): VoerkI18nFormatters // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
get activeFormatters(): VoerkI18nFormatters // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}
get activeFormatterConfig(): VoerkI18nFormatterConfigs // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数
get t(): VoerkaI18nTranslate
get translate(): VoerkaI18nTranslate
/**
*
* @param {*} callback

View File

@ -66,6 +66,7 @@ const defaultLanguageSettings = {
await this._refreshScopes(language) // 通知所有作用域刷新到对应的语言包
this._settings.activeLanguage = language
await this.emit(language) // 触发语言切换事件
return language
}else{
throw new Error("Not supported language:"+language)
}

View File

@ -12,8 +12,7 @@
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c",
"release": "pnpm autopublish"
"build": "rollup -c"
},
"exports": {
"import": "./dist/index.esm.js",
@ -21,6 +20,12 @@
},
"author": "wxzhang",
"license": "MIT",
"dependencies": {
"@babel/runtime-corejs3":"latest",
"core-js":"3.21",
"@babel/runtime":"^7.17.8"
},
"devDependencies": {
"@babel/cli": "^7.17.6",
"@babel/core": "^7.17.5",
@ -30,7 +35,6 @@
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-commonjs": "^21.0.2",
"@rollup/plugin-node-resolve": "^13.1.3",
"@voerkai18n/autopublish": "workspace:^1.0.2",
"deepmerge": "^4.2.2",
"jest": "^27.5.1",
"rollup": "^2.69.0",

View File

@ -12,6 +12,8 @@ export default [
{
file: 'dist/index.esm.js',
format:"esm",
exports:"default",
esModule:"if-default-prop",
sourcemap:true
},
{
@ -29,28 +31,8 @@ export default [
exclude: 'node_modules/**'
}),
clear({targets:["dist"]}),
terser()
// terser()
],
external:["@babel/runtime"]
}
// {
// input: './index.js',
// output: [
// {
// file: 'dist/runtime.cjs',
// exports:"auto",
// format:"cjs",
// sourcemap:true
// },
// {
// file: 'dist/runtime.mjs',
// exports:"default",
// format:"esm",
// sourcemap:true
// }
// ],
// plugins:[
// commonjs()
// ]
// }
]

View File

@ -1,4 +1,5 @@
const { DataTypes,isPlainObject, isFunction, getByPath, deepMixin,deepClone } = require("./utils");
const { translate } = require("./translate")
module.exports = class VoerkaI18nScope {
constructor(options = {}, callback) {
@ -32,6 +33,7 @@ module.exports = class VoerkaI18nScope {
languages : options.languages,
});
}
this._t = translate.bind(this)
this._global = globalThis.VoerkaI18n;
this._initFormatters(this.activeLanguage) // 初始化活动的格式化器
this._mergePatchedMessages(); // 从本地缓存中读取并合并补丁语言包
@ -51,7 +53,8 @@ module.exports = class VoerkaI18nScope {
get formatters() { return this._formatters;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
get activeFormatters() {return this._activeFormatters} // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}
get activeFormatterConfig(){return this._activeFormatterConfig} // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数
get translate(){return this._t}
get t(){return this._t}
/**
* 对输入的语言配置进行处理
* - 将en配置为默认回退语言

52
pnpm-lock.yaml generated
View File

@ -65,24 +65,30 @@ importers:
packages/apps/app:
specifiers:
'@voerkai18n/cli': workspace:^1.0.6
'@voerkai18n/runtime': ^1.0.0
'@voerkai18n/runtime': workspace:^1.0.0
dependencies:
'@voerkai18n/cli': link:../../cli
'@voerkai18n/runtime': link:../../runtime
packages/apps/reactapp:
specifiers:
'@babel/runtime-corejs3': ^7.20.7
'@vitejs/plugin-react': ^1.0.7
'@voerkai18n/cli': workspace:^1.0.11
'@voerkai18n/react': workspace:^1.0.0
'@voerkai18n/runtime': workspace:^1.1.9
'@voerkai18n/vite': workspace:^1.0.7
core-js: ^3.27.1
react: ^17.0.2
react-dom: ^17.0.2
vite: ^2.9.0
vite-plugin-inspect: ^0.4.3
dependencies:
'@babel/runtime-corejs3': 7.20.7
'@voerkai18n/react': link:../../react
'@voerkai18n/runtime': link:../../runtime
'@voerkai18n/vite': link:../../vite
core-js: 3.27.1
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
devDependencies:
@ -201,12 +207,9 @@ importers:
packages/react:
specifiers:
'@voerkai18n/autopublish': workspace:^1.0.2
react: ^17.0.2
'@voerkai18n/runtime': latest
dependencies:
react: 17.0.2
devDependencies:
'@voerkai18n/autopublish': link:../autopublish
'@voerkai18n/runtime': link:../runtime
packages/runtime:
specifiers:
@ -215,15 +218,19 @@ importers:
'@babel/plugin-transform-runtime': ^7.17.0
'@babel/preset-env': ^7.16.11
'@babel/runtime': ^7.17.8
'@babel/runtime-corejs3': latest
'@rollup/plugin-babel': ^5.3.1
'@rollup/plugin-commonjs': ^21.0.2
'@rollup/plugin-node-resolve': ^13.1.3
'@voerkai18n/autopublish': workspace:^1.0.2
core-js: '3.21'
deepmerge: ^4.2.2
jest: ^27.5.1
rollup: ^2.69.0
rollup-plugin-clear: ^2.0.7
rollup-plugin-terser: ^7.0.2
dependencies:
'@babel/runtime-corejs3': 7.20.7
core-js: 3.21.1
devDependencies:
'@babel/cli': 7.18.10_@babel+core@7.18.10
'@babel/core': 7.18.10
@ -233,7 +240,6 @@ importers:
'@rollup/plugin-babel': 5.3.1_tui6liyexu3zy4m5r2rytc7ixu
'@rollup/plugin-commonjs': 21.1.0_rollup@2.77.2
'@rollup/plugin-node-resolve': 13.3.0_rollup@2.77.2
'@voerkai18n/autopublish': link:../autopublish
deepmerge: 4.2.2
jest: 27.5.1
rollup: 2.77.2
@ -1478,11 +1484,19 @@ packages:
core-js-pure: 3.24.1
regenerator-runtime: 0.13.9
/@babel/runtime-corejs3/7.20.7:
resolution: {integrity: sha512-jr9lCZ4RbRQmCR28Q8U8Fu49zvFqLxTY9AMOUz+iyMohMoAgpEcVxY+wJNay99oXOpOcCTODkk70NDN2aaJEeg==}
engines: {node: '>=6.9.0'}
dependencies:
core-js-pure: 3.27.1
regenerator-runtime: 0.13.11
dev: false
/@babel/runtime/7.18.6:
resolution: {integrity: sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.9
regenerator-runtime: 0.13.11
dev: true
/@babel/runtime/7.18.9:
@ -2322,7 +2336,7 @@ packages:
'@umijs/utils': 3.5.32
ansi-html: 0.0.7
core-js: 3.6.5
core-js-pure: 3.24.1
core-js-pure: 3.27.1
error-stack-parser: 2.1.4
es-module-lexer: 0.7.1
es5-imcompatible-versions: 0.1.74
@ -4465,16 +4479,31 @@ packages:
resolution: {integrity: sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==}
requiresBuild: true
/core-js-pure/3.27.1:
resolution: {integrity: sha512-BS2NHgwwUppfeoqOXqi08mUqS5FiZpuRuJJpKsaME7kJz0xxuk0xkhDdfMIlP/zLa80krBqss1LtD7f889heAw==}
requiresBuild: true
/core-js/2.6.12:
resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
requiresBuild: true
dev: true
/core-js/3.21.1:
resolution: {integrity: sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==}
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
requiresBuild: true
dev: false
/core-js/3.24.1:
resolution: {integrity: sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg==}
requiresBuild: true
/core-js/3.27.1:
resolution: {integrity: sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==}
requiresBuild: true
dev: false
/core-js/3.6.5:
resolution: {integrity: sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==}
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
@ -9965,6 +9994,9 @@ packages:
resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==}
dev: true
/regenerator-runtime/0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
/regenerator-runtime/0.13.5:
resolution: {integrity: sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==}
dev: true