增加React封装
This commit is contained in:
parent
72a9db2973
commit
5eafddb562
@ -34,7 +34,7 @@
|
||||
"shelljs": "^0.8.5",
|
||||
"vinyl": "^2.2.1",
|
||||
"vuepress": "^2.0.0-beta.38",
|
||||
"vuepress-theme-hope": "^2.0.0-beta.36"
|
||||
"vuepress-theme-hope": "^2.0.0-beta.38"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
|
24
packages/apps/reactapp/.gitignore
vendored
Normal file
24
packages/apps/reactapp/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
13
packages/apps/reactapp/index.html
Normal file
13
packages/apps/reactapp/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
22
packages/apps/reactapp/package.json
Normal file
22
packages/apps/reactapp/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "reactapp",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@voerkai18n/react": "workspace:^1.0.0",
|
||||
"@voerkai18n/vite": "workspace:^1.0.7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^1.0.7",
|
||||
"@voerkai18n/cli": "workspace:^1.0.11",
|
||||
"vite": "^2.9.0",
|
||||
"vite-plugin-inspect": "^0.4.3"
|
||||
}
|
||||
}
|
42
packages/apps/reactapp/src/App.css
Normal file
42
packages/apps/reactapp/src/App.css
Normal file
@ -0,0 +1,42 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: calc(10px + 2vmin);
|
||||
}
|
33
packages/apps/reactapp/src/App.jsx
Normal file
33
packages/apps/reactapp/src/App.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { useState,useRef,useEffect } from 'react'
|
||||
import logo from './logo.svg'
|
||||
import './App.css'
|
||||
import "./main"
|
||||
import Banner from './components/Banner'
|
||||
import LanguageConfigurator from "./components/LanguageConfigurator.jsx"
|
||||
import { useVoerkaI18n } from "@voerkai18n/react";
|
||||
|
||||
|
||||
function App() {
|
||||
const { activeLanguage, changeLanguage, languages } = useVoerkaI18n();
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<div>{t("Voerkai1n是一个功能强大设计精良的国际化解决方案")}</div>
|
||||
<LanguageConfigurator/>
|
||||
<div>
|
||||
<Banner memo={t("这是一个测试")} />
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://zhangfisher.github.io/voerka-i18n"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
学习VoerkaI18n
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
12
packages/apps/reactapp/src/components/Banner.jsx
Normal file
12
packages/apps/reactapp/src/components/Banner.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
function Banner( props ) {
|
||||
return (
|
||||
<div>
|
||||
<h2>{t("一字一世界,一笔一乾坤,汉字是这个星球上最美的语言")}</h2>
|
||||
<h3>{props.memo}</h3>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Banner
|
@ -0,0 +1,26 @@
|
||||
import { useState } from "react";
|
||||
import { useVoerkaI18n } from "@voerkai18n/react"
|
||||
|
||||
function LanguageConfigurator(props) {
|
||||
const { activeLanguage, changeLanguage, languages } = useVoerkaI18n();
|
||||
return (
|
||||
<div>
|
||||
<div>{t("当前语言")}:{ activeLanguage }</div>
|
||||
<div>
|
||||
{languages.map((lang) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={lang.name}
|
||||
onClick={() => changeLanguage(lang.name)}
|
||||
>
|
||||
{lang.title}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LanguageConfigurator;
|
15
packages/apps/reactapp/src/favicon.svg
Normal file
15
packages/apps/reactapp/src/favicon.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
13
packages/apps/reactapp/src/index.css
Normal file
13
packages/apps/reactapp/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
8
packages/apps/reactapp/src/languages/en.js
Normal file
8
packages/apps/reactapp/src/languages/en.js
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
"1": "Voerkai1n is a powerful and well-designed international solution",
|
||||
"2": "The great rejuvenation of the Chinese nation",
|
||||
"3": "Those who violate China will be punished even if they are far away!",
|
||||
"4": "This is test",
|
||||
"5": "Chinese characters are the most beautiful language on the planet",
|
||||
"6": "Current Language"
|
||||
}
|
108
packages/apps/reactapp/src/languages/formatters.js
Normal file
108
packages/apps/reactapp/src/languages/formatters.js
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
|
||||
格式化器用来对翻译文本内容中的插值变量进行格式化,
|
||||
比如将一个数字格式化为货币格式,或者将一个日期格式化为友好的日期格式。
|
||||
|
||||
- 以下定义了一些格式化器,在中文场景下,会启用这些格式化器。
|
||||
import dayjs from "dayjs";
|
||||
const formatters = {
|
||||
"*":{ // 在所有语言下生效的格式化器
|
||||
$types:{...}, // 只作用于特定数据类型的默认格式化器
|
||||
.... // 全局格式化器
|
||||
},
|
||||
zh:{
|
||||
// 只作用于特定数据类型的格式化器
|
||||
$types:{
|
||||
Date:(value)=>dayjs(value).format("YYYY年MM月DD日 HH:mm:ss"),
|
||||
},
|
||||
date:(value)=>dayjs(value).format("YYYY年MM月DD日")
|
||||
bjTime:(value)=>"北京时间"+ value,
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
Date:(value)=>dayjs(value).format("YYYY/MM/DD HH:mm:ss"), // 默认的格式化器
|
||||
},
|
||||
date:(value)=>dayjs(value).format("YYYY/MM/DD")
|
||||
bjTime:(value)=>"BeiJing "+ value,
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
}
|
||||
}
|
||||
- 在翻译函数中使用格式化器的方法,示例如下:
|
||||
|
||||
t("Now is { value | date | bjTime }",{value: new Date()})
|
||||
其等效于:
|
||||
t(`Now is ${bjTime(date(value))",{value: new Date()})
|
||||
由于value分别经过两个管道符转换,上一个管道符的输出作为下一个管道符的输入,可以多次使用管道符。
|
||||
|
||||
最终的输出结果:
|
||||
中文: "现在是北京时间2022年3月1日"
|
||||
英文: "Now is BeiJing 2022/03/01"
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
export default{
|
||||
// 在所有语言下生效的格式化器
|
||||
"*":{
|
||||
//[格式化名称]:(value)=>{...},
|
||||
//[格式化名称]:(value,arg)=>{...},
|
||||
},
|
||||
// 在所有语言下只作用于特定数据类型的格式化器
|
||||
$types:{
|
||||
|
||||
},
|
||||
zh:{
|
||||
$types:{
|
||||
// 所有类型的默认格式化器
|
||||
// "*":{
|
||||
|
||||
// },
|
||||
// Date:{
|
||||
|
||||
// },
|
||||
// Number:{
|
||||
|
||||
// },
|
||||
// String:{
|
||||
|
||||
// },
|
||||
// Array:{
|
||||
|
||||
// },
|
||||
// Object:{
|
||||
|
||||
// }
|
||||
}
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
// 所有类型的默认格式化器
|
||||
// "*":{
|
||||
|
||||
// },
|
||||
// Date:{
|
||||
|
||||
// },
|
||||
// Number:{
|
||||
|
||||
// },
|
||||
// String:{
|
||||
|
||||
// },
|
||||
// Array:{
|
||||
|
||||
// },
|
||||
// Object:{
|
||||
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
8
packages/apps/reactapp/src/languages/idMap.js
Normal file
8
packages/apps/reactapp/src/languages/idMap.js
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
"Voerkai1n是一个功能强大设计精良的国际化解决方案": 1,
|
||||
"中华民族伟大复兴": 2,
|
||||
"犯我中华者,虽远必诛!": 3,
|
||||
"这是一个测试": 4,
|
||||
"一字一世界,一笔一乾坤,汉字是这个星球上最美的语言": 5,
|
||||
"当前语言": 6
|
||||
}
|
47
packages/apps/reactapp/src/languages/index.js
Normal file
47
packages/apps/reactapp/src/languages/index.js
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
import messageIds from "./idMap.js"
|
||||
import runtime from "./runtime.js"
|
||||
const { translate,i18nScope } = runtime
|
||||
|
||||
import formatters from "./formatters.js"
|
||||
import defaultMessages from "./zh.js"
|
||||
const activeMessages = defaultMessages
|
||||
|
||||
|
||||
// 语言配置文件
|
||||
const scopeSettings = {
|
||||
"languages": [
|
||||
{
|
||||
"name": "zh",
|
||||
"title": "中文"
|
||||
},
|
||||
{
|
||||
"name": "en",
|
||||
"title": "英语"
|
||||
}
|
||||
],
|
||||
"defaultLanguage": "zh",
|
||||
"activeLanguage": "zh",
|
||||
"namespaces": {}
|
||||
}
|
||||
|
||||
// 语言作用域
|
||||
const scope = new i18nScope({
|
||||
...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters
|
||||
id: "reactapp", // 当前作用域的id,自动取当前工程的package.json的name
|
||||
default: defaultMessages, // 默认语言包
|
||||
messages : activeMessages, // 当前语言包
|
||||
idMap:messageIds, // 消息id映射列表
|
||||
formatters, // 当前作用域的格式化函数列表
|
||||
loaders:{
|
||||
"en" : ()=>import("./en.js")
|
||||
}
|
||||
})
|
||||
// 翻译函数
|
||||
const scopedTtranslate = translate.bind(scope)
|
||||
|
||||
export {
|
||||
scopedTtranslate as t,
|
||||
scope as i18nScope
|
||||
}
|
||||
|
936
packages/apps/reactapp/src/languages/runtime.js
Normal file
936
packages/apps/reactapp/src/languages/runtime.js
Normal file
@ -0,0 +1,936 @@
|
||||
/**
|
||||
* 判断是否是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 };
|
15
packages/apps/reactapp/src/languages/settings.json
Normal file
15
packages/apps/reactapp/src/languages/settings.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"languages": [
|
||||
{
|
||||
"name": "zh",
|
||||
"title": "中文"
|
||||
},
|
||||
{
|
||||
"name": "en",
|
||||
"title": "英语"
|
||||
}
|
||||
],
|
||||
"defaultLanguage": "zh",
|
||||
"activeLanguage": "zh",
|
||||
"namespaces": {}
|
||||
}
|
38
packages/apps/reactapp/src/languages/translates/default.json
Normal file
38
packages/apps/reactapp/src/languages/translates/default.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"Voerkai1n是一个功能强大设计精良的国际化解决方案": {
|
||||
"en": "Voerkai1n is a powerful and well-designed international solution",
|
||||
"$file": [
|
||||
"App.jsx"
|
||||
]
|
||||
},
|
||||
"中华民族伟大复兴": {
|
||||
"en": "The great rejuvenation of the Chinese nation",
|
||||
"$file": [
|
||||
"modules\\module1.js"
|
||||
]
|
||||
},
|
||||
"犯我中华者,虽远必诛!": {
|
||||
"en": "Those who violate China will be punished even if they are far away!",
|
||||
"$file": [
|
||||
"modules\\module2.js"
|
||||
]
|
||||
},
|
||||
"这是一个测试": {
|
||||
"en": "This is test",
|
||||
"$file": [
|
||||
"App.jsx"
|
||||
]
|
||||
},
|
||||
"一字一世界,一笔一乾坤,汉字是这个星球上最美的语言": {
|
||||
"en": "Chinese characters are the most beautiful language on the planet",
|
||||
"$file": [
|
||||
"components\\Banner.jsx"
|
||||
]
|
||||
},
|
||||
"当前语言": {
|
||||
"en": "Current Language",
|
||||
"$file": [
|
||||
"components\\LanguageConfigurator.jsx"
|
||||
]
|
||||
}
|
||||
}
|
8
packages/apps/reactapp/src/languages/zh.js
Normal file
8
packages/apps/reactapp/src/languages/zh.js
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
"1": "Voerkai1n是一个功能强大设计精良的国际化解决方案",
|
||||
"2": "中华民族伟大复兴",
|
||||
"3": "犯我中华者,虽远必诛!",
|
||||
"4": "这是一个测试",
|
||||
"5": "一字一世界,一笔一乾坤,汉字是这个星球上最美的语言",
|
||||
"6": "当前语言"
|
||||
}
|
7
packages/apps/reactapp/src/logo.svg
Normal file
7
packages/apps/reactapp/src/logo.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
3
packages/apps/reactapp/src/main.js
Normal file
3
packages/apps/reactapp/src/main.js
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
console.log(t("中华民族伟大复兴"))
|
13
packages/apps/reactapp/src/main.jsx
Normal file
13
packages/apps/reactapp/src/main.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React,{ } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import "./languages"
|
||||
|
||||
|
||||
ReactDOM.render((
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
), document.getElementById('root')
|
||||
)
|
2
packages/apps/reactapp/src/modules/module1.js
Normal file
2
packages/apps/reactapp/src/modules/module1.js
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export const title = t("中华民族伟大复兴")
|
3
packages/apps/reactapp/src/modules/module2.js
Normal file
3
packages/apps/reactapp/src/modules/module2.js
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
export const title = t("犯我中华者,虽远必诛!")
|
||||
|
13
packages/apps/reactapp/vite.config.js
Normal file
13
packages/apps/reactapp/vite.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import Inspect from 'vite-plugin-inspect'
|
||||
import Voerkai18nPlugin from "@voerkai18n/vite"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Inspect(), // localhost:3000/__inspect/
|
||||
Voerkai18nPlugin({ debug: true }),
|
||||
react()
|
||||
]
|
||||
})
|
@ -9,6 +9,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@voerkai18n/cli": "workspace:^1.0.11",
|
||||
"@voerkai18n/vite": "workspace:^1.0.7",
|
||||
"@voerkai18n/vue": "workspace:^1.0.0",
|
||||
"vue": "^3.2.31"
|
||||
},
|
||||
@ -25,7 +26,6 @@
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"core-js": "^3.21.1",
|
||||
"vite": "^2.8.6",
|
||||
"vite-plugin-babel": "^1.0.0",
|
||||
"vite-plugin-inspect": "^0.4.3"
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import vue from '@vitejs/plugin-vue'
|
||||
import legacy from '@vitejs/plugin-legacy'
|
||||
import { babel } from '@rollup/plugin-babel';
|
||||
import Inspect from 'vite-plugin-inspect'
|
||||
import Voerkai18nPlugin from "../../vite"
|
||||
import Voerkai18nPlugin from "@voerkai18n/vite"
|
||||
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
|
39
packages/react/index.js
Normal file
39
packages/react/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { useState, useEffect,useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* MyComponent(){
|
||||
* const { activeLanguage, changeLanguage } = useVoerkaI18n()
|
||||
*
|
||||
*
|
||||
* }
|
||||
*
|
||||
*
|
||||
*/
|
||||
export function useVoerkaI18n(i18nScope) {
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
@ -8,9 +8,7 @@
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"deepmerge": "^4.2.2",
|
||||
"gulp": "^4.0.2",
|
||||
"vinyl": "^2.2.1"
|
||||
"dependencies": {
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -100,8 +100,11 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
autoImport: true, // 是否自动导入t函数
|
||||
debug:false, // 是否输出调试信息,当=true时,在控制台输出转换匹配的文件清单
|
||||
patterns:[
|
||||
"!(?<!.vue\?.*).(css|json|scss|less|sass)$", // 排除所有css文件
|
||||
/\.vue(\?.*)?/, // 所有vue文件
|
||||
"!\.(svg|css|json|scss|less|sass)$",
|
||||
"!(?<!.vue\?.*).(css|json|scss|less|sass)$", // 排除所有css文件
|
||||
/\.vue(\?.*)?/, // 所有vue文件
|
||||
"!(?<!.jsx\?.*).(css|json|scss|less|sass)$",
|
||||
/\.jsx(\?.*)?/,
|
||||
] // 提取范围
|
||||
},opts)
|
||||
|
||||
@ -110,6 +113,9 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
let projectRoot = getProjectRootFolder(options.location)
|
||||
let languageFolder = getProjectLanguageFolder(projectRoot)
|
||||
|
||||
if(!fs.existsSync(languageFolder)){
|
||||
console.warn(`Voerkai18n语言文件夹不存在,@voerkai18n/vite未启用`)
|
||||
}
|
||||
if(debug){
|
||||
console.log("Project root: ",projectRoot)
|
||||
console.log("Language folder: ",languageFolder)
|
||||
@ -119,7 +125,13 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
basePath:projectRoot,
|
||||
debug:debug
|
||||
})
|
||||
let idMap = readIdMapFile(options)
|
||||
let idMap
|
||||
try{
|
||||
idMap = readIdMapFile(options)
|
||||
}catch(e){
|
||||
console.warn("读取idMap.js文件失败,@voerkai18n/vite未启用")
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'voerkai18n',
|
||||
@ -140,12 +152,17 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
importSource = "./" + importSource
|
||||
}
|
||||
importSource=importSource.replace("\\","/")
|
||||
// 优先在<script setup></script>中导入
|
||||
const setupScriptRegex = /(^\s*\<script.*\s*setup\s*.*\>)/gmi
|
||||
if(setupScriptRegex.test(code)){
|
||||
code = code.replace(setupScriptRegex,`$1\nimport { t } from '${importSource}';`)
|
||||
}else{// 如果没有<script setup>则在<script></script>中导入
|
||||
code = code.replace(/(^\s*\<script.*\>)/gmi,`$1\nimport { t } from '${importSource}';`)
|
||||
// 转换Vue文件
|
||||
if(path.extname(id)==".vue"){
|
||||
// 优先在<script setup></script>中导入
|
||||
const setupScriptRegex = /(^\s*\<script.*\s*setup\s*.*\>)/gmi
|
||||
if(setupScriptRegex.test(code)){
|
||||
code = code.replace(setupScriptRegex,`$1\nimport { t } from '${importSource}';`)
|
||||
}else{// 如果没有<script setup>则在<script></script>中导入
|
||||
code = code.replace(/(^\s*\<script.*\>)/gmi,`$1\nimport { t } from '${importSource}';`)
|
||||
}
|
||||
}else{// 普通js文件需要添加到最前面
|
||||
code = code = `import { t } from '${importSource}';\n${code}`
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@voerkai18n/vite",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"description": "VoerkaI18n plugin for Vite",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@ -10,10 +10,10 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@voerkai18n/utils": "workspace:^1.0.0"
|
||||
"@voerkai18n/utils": "workspace:^1.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
||||
},
|
||||
"lastPublish": "2022-04-10T17:21:48+08:00"
|
||||
"lastPublish": "2022-04-14T20:04:32+08:00"
|
||||
}
|
1996
pnpm-lock.yaml
generated
1996
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user