update
This commit is contained in:
parent
b22d2ddaf7
commit
3aaba68e2f
10
package.json
10
package.json
@ -15,19 +15,23 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.5",
|
||||
"@babel/core": "^7.17.8",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
"@rollup/plugin-commonjs": "^21.0.2",
|
||||
"dayjs": "^1.10.8",
|
||||
"dayjs": "^1.11.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^10.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"jest": "^27.5.1",
|
||||
"rollup": "^2.69.0",
|
||||
"logsets": "^1.0.8",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-clear": "^2.0.7",
|
||||
"shelljs": "^0.8.5",
|
||||
"vinyl": "^2.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"inquirer": "^8.2.2"
|
||||
}
|
||||
}
|
||||
|
17
packages/apps/vueapp/babel.config.js
Normal file
17
packages/apps/vueapp/babel.config.js
Normal file
@ -0,0 +1,17 @@
|
||||
const i18nPlugin =require("@voerkai18n/babel")
|
||||
module.exports = {
|
||||
presets: [
|
||||
"@babel/preset-env"
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
i18nPlugin,
|
||||
{
|
||||
// 可选,指定语言文件存放的目录,即保存编译后的语言文件的文件夹
|
||||
// 可以指定相对路径,也可以指定绝对路径
|
||||
// location:"",
|
||||
autoImport:"/src/languages/index.js"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@ -8,11 +8,24 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@voerkai18n/cli": "workspace:^1.0.6",
|
||||
"vue": "^3.2.25"
|
||||
"@voerkai18n/cli": "workspace:^1.0.11",
|
||||
"@voerkai18n/vue": "workspace:^1.0.0",
|
||||
"vue": "^3.2.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.2.0",
|
||||
"vite": "^2.8.0"
|
||||
"@babel/cli": "^7.17.6",
|
||||
"@babel/core": "^7.17.8",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/runtime": "^7.17.8",
|
||||
"@babel/runtime-corejs3": "^7.17.8",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
"@vitejs/plugin-legacy": "^1.7.1",
|
||||
"@vitejs/plugin-vue": "^2.2.4",
|
||||
"@voerkai18n/babel": "workspace:^1.0.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,51 @@
|
||||
<script setup>
|
||||
import {reactive } from 'vue'
|
||||
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
import { t } from './languages'
|
||||
import China from './components/china.vue'
|
||||
|
||||
console.log(t("Hello world!"))
|
||||
let messages = reactive({
|
||||
name: t('VoerkaI18n多语言解决方案 ')
|
||||
})
|
||||
|
||||
</script>
|
||||
console.log("App=",messages.name)
|
||||
|
||||
setTimeout(() => {
|
||||
messages.name = 'Vue App 2'
|
||||
}, 5000)
|
||||
|
||||
// console.log(t("Hello world!"))
|
||||
</script>
|
||||
<script>
|
||||
import {reactive } from 'vue'
|
||||
export default {
|
||||
inject: ['i18n'],
|
||||
data() {
|
||||
return {
|
||||
language: this.i18n.activeLanguage,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
async changeLanguage(value) {
|
||||
this.i18n.activeLanguage = value
|
||||
//this.activeLanguage = value
|
||||
// await this.i18n.change(this.language)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<img alt="Vue logo" src="./assets/logo.png" />
|
||||
<HelloWorld msg="Hello Vue 3 + Vite" />
|
||||
<h1>{{ t("中华人民共和国")}} </h1>
|
||||
<h2>{{ t("迎接中华民族的伟大复兴")}} </h2>
|
||||
<China :title="t('中华人民共和国')" />
|
||||
<h5>默认语言:{{ i18n.defaultLanguage }}</h5>
|
||||
<h5>当前语言:{{ i18n.activeLanguage.value }}</h5>
|
||||
<button v-for="lng of i18n.languages" @click="i18n.activeLanguage = lng.name" style="padding:8px;margin:8px;cursor:pointer" :style="{'outline' : lng.name === i18n.activeLanguage.value ? '2px red solid' : ''}" >{{ lng.title }}</button>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
@ -1,40 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<p>
|
||||
Recommended IDE setup:
|
||||
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
|
||||
+
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">
|
||||
Vite Documentation
|
||||
</a>
|
||||
|
|
||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
|
||||
</p>
|
||||
|
||||
<button type="button" @click="count++">count is: {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
24
packages/apps/vueapp/src/components/china.vue
Normal file
24
packages/apps/vueapp/src/components/china.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
title: String
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="padding:16px;border:1px red solid;margin:0 auto;margin-bottom:16px;width:600px;">
|
||||
<h2>{{ title }}</h2>
|
||||
<h3>{{ t("成立于{}年",1949)}} </h3>
|
||||
<h3>{{ t("首都:北京")}} </h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
@ -1,3 +1,8 @@
|
||||
export default {
|
||||
"1": "Hello world!"
|
||||
"1": "Hello world!",
|
||||
"2": "中华人民共和国",
|
||||
"3": "迎接中华民族的伟大复兴",
|
||||
"4": "成立于{}年",
|
||||
"5": "首都:北京",
|
||||
"6": "VoerkaI18n多语言解决方案 "
|
||||
}
|
@ -1,3 +1,8 @@
|
||||
export default {
|
||||
"1": "Hello world!"
|
||||
"1": "Hello world!",
|
||||
"2": "The People's Republic of China",
|
||||
"3": "Welcome the great rejuvenation of the Chinese nation",
|
||||
"4": "Founded in {}",
|
||||
"5": "Capital: Beijing",
|
||||
"6": "VoerkaI18n多语言解决方案 "
|
||||
}
|
@ -1,3 +1,8 @@
|
||||
export default {
|
||||
"Hello world!": 1
|
||||
"Hello world!": 1,
|
||||
"中华人民共和国": 2,
|
||||
"迎接中华民族的伟大复兴": 3,
|
||||
"成立于{}年": 4,
|
||||
"首都:北京": 5,
|
||||
"VoerkaI18n多语言解决方案 ": 6
|
||||
}
|
@ -38,10 +38,10 @@ const scope = new i18nScope({
|
||||
}
|
||||
})
|
||||
// 翻译函数
|
||||
const t = translate.bind(scope)
|
||||
const scopedTtranslate = translate.bind(scope)
|
||||
|
||||
export {
|
||||
t,
|
||||
i18nScope as scope
|
||||
scopedTtranslate as t,
|
||||
scope as i18nScope
|
||||
}
|
||||
|
||||
|
@ -4,5 +4,35 @@
|
||||
"$file": [
|
||||
"App.vue"
|
||||
]
|
||||
},
|
||||
"中华人民共和国": {
|
||||
"en": "The People's Republic of China",
|
||||
"$file": [
|
||||
"App.vue"
|
||||
]
|
||||
},
|
||||
"迎接中华民族的伟大复兴": {
|
||||
"en": "Welcome the great rejuvenation of the Chinese nation",
|
||||
"$file": [
|
||||
"App.vue"
|
||||
]
|
||||
},
|
||||
"成立于{}年": {
|
||||
"en": "Founded in {}",
|
||||
"$file": [
|
||||
"components\\china.vue"
|
||||
]
|
||||
},
|
||||
"首都:北京": {
|
||||
"en": "Capital: Beijing",
|
||||
"$file": [
|
||||
"components\\china.vue"
|
||||
]
|
||||
},
|
||||
"VoerkaI18n多语言解决方案 ": {
|
||||
"en": "VoerkaI18n多语言解决方案 ",
|
||||
"$file": [
|
||||
"App.vue"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
|
||||
|
||||
import i18nPlugin from '@voerkai18n/vue'
|
||||
import { t,i18nScope } from './languages'
|
||||
const app = createApp(App)
|
||||
app.use(i18nPlugin,{ t,i18nScope })
|
||||
app.mount('#app')
|
||||
|
||||
|
@ -1,7 +1,23 @@
|
||||
import { defineConfig } from 'vite'
|
||||
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"
|
||||
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()]
|
||||
plugins: [
|
||||
//legacy(),
|
||||
//babel(),
|
||||
Inspect(), // localhost:3000/__inspect/
|
||||
Voerkai18nPlugin({debug:true}),
|
||||
vue()
|
||||
],
|
||||
resolve:{
|
||||
alias:{
|
||||
//"voerkai18n":"./languages/index.js"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
59
packages/autopublish/addGitTag.js
Normal file
59
packages/autopublish/addGitTag.js
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* 读取当前项目的<package.json>.version,然后运行git tag [Version]在当前工程中添加版本tag
|
||||
*
|
||||
* 添加会判断是否已经存在该版本标签,如果存在则不添加
|
||||
* 本脚本用在发布后自动添加版本标签
|
||||
*
|
||||
* addGitTag
|
||||
*
|
||||
* {
|
||||
* scripts:{
|
||||
* "publish":"node version patch",
|
||||
* // 在发布后在当前工程中添加版本标签
|
||||
* "postpublish":"node addGitTag
|
||||
* "
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*/
|
||||
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const dayjs = require("dayjs");
|
||||
const shelljs = require("shelljs");
|
||||
|
||||
// 可选的,指定工程目录
|
||||
const args = process.argv.slice(2);
|
||||
const projectFolder = args.length > 0 ? (path.isAbsolute(args[0]) ? args[0] : path.join(process.cwd(),args[0])) : process.cwd();
|
||||
|
||||
// 读取当前项目的package.json
|
||||
|
||||
const pkgFile = path.join(projectFolder, "package.json")
|
||||
const pkg = fs.readJSONSync(pkgFile);
|
||||
|
||||
// 切换到当前目录
|
||||
shelljs.cd(projectFolder);
|
||||
|
||||
// 读取当前版本
|
||||
const tagInfo = shelljs.exec(`git describe --tags --match=V${pkg.version}`, {silent: true}).stdout.trim();
|
||||
if(tagInfo.length===0){
|
||||
console.log(`未发现版本标签:V${pkg.version}`);
|
||||
if(shelljs.exec(`git tag V${pkg.version}`, {silent: false}).code>0){
|
||||
console.log(`添加版本标签<V${pkg.version}>失败`)
|
||||
}else{
|
||||
console.log(`已添加版本标签:V${pkg.version}`)
|
||||
}
|
||||
}else {
|
||||
console.log(`已发现版本标签:${tagInfo}`);
|
||||
}
|
||||
// 更新
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
113
packages/autopublish/index.js
Normal file
113
packages/autopublish/index.js
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
*
|
||||
* 自动发布工具
|
||||
*
|
||||
* 自动读取当前项目的版本标签,如果不致则进行
|
||||
*
|
||||
*
|
||||
*
|
||||
* 支持两种发布方式:
|
||||
*
|
||||
* 1. 自动发布
|
||||
*
|
||||
* 当选择自动发布时,会读取当前项目下的所有文件,然后
|
||||
*
|
||||
* 根据每个包的最后提交时间和最近一次发布来决定是否自动发布
|
||||
* 每个包发布时均需要修改package.json.lastPublish值
|
||||
*
|
||||
* 2. 手动发布
|
||||
* 手动选择要发布的包
|
||||
*
|
||||
* pnpm add @voerkai18n/autopublish
|
||||
*
|
||||
* // 自动发布
|
||||
* autopublish
|
||||
*
|
||||
*/
|
||||
|
||||
const inquirer = require("inquirer");
|
||||
const fs = require("fs-extra");
|
||||
const shelljs = require("shelljs");
|
||||
const path = require("path");
|
||||
const createLogger = require("logsets");
|
||||
const commander = require("commander");
|
||||
|
||||
const dayjs = require("dayjs");
|
||||
const relativeTime = require("dayjs/plugin/relativeTime");
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const logger = createLogger();
|
||||
|
||||
|
||||
const program = commander()
|
||||
|
||||
|
||||
program
|
||||
.version("1.0.0")
|
||||
.option("-a, --auto", "自动发布")
|
||||
.option("-a, --auto", "启用自动发布")
|
||||
.option("-m, --monorepo", "是否是monorepo工程")
|
||||
.option("-i, --independent", "每个包独立发布")
|
||||
.option("-e, --excludePackages", "多包场景时忽略的包")
|
||||
.action((options) => {
|
||||
|
||||
|
||||
|
||||
|
||||
}))
|
||||
|
||||
|
||||
|
||||
|
||||
// 读取packages所有包
|
||||
|
||||
let excludePackages = ["apps"];
|
||||
|
||||
let packages = [];
|
||||
|
||||
fs.readdirSync(path.join(__dirname, "packages")).forEach(function(packageName){
|
||||
if (excludePackages.includes(packageName)) return;
|
||||
const packageFolder = path.join(__dirname, "packages", packageName);
|
||||
// 读取指定包
|
||||
const { version: packageVersion,lastPublishTime } = require(path.join(packageFolder,"package.json"));
|
||||
// 最后一次提交的时间
|
||||
const packageLastCommitTime = shelljs.exec(`git log -1 --format=%cd --date=iso ${packageFolder}`, {silent: true}).stdout.trim();
|
||||
// 最后发布的时间
|
||||
packages.push({
|
||||
name: `@voerkai18n/${packageName.padEnd(20)}\t\t( Version:${packageVersion}, LastCommit: ${dayjs(packageLastCommitTime).fromNow()})`,
|
||||
value: {
|
||||
packageFolder,
|
||||
lastCommitTime: packageLastCommitTime,
|
||||
version: packageVersion,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "confirm",
|
||||
name: "autoPublish",
|
||||
message: "是否自动发布?",
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
type: "checkbox",
|
||||
name: "selectPackages",
|
||||
message: "请选择要发布的库:",
|
||||
choices: packages,
|
||||
when: function (answer) {
|
||||
return !answer.autoPublish;
|
||||
},
|
||||
},
|
||||
])
|
||||
.then((answers) => {
|
||||
console.log(answers);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
// Prompt couldn't be rendered in the current environment
|
||||
} else {
|
||||
// Something else went wrong
|
||||
}
|
||||
});
|
19
packages/autopublish/package.json
Normal file
19
packages/autopublish/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@voerkai18n/publish",
|
||||
"version": "1.0.0",
|
||||
"description": "发布项目工具",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"release": "npm version patch && pnpm publish --no-git-checks --access public",
|
||||
"postpublish": "git push --follow-tags && npm run build && npm publish"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"publish": "./index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^9.0.0"
|
||||
}
|
||||
}
|
119
packages/autopublish/publish.js
Normal file
119
packages/autopublish/publish.js
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 用于多包环境下的自动发布
|
||||
*
|
||||
* autopublish
|
||||
*
|
||||
* 1.在package.json中添加scripts
|
||||
* {
|
||||
* scripts:{
|
||||
* "publish":"autopublish [options]",
|
||||
* }
|
||||
* }
|
||||
* 2. 参数
|
||||
* -q: 默认情况下会比对最后一次发布的时间,来决定是否自动发布
|
||||
* 当-q参数被指定时,会询问用户
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const shelljs = require("shelljs");
|
||||
const createLogger = require("logsets");
|
||||
const { Command } = require('commander');
|
||||
const dayjs = require("dayjs");
|
||||
const relativeTime = require("dayjs/plugin/relativeTime");
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const logger = createLogger();
|
||||
|
||||
|
||||
const program =new Command()
|
||||
|
||||
|
||||
|
||||
|
||||
const packages = [
|
||||
"git log --format=%cd --date=iso -1 -- packages/babel/package.json",
|
||||
"git log --format=%cd --date=iso -1 -- packages/cli/package.json",
|
||||
"git log --format=%cd --date=iso -1 -- packages/runtime/package.json",
|
||||
"git log --format=%cd --date=iso -1 -- packages/formatters/package.json",
|
||||
"git log --format=%cd --date=iso -1 -- packages/vue/package.json",
|
||||
"git log --format=%cd --date=iso -1 -- packages/vite/package.json",
|
||||
"git log --format=%cd --date=iso -1 -- packages/autopublish/package.json",
|
||||
"git log --format=%cd --date=iso -1 -- packages/utils/package.json"
|
||||
]
|
||||
|
||||
function getPackages(){
|
||||
let workspaceRoot = process.cwd()
|
||||
if(!fs.existsSync(path.join(workspaceRoot,"pnpm-workspace.yaml"))){
|
||||
console.log("命令只能在工作区根目录下执行")
|
||||
return
|
||||
}
|
||||
// 获取包最后一次提交的时间
|
||||
const getLastCommitScript = "git log --format=%cd --date=iso -1 -- {packagePath}"
|
||||
return fs.readdirSync(path.join(workspaceRoot,"packages")).map(packageName=>{
|
||||
const pkgFile = path.join(workspaceRoot,"packages",packageName,"package.json")
|
||||
if(fs.existsSync(pkgFile)){
|
||||
const { name, version }= fs.readJSONSync(pkgFile)
|
||||
const lastCommit = shelljs.exec(getLastCommitScript.replace("{packagePath}",`packages/${packageName}/package.json`), { silent: true }).stdout.trim()
|
||||
return {
|
||||
name,
|
||||
version,
|
||||
lastCommit
|
||||
}
|
||||
}
|
||||
}).filter(pkgInfo=>pkgInfo)
|
||||
}
|
||||
|
||||
function assertInWorkspaceRoot(){
|
||||
const workspaceRoot = process.cwd()
|
||||
if(!fs.existsSync(path.join(workspaceRoot,"pnpm-workspace.yaml"))){
|
||||
throw new Error("命令只能在工作区根目录下执行")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
program
|
||||
.command("publish")
|
||||
.description("发布当前工作区下的包")
|
||||
.option("-f, --force", "强制发布")
|
||||
.option("-q, --query", "询问是否发布,否则会自动发布")
|
||||
.option("-i, --version-increment-step [value]", "版本增长方式,取值major,minor,patch",'patch')
|
||||
.action(options => {
|
||||
const {versionIncrementStep} = options
|
||||
const packageFolder = process.cwd()
|
||||
const pkgFile = path.join(packageFolder,"package.json")
|
||||
|
||||
// 由于每次发布均会更新npm version patch,并且需要提交代码
|
||||
const lastCommit = shelljs.exec(`git log --format=%cd --date=iso -1 -- ${pkgFile}`, { silent: true }).stdout.trim()
|
||||
|
||||
// 增加版本号
|
||||
shelljs.exec(`npm version ${versionIncrementStep}`, { silent: true }).stdout.trim()
|
||||
|
||||
//
|
||||
shelljs.exec(`pnpm publish --access publish`, { silent: true }).stdout.trim()
|
||||
|
||||
})
|
||||
|
||||
program
|
||||
.command("list")
|
||||
.description("列出各个包的最后一次提交时间和版本信息")
|
||||
.action(options => {
|
||||
assertInWorkspaceRoot()
|
||||
|
||||
getPackages().forEach(package => {
|
||||
//console.log(`${package.name}`)
|
||||
if(package.lastCommit){
|
||||
console.log(`${package.name.padEnd(16)}\tVersion: ${package.version.padEnd(12)} lastCommit: ${dayjs(package.lastCommit).format("YYYY/MM/DD ")}(${dayjs(package.lastCommit).fromNow()}) `)
|
||||
}else{
|
||||
console.log(`${package.name.padEnd(16)}\tVersion: ${package.version.padEnd(12)} lastCommit: None `)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
program.parse(process.argv);
|
43
packages/autopublish/publishall.js
Normal file
43
packages/autopublish/publishall.js
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 用于多包环境下的自动发布
|
||||
*
|
||||
*
|
||||
* {
|
||||
* scripts:{
|
||||
* "publish":"autopublish [options]",
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const dayjs = require("dayjs");
|
||||
const shelljs = require("shelljs");
|
||||
const shelljs = require("shelljs");
|
||||
const createLogger = require("logsets");
|
||||
const commander = require("commander");
|
||||
|
||||
const dayjs = require("dayjs");
|
||||
const relativeTime = require("dayjs/plugin/relativeTime");
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const logger = createLogger();
|
||||
|
||||
|
||||
const program = commander()
|
||||
|
||||
|
||||
program
|
||||
.version("1.0.0")
|
||||
.option("-v, --auto", "自动发布")
|
||||
.option("-a, --auto", "启用自动发布")
|
||||
.option("-p, --push []", "发布前执行git push")
|
||||
.option("-m, --commit [message]", "发布前执行git commit")
|
||||
.option("-v, --version", "默认版本升级,取值major,minor,patch")
|
||||
.action((options) => {
|
||||
|
||||
|
||||
|
||||
|
||||
}))
|
||||
|
5
packages/autopublish/readme.md
Normal file
5
packages/autopublish/readme.md
Normal file
@ -0,0 +1,5 @@
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
|
||||
`@voerkai18n/autopublish`辅助进行自动发布
|
||||
|
||||
源码与文档:[https://gitee.com/zhangfisher/voerka-i18n](https://gitee.com/zhangfisher/voerka-i18n)
|
24
packages/autopublish/utils.js
Normal file
24
packages/autopublish/utils.js
Normal file
@ -0,0 +1,24 @@
|
||||
const shelljs = require("shelljs");
|
||||
const path = require("path")
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检测当前工程是否是git工程
|
||||
*/
|
||||
function isGitRepo(){
|
||||
return shelljs.exec("git status", {silent: true}).code === 0;
|
||||
}
|
||||
|
||||
|
||||
function getProjectName(){
|
||||
const pkgFile = path.join(process.cwd(), "package.json")
|
||||
const pkg = fs.readJSONSync(pkgFile);
|
||||
return pkg.name;
|
||||
}
|
||||
|
||||
|
||||
module.exports ={
|
||||
isMonorepo,
|
||||
isGitRepo
|
||||
}
|
@ -21,15 +21,15 @@
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { isPlainObject } = require("./utils");
|
||||
|
||||
const pathobj = require("path");
|
||||
const { getProjectRootFolder } = require("@voerkai18n/utils")
|
||||
|
||||
|
||||
const DefaultI18nPluginOptions = {
|
||||
translateFunctionName:"t", // 默认的翻译函数名称
|
||||
// 翻译文件存放的目录,即编译后的语言文件的文件夹
|
||||
// 默认从当前目录下的languages文件夹中导入
|
||||
// 如果不存在则会
|
||||
location:"./languages",
|
||||
// 默认从当前目录下的languages文件夹中导入
|
||||
location:"./",
|
||||
// 自动创建import {t} from "#/languages" 或 const { t } = require("#/languages")
|
||||
// 如果此值是空,则不会自动创建import语句
|
||||
autoImport:"#/languages",
|
||||
@ -42,6 +42,7 @@ const DefaultI18nPluginOptions = {
|
||||
idMap:{}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断当前是否是一个esmodule,判断依据是
|
||||
* - 包含import语句
|
||||
@ -56,7 +57,6 @@ function isEsModule(path){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否导入了翻译函数
|
||||
* import { t } from "./i18n"
|
||||
@ -67,7 +67,7 @@ function isEsModule(path){
|
||||
function hasImportTranslateFunction(path){
|
||||
for(let ele of path.node.body){
|
||||
if(ele.type==="ImportDeclaration"){
|
||||
if(ele.specifiers.findIndex(s => s.type === "ImportSpecifier" && s.imported.name ==TRANSLATE_FUNCTION_NAME && s.local.name===TRANSLATE_FUNCTION_NAME)>-1){
|
||||
if(Array.isArray(ele.specifiers) && ele.specifiers.findIndex(s => (s.type === "ImportSpecifier") && (s.imported.name ==this.translateFunctionName) && (s.local.name===this.translateFunctionName))>-1){
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -76,24 +76,39 @@ function hasImportTranslateFunction(path){
|
||||
function hasRequireTranslateFunction(path){
|
||||
for(let ele of path.node.body){
|
||||
if(ele.type==="VariableDeclaration"){
|
||||
if(ele.specifiers.findIndex(s => s.type === "ImportSpecifier" && s.imported.name ==TRANSLATE_FUNCTION_NAME && s.local.name===TRANSLATE_FUNCTION_NAME)>-1){
|
||||
if(Array.isArray(ele.specifiers) && ele.specifiers.findIndex(s => s.type === "ImportSpecifier" && s.imported.name ==this.translateFunctionName && s.local.name===this.translateFunctionName)>-1){
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getIdMap(options){
|
||||
const { idMap,location } = options
|
||||
if(isPlainObject(idMap) && Object.keys(idMap).length>0){
|
||||
|
||||
/**
|
||||
* 读取idMap.js文件
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
function readIdMapFile(options){
|
||||
let { idMap,location } = options
|
||||
if(typeof(idMap)==="object" && Object.keys(idMap).length>0){
|
||||
return idMap
|
||||
}else{
|
||||
let idMapFiles = [
|
||||
path.join((path.isAbsolute(location) ? location : path.join(process.cwd(),location)),"idMap.js"),
|
||||
path.join((path.isAbsolute(location) ? path.join(location,"languages") : path.join(process.cwd(),location,"languages")),'idMap.js')
|
||||
]
|
||||
let searchIdMapFiles = []
|
||||
if(!pathobj.isAbsolute(location)){
|
||||
location = pathobj.join(process.cwd(),location)
|
||||
}
|
||||
searchIdMapFiles.push(pathobj.join(location,"src","languages/idMap.js"))
|
||||
searchIdMapFiles.push(pathobj.join(location,"languages/idMap.js"))
|
||||
searchIdMapFiles.push(pathobj.join(location,"idMap.js"))
|
||||
|
||||
let projectRoot = getProjectRootFolder(location)
|
||||
searchIdMapFiles.push(pathobj.join(projectRoot,"src","languages/idMap.js"))
|
||||
searchIdMapFiles.push(pathobj.join(projectRoot,"languages/idMap.js"))
|
||||
searchIdMapFiles.push(pathobj.join(projectRoot,"idMap.js"))
|
||||
|
||||
let idMapFile
|
||||
for( idMapFile of idMapFiles){
|
||||
for( idMapFile of searchIdMapFiles){
|
||||
// 如果不存在idMap文件,则尝试从location/languages/中导入
|
||||
if(fs.existsSync(idMapFile)){
|
||||
try{
|
||||
@ -101,8 +116,7 @@ function getIdMap(options){
|
||||
// 当require(idMap.js)失败时,对esm模块尝试采用直接读取的方式
|
||||
return require(idMapFile)
|
||||
}catch(e){
|
||||
// 出错原因可能是因为无效require esm模块,
|
||||
// 由于idMap.js文件格式相对简单,因此尝试直接读取解析
|
||||
// 出错原因可能是因为无效require esm模块,由于idMap.js文件格式相对简单,因此尝试直接读取解析
|
||||
try{
|
||||
let idMapContent = fs.readFileSync(idMapFile).toString()
|
||||
idMapContent = idMapContent.trim().replace(/^\s*export\s*default\s/g,"")
|
||||
@ -126,20 +140,22 @@ module.exports = function voerkai18nPlugin(babel) {
|
||||
// 转码插件参数可以覆盖默认参数
|
||||
Object.assign(pluginOptions,state.opts || {});
|
||||
const { location ,autoImport, translateFunctionName,moduleType } = pluginOptions
|
||||
idMap = getIdMap(pluginOptions)
|
||||
if(Object.keys(idMap).length===0){
|
||||
idMap = readIdMapFile(pluginOptions)
|
||||
}
|
||||
// 是否自动导入t函数
|
||||
if(autoImport){
|
||||
let module = moduleType === 'auto' ? isEsModule(path) ? 'esm' : 'cjs' : moduleType
|
||||
if(!["esm","es","cjs","commonjs"].includes(module)) module = 'esm'
|
||||
if(module === 'esm'){
|
||||
// 如果没有定义t函数,则自动导入
|
||||
if(!hasImportTranslateFunction(path)){
|
||||
if(!hasImportTranslateFunction.call(pluginOptions, path)){
|
||||
path.node.body.unshift(t.importDeclaration([
|
||||
t.ImportSpecifier(t.identifier(translateFunctionName),t.identifier(translateFunctionName)
|
||||
)],t.stringLiteral(autoImport)))
|
||||
}
|
||||
}else{
|
||||
if(!hasRequireTranslateFunction(path)){
|
||||
if(!hasRequireTranslateFunction.call(pluginOptions, path)){
|
||||
path.node.body.unshift(t.variableDeclaration("const",[
|
||||
t.variableDeclarator(
|
||||
t.ObjectPattern([t.ObjectProperty(t.Identifier(translateFunctionName),t.Identifier(translateFunctionName),false,true)]),
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@voerkai18n/babel",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.3",
|
||||
"description": "VoerkaI18n babel plugin",
|
||||
"main": "index.js",
|
||||
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
||||
@ -10,8 +10,11 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"publish": "npm publish -access public"
|
||||
"release": "npm version patch && pnpm publish --no-git-checks --access public"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@voerkai18n/utils": "workspace:^1.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
# @voerkai18n/babel
|
||||
|
||||
|
||||
`Babel`转码插件,用来对翻译文本进行自动转码
|
||||
|
||||
|
||||
源码与文档:[https://gitee.com/zhangfisher/voerka-i18n](https://gitee.com/zhangfisher/voerka-i18n)
|
@ -1,18 +0,0 @@
|
||||
|
||||
function isPlainObject(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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
isPlainObject
|
||||
}
|
@ -25,7 +25,8 @@
|
||||
const glob = require("glob")
|
||||
const createLogger = require("logsets")
|
||||
const path = require("path")
|
||||
const { t,findModuleType,getCurrentPackageJson} = require("./utils")
|
||||
const { findModuleType,getCurrentPackageJson,installVoerkai18nRuntime,isInstallDependent} = require("@voerkai18n/utils")
|
||||
const { t } = require("./i18nProxy")
|
||||
const fs = require("fs-extra")
|
||||
const logger = createLogger()
|
||||
const artTemplate = require("art-template")
|
||||
@ -52,9 +53,7 @@ module.exports =async function compile(langFolder,opts={}){
|
||||
// 加载多语言配置文件
|
||||
const settingsFile = path.join(langFolder,"settings.json")
|
||||
|
||||
|
||||
try{
|
||||
|
||||
try{
|
||||
|
||||
// 读取多语言配置文件
|
||||
const langSettings = fs.readJSONSync(settingsFile)
|
||||
@ -125,8 +124,14 @@ module.exports =async function compile(langFolder,opts={}){
|
||||
path.join(langFolder,"runtime.js")
|
||||
)
|
||||
logger.log(t(" - 运行时: {}"),"runtime.js")
|
||||
}
|
||||
|
||||
}else{//如果不嵌入则需要安装运行时依赖
|
||||
if(!isInstallDependent("@voerkai18n/runtime")){
|
||||
installVoerkai18nRuntime(langFolder,moduleType)
|
||||
logger.log(t(" - 安装运行时: {}"),"@voerkai18n/runtime")
|
||||
}else{
|
||||
logger.log(t(" - 运行时{}已安装"),"@voerkai18n/runtime")
|
||||
}
|
||||
}
|
||||
const templateContext = {
|
||||
scopeId:projectPackageJson.name,
|
||||
inlineRuntime,
|
||||
|
@ -10,7 +10,10 @@ const deepmerge = require("deepmerge")
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const createLogger = require("logsets")
|
||||
const { findModuleType,createPackageJsonFile,t } = require("./utils")
|
||||
const { t } = require("./i18nProxy")
|
||||
const { findModuleType } = require("@voerkai18n/utils")
|
||||
|
||||
|
||||
const logger = createLogger()
|
||||
|
||||
|
||||
|
21
packages/cli/i18nProxy.js
Normal file
21
packages/cli/i18nProxy.js
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
let i18nScope,t
|
||||
try{
|
||||
// @voerkai18n/cli工程本身使用了voerkai18n,即@voerkai18n/cli的extract和compile依赖于其自己生成的languages运行时
|
||||
// 而@voerkai18n/cli又用来编译多语言包,这样产生了鸡生蛋问题
|
||||
// extract与compile调试阶段如果t函数无法使用(即编译的languages无法正常使用),则需要提供t函数
|
||||
const language = require('./languages');
|
||||
t = language.t
|
||||
i18nScope = language.i18nScope
|
||||
}catch{
|
||||
t=v=>v
|
||||
i18nScope={change:()=>{} }
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
i18nScope,
|
||||
t
|
||||
}
|
||||
|
||||
|
@ -2,42 +2,13 @@ const { Command } = require('commander');
|
||||
const createLogger = require("logsets")
|
||||
const path = require("path")
|
||||
const fs = require("fs-extra")
|
||||
const deepmerge = require('deepmerge');
|
||||
const logger = createLogger()
|
||||
const { getCurrentProjectRootFolder,i18nScope ,t } = require("./utils")
|
||||
|
||||
const { i18nScope ,t } = require("./i18nScope")
|
||||
const { getProjectRootFolder, getProjectSourceFolder } = require("@voerkai18n/utils")
|
||||
|
||||
|
||||
const program = new Command();
|
||||
|
||||
/**
|
||||
* 根据当前输入的文件夹位置自动确定源码文件夹位置
|
||||
*
|
||||
* - 如果没有指定,则取当前文件夹
|
||||
* - 如果指定是非绝对路径,则以当前文件夹作为base
|
||||
* - 查找pack
|
||||
* - 如果该文件夹中存在src,则取src下的文件夹
|
||||
* -
|
||||
*
|
||||
* @param {*} location
|
||||
* @returns
|
||||
*/
|
||||
function getProjectSourceFolder(location){
|
||||
if(!location) {
|
||||
location = process.cwd()
|
||||
}else{
|
||||
if(!path.isAbsolute(location)){
|
||||
location = path.join(process.cwd(),location)
|
||||
}
|
||||
}
|
||||
let projectRoot = getCurrentProjectRootFolder(location)
|
||||
// 如果当前工程存在src文件夹,则自动使用该文件夹作为源文件夹
|
||||
if(fs.existsSync(path.join(projectRoot,"src"))){
|
||||
projectRoot = path.join(projectRoot,"src")
|
||||
}
|
||||
return projectRoot
|
||||
}
|
||||
|
||||
program
|
||||
.command('init')
|
||||
.argument('[location]', t('工程项目所在目录'))
|
||||
|
@ -4,30 +4,14 @@
|
||||
*/
|
||||
|
||||
|
||||
const { findModuleType,createPackageJsonFile,t,getCurrentProjectRootFolder } = require("./utils")
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
const shelljs = require("shelljs")
|
||||
const { t } = require("./i18nProxy")
|
||||
const { findModuleType } = require("@voerkai18n/utils")
|
||||
const createLogger = require("logsets")
|
||||
const logger = createLogger()
|
||||
|
||||
/**
|
||||
* 在当前工程自动安装@voerkai18n/runtime
|
||||
* @param {*} langFolder
|
||||
* @param {*} opts
|
||||
*/
|
||||
function installVoerkai18nRuntim(srcPath){
|
||||
const projectFolder = getCurrentProjectRootFolder(srcPath || process.cwd())
|
||||
if(fs.existsSync("pnpm-lock.yaml")){
|
||||
shelljs.exec("pnpm add @voerkai18n/runtime")
|
||||
}else if(fs.existsSync("yarn.lock")){
|
||||
shelljs.exec("yarn add @voerkai18n/runtime")
|
||||
}else{
|
||||
shelljs.exec("npm install @voerkai18n/runtime")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = function(srcPath,{debug = true,languages=["cn","en"],defaultLanguage="cn",activeLanguage="cn",reset=false,installRuntime=true}={}){
|
||||
// 语言文件夹名称
|
||||
const langPath = "languages"
|
||||
@ -38,7 +22,6 @@ module.exports = function(srcPath,{debug = true,languages=["cn","en"],defaultLan
|
||||
if(debug) logger.log(t("创建语言包文件夹: {}"),lngPath)
|
||||
}
|
||||
|
||||
|
||||
// 创建settings.json文件
|
||||
const settingsFile = path.join(lngPath,"settings.json")
|
||||
if(fs.existsSync(settingsFile) && !reset){
|
||||
@ -54,12 +37,6 @@ module.exports = function(srcPath,{debug = true,languages=["cn","en"],defaultLan
|
||||
|
||||
// 写入配置文件
|
||||
fs.writeFileSync(settingsFile,JSON.stringify(settings,null,4))
|
||||
|
||||
// 自动安装运行时@voerkai18n/runtime
|
||||
// if(installRuntime){
|
||||
// logger.log(t("正在安装多语言运行时:{}"),"@voerkai18n/runtime")
|
||||
// installVoerkai18nRuntim(srcPath)
|
||||
// }
|
||||
|
||||
if(debug) {
|
||||
logger.log(t("生成语言配置文件:{}"),"./languages/settings.json")
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@voerkai18n/cli",
|
||||
"version": "1.0.10",
|
||||
"version": "1.0.11",
|
||||
"description": "VoerkaI18n command line interactive tools",
|
||||
"main": "index.js",
|
||||
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
||||
@ -29,7 +29,8 @@
|
||||
"dependencies": {
|
||||
"@babel/cli": "^7.17.6",
|
||||
"@babel/core": "^7.17.5",
|
||||
"@voerkai18n/runtime": "workspace:^1.0.0",
|
||||
"@voerkai18n/runtime": "workspace:^1.0.8",
|
||||
"@voerkai18n/utils": "workspace:^1.0.0",
|
||||
"art-template": "^4.13.2",
|
||||
"commander": "^9.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
|
@ -1,64 +1,6 @@
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
|
||||
# VoerkaI18n命令行工具
|
||||
|
||||
`@VoerkaI18n/cli`实现初始化、文本提取和编译等命令
|
||||
|
||||
## 初始化项目 - init
|
||||
|
||||
```shell
|
||||
初始化项目国际化配置
|
||||
Arguments:
|
||||
location 工程项目所在目录
|
||||
Options:
|
||||
-D, --debug 输出调试信息
|
||||
-r, --reset 重新生成当前项目的语言配置
|
||||
-lngs, --languages <languages...> 支持的语言列表 (default: ["cn","en"])
|
||||
-d, --defaultLanguage 默认语言
|
||||
-a, --activeLanguage 激活语言
|
||||
-h, --help display help for command
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 提取文本 - extract
|
||||
|
||||
```shell
|
||||
|
||||
扫描并提取所有待翻译的字符串到<languages/translates>文件夹中
|
||||
|
||||
Arguments:
|
||||
location 工程项目所在目录 (default: "./")
|
||||
|
||||
Options:
|
||||
-D, --debug 输出调试信息
|
||||
-lngs, --languages 支持的语言
|
||||
-d, --defaultLanguage 默认语言
|
||||
-a, --activeLanguage 激活语言
|
||||
-ns, --namespaces 翻译名称空间
|
||||
-e, --exclude <folders> 排除要扫描的文件夹,多个用逗号分隔
|
||||
-u, --updateMode 本次提取内容与已存在内容的数据合并策略,默认取值sync=同步,overwrite=覆盖,merge=合并
|
||||
-f, --filetypes 要扫描的文件类型
|
||||
-h, --help display help for command
|
||||
|
||||
```
|
||||
|
||||
## 编译文本 - compile
|
||||
|
||||
```shell
|
||||
|
||||
Usage: voerkai18n compile [options] [location]
|
||||
|
||||
编译指定项目的语言包
|
||||
|
||||
Arguments:
|
||||
location 工程项目所在目录 (default: "./")
|
||||
|
||||
Options:
|
||||
-d, --debug 输出调试信息
|
||||
-m, --moduleType [types] 输出模块类型,取值auto,esm,cjs (default: "esm")
|
||||
-h, --help display help for command
|
||||
|
||||
```
|
||||
|
||||
源码与文档:[https://gitee.com/zhangfisher/voerka-i18n](https://gitee.com/zhangfisher/voerka-i18n)
|
@ -32,13 +32,13 @@ const scope = new i18nScope({
|
||||
}
|
||||
})
|
||||
// 翻译函数
|
||||
const t = translate.bind(scope)
|
||||
const scopedTtranslate = translate.bind(scope)
|
||||
{{if moduleType === "esm"}}
|
||||
export {
|
||||
t,
|
||||
i18nScope as scope
|
||||
scopedTtranslate as t,
|
||||
scope as i18nScope
|
||||
}
|
||||
{{else}}
|
||||
module.exports.t = t
|
||||
module.exports.t = scopedTtranslate
|
||||
module.exports.i18nScope = scope
|
||||
{{/if}}
|
||||
|
@ -1,186 +0,0 @@
|
||||
const path = require("path")
|
||||
const fs = require("fs-extra")
|
||||
|
||||
/**
|
||||
* 返回当前项目根文件夹
|
||||
* 从指定文件夹folder向上查找package.json
|
||||
* @param {*} folder
|
||||
*/
|
||||
function getCurrentProjectRootFolder(folder,exclueCurrent=false){
|
||||
try{
|
||||
const pkgFile =exclueCurrent ?
|
||||
path.join(folder, "..", "package.json")
|
||||
: path.join(folder, "package.json")
|
||||
if(fs.existsSync(pkgFile)){
|
||||
return path.dirname(pkgFile)
|
||||
}
|
||||
const parent = path.dirname(folder)
|
||||
if(parent===folder) return null
|
||||
return getCurrentProjectRootFolder(parent,false)
|
||||
}catch(e){
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取指定文件夹的package.json文件,如果当前文件夹没有package.json文件,则向上查找
|
||||
* @param {*} folder
|
||||
* @param {*} exclueCurrent = true 排除folder,从folder的父级开始查找
|
||||
* @returns
|
||||
*/
|
||||
function getCurrentPackageJson(folder,exclueCurrent=true){
|
||||
let projectFolder = getCurrentProjectRootFolder(folder,exclueCurrent)
|
||||
if(projectFolder){
|
||||
return fs.readJSONSync(path.join(projectFolder,"package.json"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 返回当前项目的模块类型
|
||||
*
|
||||
* 从当前文件夹开始向上查找package.json文件,并解析出语言包的类型
|
||||
*
|
||||
* @param {*} folder
|
||||
*/
|
||||
function findModuleType(folder){
|
||||
let packageJson = getCurrentPackageJson(folder)
|
||||
try{
|
||||
return packageJson.type || "commonjs"
|
||||
}catch(e){
|
||||
return "esm"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function createPackageJsonFile(targetPath,moduleType="auto"){
|
||||
if(moduleType==="auto"){
|
||||
moduleType = findModuleType(targetPath)
|
||||
}
|
||||
const packageJsonFile = path.join(targetPath, "package.json")
|
||||
if(["esm","es","module"].includes(moduleType)){
|
||||
fs.writeFileSync(packageJsonFile,JSON.stringify({type:"module",license:"MIT"},null,4))
|
||||
if(moduleType==="module"){
|
||||
moduleType = "esm"
|
||||
}
|
||||
}else{
|
||||
fs.writeFileSync(packageJsonFile,JSON.stringify({license:"MIT"},null,4))
|
||||
}
|
||||
return moduleType
|
||||
}
|
||||
|
||||
|
||||
function isPlainObject(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;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* getExportContent({a:1}) == export let a = 1
|
||||
*
|
||||
* @param {*} values
|
||||
* @param {*} moduleType
|
||||
* @returns
|
||||
*/
|
||||
function generateExportContents(values,{moduleType="esm",varExportDeclare="let"}={}){
|
||||
if(!isPlainObject(values)) throw new TypeError("export value must be a function or plain object")
|
||||
let results = []
|
||||
let varExports = []
|
||||
let varExportSyntax = moduleType === "esm" ? `export ${varExportDeclare} ` : "module.exports."
|
||||
let funcExportSyntax = moduleType === "esm" ? `export ` : "module.exports."
|
||||
|
||||
Object.entries(values).forEach(([name,value])=>{
|
||||
if(Array.isArray(value) || isPlainObject(value)){
|
||||
results.push(`${varExportDeclare} ${name} = ${JSON.stringify(value,null,4)}`)
|
||||
}else if(typeof(value)==="function"){
|
||||
if(value.prototype){
|
||||
results.push(value.toString())
|
||||
}else{// 箭头函数
|
||||
results.push(`const ${name} = ${value.toString()}`)
|
||||
}
|
||||
}else{
|
||||
results.push(`${varExportDeclare} ${name} = ${JSON.stringify(value)}`)
|
||||
}
|
||||
})
|
||||
|
||||
if(moduleType === "esm"){
|
||||
results.push(`export {\n\t${Object.keys(values).join(",\n\t")}\n}`)
|
||||
}else{ fu
|
||||
results.push(`module.exports = {\n\t${Object.keys(values).join(",\n\t")}\n}`)
|
||||
}
|
||||
|
||||
return results.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建js文件
|
||||
* @param {*} filename
|
||||
* @param {*} defaultExports
|
||||
* @param {*} namedExports {name:value}
|
||||
*
|
||||
* @param {*} moduleType
|
||||
*/
|
||||
function createJsModuleFile(filename,defaultExports={},namedExports={},moduleType="esm"){
|
||||
let jsContents = []
|
||||
if(moduleType === "esm"){
|
||||
Object.entries(namedExports).forEach(([name,value])=>{
|
||||
|
||||
})
|
||||
jsContents.push
|
||||
}else{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function escape(str){
|
||||
return str
|
||||
.replaceAll("\\t","\t")
|
||||
.replaceAll("\\n","\n")
|
||||
.replaceAll("\\b","\b")
|
||||
.replaceAll("\\r","\r")
|
||||
.replaceAll("\\f","\f")
|
||||
.replaceAll("\\'","\'")
|
||||
.replaceAll('\\"','\"')
|
||||
.replaceAll('\\v','\v')
|
||||
.replaceAll("\\\\",'\\')
|
||||
}
|
||||
|
||||
|
||||
let i18nScope,t
|
||||
try{
|
||||
// @voerkai18n/cli工程本身使用了voerkai18n,即@voerkai18n/cli的extract和compile依赖于其自己生成的languages运行时
|
||||
// 而@voerkai18n/cli又用来编译多语言包,这样产生了鸡生蛋问题
|
||||
// extract与compile调试阶段如果t函数无法使用(即编译的languages无法正常使用),则需要提供t函数
|
||||
const language = require('./languages');
|
||||
t = language.t
|
||||
i18nScope = language.i18nScope
|
||||
}catch{
|
||||
t=v=>v
|
||||
i18nScope={change:()=>{} }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
findModuleType,
|
||||
createPackageJsonFile,
|
||||
isPlainObject,
|
||||
getCurrentPackageJson,
|
||||
getCurrentProjectRootFolder,
|
||||
escape,
|
||||
i18nScope,
|
||||
t
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,8 @@
|
||||
"description": "",
|
||||
"main": "currency.formatters.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"release": "npm version patch && pnpm publish --no-git-checks --access public"
|
||||
},
|
||||
"exports":{
|
||||
|
||||
|
2
packages/runtime/dist/index.cjs
vendored
2
packages/runtime/dist/index.cjs
vendored
File diff suppressed because one or more lines are too long
2
packages/runtime/dist/index.cjs.map
vendored
2
packages/runtime/dist/index.cjs.map
vendored
File diff suppressed because one or more lines are too long
2
packages/runtime/dist/index.esm.js
vendored
2
packages/runtime/dist/index.esm.js
vendored
File diff suppressed because one or more lines are too long
2
packages/runtime/dist/index.esm.js.map
vendored
2
packages/runtime/dist/index.esm.js.map
vendored
File diff suppressed because one or more lines are too long
92
packages/runtime/dist/runtime.cjs
vendored
92
packages/runtime/dist/runtime.cjs
vendored
@ -1,5 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
var require$$2 = require('@voerkai18n/utils');
|
||||
|
||||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||
|
||||
var require$$2__default = /*#__PURE__*/_interopDefaultLegacy(require$$2);
|
||||
|
||||
/**
|
||||
*
|
||||
* 简单的事件触发器
|
||||
@ -129,7 +135,7 @@ var scope = class i18nScope {
|
||||
const loader = this.loaders[newLanguage];
|
||||
if(typeof(loader) === "function"){
|
||||
try{
|
||||
this._messages = (await loader()).default;
|
||||
this._messages = (await loader()).default;
|
||||
this._activeLanguage = newLanguage;
|
||||
}catch(e){
|
||||
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
||||
@ -214,7 +220,9 @@ var formatters$1 = {
|
||||
|
||||
const EventEmitter = eventemitter;
|
||||
const i18nScope = scope;
|
||||
let formatters = formatters$1;
|
||||
const { getDataTypeName,isNumber,isPlainObject,deepMerge } = require$$2__default["default"];
|
||||
let inlineFormatters = formatters$1; // 内置格式化器
|
||||
|
||||
|
||||
|
||||
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
|
||||
@ -239,79 +247,7 @@ 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"];
|
||||
|
||||
/**
|
||||
* 获取指定变量类型名称
|
||||
* getDataTypeName(1) == Number
|
||||
* getDataTypeName("") == String
|
||||
* getDataTypeName(null) == Null
|
||||
* getDataTypeName(undefined) == Undefined
|
||||
* getDataTypeName(new Date()) == Date
|
||||
* getDataTypeName(new Error()) == Error
|
||||
*
|
||||
* @param {*} v
|
||||
* @returns
|
||||
*/
|
||||
function getDataTypeName(v){
|
||||
if (v === null) return 'Null'
|
||||
if (v === undefined) return 'Undefined'
|
||||
if(typeof(v)==="function") return "Function"
|
||||
return v.constructor && v.constructor.name;
|
||||
}function isPlainObject(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(value){
|
||||
return !isNaN(parseInt(value))
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 简单进行对象合并
|
||||
*
|
||||
* options={
|
||||
* array:0 , // 数组合并策略,0-替换,1-合并,2-去重合并
|
||||
* object:0, // 对象合并策略,0-替换,1-合并,2-去重合并
|
||||
* }
|
||||
*
|
||||
* @param {*} toObj
|
||||
* @param {*} formObj
|
||||
* @returns 合并后的对象
|
||||
*/
|
||||
function deepMerge(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(results[key],value,options);
|
||||
}
|
||||
}else {
|
||||
results[key] = value;
|
||||
}
|
||||
}else {
|
||||
results[key] = value;
|
||||
}
|
||||
});
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
通过正则表达式对原始文本内容进行解析匹配后得到的
|
||||
@ -831,11 +767,11 @@ function translate(message) {
|
||||
// 当前激活语言
|
||||
get activeLanguage(){ return this._settings.activeLanguage}
|
||||
// 默认语言
|
||||
get defaultLanguage(){ return this.this._settings.defaultLanguage}
|
||||
get defaultLanguage(){ return this._settings.defaultLanguage}
|
||||
// 支持的语言列表
|
||||
get languages(){ return this._settings.languages}
|
||||
// 全局格式化器
|
||||
get formatters(){ return formatters }
|
||||
// 内置格式化器
|
||||
get formatters(){ return inlineFormatters }
|
||||
/**
|
||||
* 切换语言
|
||||
*/
|
||||
|
88
packages/runtime/dist/runtime.mjs
vendored
88
packages/runtime/dist/runtime.mjs
vendored
@ -1,3 +1,5 @@
|
||||
import require$$2 from '@voerkai18n/utils';
|
||||
|
||||
/**
|
||||
*
|
||||
* 简单的事件触发器
|
||||
@ -127,7 +129,7 @@ var scope = class i18nScope {
|
||||
const loader = this.loaders[newLanguage];
|
||||
if(typeof(loader) === "function"){
|
||||
try{
|
||||
this._messages = (await loader()).default;
|
||||
this._messages = (await loader()).default;
|
||||
this._activeLanguage = newLanguage;
|
||||
}catch(e){
|
||||
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
||||
@ -212,7 +214,9 @@ var formatters$1 = {
|
||||
|
||||
const EventEmitter = eventemitter;
|
||||
const i18nScope = scope;
|
||||
let formatters = formatters$1;
|
||||
const { getDataTypeName,isNumber,isPlainObject,deepMerge } = require$$2;
|
||||
let inlineFormatters = formatters$1; // 内置格式化器
|
||||
|
||||
|
||||
|
||||
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
|
||||
@ -237,79 +241,7 @@ 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"];
|
||||
|
||||
/**
|
||||
* 获取指定变量类型名称
|
||||
* getDataTypeName(1) == Number
|
||||
* getDataTypeName("") == String
|
||||
* getDataTypeName(null) == Null
|
||||
* getDataTypeName(undefined) == Undefined
|
||||
* getDataTypeName(new Date()) == Date
|
||||
* getDataTypeName(new Error()) == Error
|
||||
*
|
||||
* @param {*} v
|
||||
* @returns
|
||||
*/
|
||||
function getDataTypeName(v){
|
||||
if (v === null) return 'Null'
|
||||
if (v === undefined) return 'Undefined'
|
||||
if(typeof(v)==="function") return "Function"
|
||||
return v.constructor && v.constructor.name;
|
||||
}function isPlainObject(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(value){
|
||||
return !isNaN(parseInt(value))
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 简单进行对象合并
|
||||
*
|
||||
* options={
|
||||
* array:0 , // 数组合并策略,0-替换,1-合并,2-去重合并
|
||||
* object:0, // 对象合并策略,0-替换,1-合并,2-去重合并
|
||||
* }
|
||||
*
|
||||
* @param {*} toObj
|
||||
* @param {*} formObj
|
||||
* @returns 合并后的对象
|
||||
*/
|
||||
function deepMerge(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(results[key],value,options);
|
||||
}
|
||||
}else {
|
||||
results[key] = value;
|
||||
}
|
||||
}else {
|
||||
results[key] = value;
|
||||
}
|
||||
});
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
通过正则表达式对原始文本内容进行解析匹配后得到的
|
||||
@ -829,11 +761,11 @@ function translate(message) {
|
||||
// 当前激活语言
|
||||
get activeLanguage(){ return this._settings.activeLanguage}
|
||||
// 默认语言
|
||||
get defaultLanguage(){ return this.this._settings.defaultLanguage}
|
||||
get defaultLanguage(){ return this._settings.defaultLanguage}
|
||||
// 支持的语言列表
|
||||
get languages(){ return this._settings.languages}
|
||||
// 全局格式化器
|
||||
get formatters(){ return formatters }
|
||||
// 内置格式化器
|
||||
get formatters(){ return inlineFormatters }
|
||||
/**
|
||||
* 切换语言
|
||||
*/
|
||||
|
@ -1,6 +1,8 @@
|
||||
const EventEmitter = require("./eventemitter")
|
||||
const i18nScope = require("./scope.js")
|
||||
let formatters = require("./formatters")
|
||||
const { getDataTypeName,isNumber,isPlainObject,deepMerge } = require("@voerkai18n/utils")
|
||||
let inlineFormatters = require("./formatters") // 内置格式化器
|
||||
|
||||
|
||||
|
||||
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
|
||||
@ -33,80 +35,7 @@ function hasInterpolation(str){
|
||||
return str.includes("{") && str.includes("}")
|
||||
}
|
||||
const DataTypes = ["String","Number","Boolean","Object","Array","Function","Error","Symbol","RegExp","Date","Null","Undefined","Set","Map","WeakSet","WeakMap"]
|
||||
|
||||
/**
|
||||
* 获取指定变量类型名称
|
||||
* getDataTypeName(1) == Number
|
||||
* getDataTypeName("") == String
|
||||
* getDataTypeName(null) == Null
|
||||
* getDataTypeName(undefined) == Undefined
|
||||
* getDataTypeName(new Date()) == Date
|
||||
* getDataTypeName(new Error()) == Error
|
||||
*
|
||||
* @param {*} v
|
||||
* @returns
|
||||
*/
|
||||
function getDataTypeName(v){
|
||||
if (v === null) return 'Null'
|
||||
if (v === undefined) return 'Undefined'
|
||||
if(typeof(v)==="function") return "Function"
|
||||
return v.constructor && v.constructor.name;
|
||||
};
|
||||
function isPlainObject(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(value){
|
||||
return !isNaN(parseInt(value))
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 简单进行对象合并
|
||||
*
|
||||
* options={
|
||||
* array:0 , // 数组合并策略,0-替换,1-合并,2-去重合并
|
||||
* object:0, // 对象合并策略,0-替换,1-合并,2-去重合并
|
||||
* }
|
||||
*
|
||||
* @param {*} toObj
|
||||
* @param {*} formObj
|
||||
* @returns 合并后的对象
|
||||
*/
|
||||
function deepMerge(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(results[key],value,options)
|
||||
}
|
||||
}else{
|
||||
results[key] = value
|
||||
}
|
||||
}else{
|
||||
results[key] = value
|
||||
}
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
通过正则表达式对原始文本内容进行解析匹配后得到的
|
||||
@ -649,11 +578,11 @@ function translate(message) {
|
||||
// 当前激活语言
|
||||
get activeLanguage(){ return this._settings.activeLanguage}
|
||||
// 默认语言
|
||||
get defaultLanguage(){ return this.this._settings.defaultLanguage}
|
||||
get defaultLanguage(){ return this._settings.defaultLanguage}
|
||||
// 支持的语言列表
|
||||
get languages(){ return this._settings.languages}
|
||||
// 全局格式化器
|
||||
get formatters(){ return formatters }
|
||||
// 内置格式化器
|
||||
get formatters(){ return inlineFormatters }
|
||||
/**
|
||||
* 切换语言
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@voerkai18n/runtime",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"description": "Voerkai18n Runtime",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "dist/index.esm.js",
|
||||
@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "rollup -c",
|
||||
"release": "npm version patch && npm run build && npm publish -access public"
|
||||
"release": "npm version patch && pnpm publish --no-git-checks --access public"
|
||||
},
|
||||
"exports": {
|
||||
"import": "./dist/index.esm.js",
|
||||
@ -33,5 +33,8 @@
|
||||
"rollup": "^2.69.0",
|
||||
"rollup-plugin-clear": "^2.0.7",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@voerkai18n/utils": "workspace:^1.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
|
||||
# @voerkai18n/runtime
|
||||
|
||||
|
||||
`voerkai18n`运行时核心代码
|
||||
`voerkai18n`运行时核心代码
|
||||
|
||||
源码与文档:[https://gitee.com/zhangfisher/voerka-i18n](https://gitee.com/zhangfisher/voerka-i18n)
|
@ -95,7 +95,7 @@ module.exports = class i18nScope {
|
||||
const loader = this.loaders[newLanguage]
|
||||
if(typeof(loader) === "function"){
|
||||
try{
|
||||
this._messages = (await loader()).default
|
||||
this._messages = (await loader()).default
|
||||
this._activeLanguage = newLanguage
|
||||
}catch(e){
|
||||
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`)
|
||||
|
376
packages/utils/index.js
Normal file
376
packages/utils/index.js
Normal file
@ -0,0 +1,376 @@
|
||||
const path = require("path")
|
||||
const shelljs = require("shelljs")
|
||||
const fs = require("fs-extra")
|
||||
|
||||
/**
|
||||
*
|
||||
* 匹配指定路径或文件名称
|
||||
*
|
||||
* const matcher = fileMatcher([
|
||||
* "<pattern>", // 匹配正则表达式字符串
|
||||
* "!<pattern>", // 以!开头代表否定匹配
|
||||
* /正则表达式/
|
||||
* ],{
|
||||
* basePath:"<指定一个基准目录,所有不是以此开头的均视为不匹配>",
|
||||
* defaultPatterns:["<默认排除的模式>","<默认排除的模式>","<默认排除的模式>"],
|
||||
* debug:<true/>false,是否输出调试信息,当=true时,.test()方法返回[<true/false>,pattern] *
|
||||
* })
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {*} patterns
|
||||
* @param {*} basePath 如果指定basePath,则所有不是以basePath开头的文件都排除
|
||||
* @param {*} defaultPatterns 默认的匹配模式
|
||||
* @param {*} debug 是否输出调试信息
|
||||
*/
|
||||
|
||||
function fileMatcher(patterns,{basePath,defaultPatterns=[],debug=true}={}) {
|
||||
if(basePath) {
|
||||
basePath = path.normalize(basePath)
|
||||
}
|
||||
//[[pattern,exclude],[pattern,false],[pattern,true]]
|
||||
let finalPatterns = []
|
||||
let inputPatterns = Array.isArray(patterns) ? patterns : (patterns ? [patterns] : [])
|
||||
|
||||
// 默认排除模式
|
||||
if(defaultPatterns.length===0){
|
||||
finalPatterns.push([/.*\/node_modules\/.*/,true])
|
||||
finalPatterns.push([/.*\/languages\/.*/,true]) // 默认排除语言文件
|
||||
finalPatterns.push([/\.babelrc/,true])
|
||||
finalPatterns.push([/babel\.config\.js/,true])
|
||||
finalPatterns.push([/package\.json$/,true])
|
||||
finalPatterns.push([/vite\.config\.js$/,true])
|
||||
finalPatterns.push([/^plugin-vue:.*/,true])
|
||||
}
|
||||
|
||||
inputPatterns.forEach(pattern=>{
|
||||
if(typeof pattern === "string"){
|
||||
pattern.replaceAll("**",".*")
|
||||
pattern.replaceAll("?","[^\/]?")
|
||||
pattern.replaceAll(/(?<!\.)\*/g,"[^\/]*")
|
||||
// 以!开头的表示排除
|
||||
if(pattern.startsWith("!")){
|
||||
finalPatterns.unshift([new RegExp(pattern.substring(1),"g"),true])
|
||||
}else{
|
||||
finalPatterns.push([new RegExp(pattern,"g"),false])
|
||||
}
|
||||
}else{
|
||||
finalPatterns.push([pattern,false])
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
patterns:finalPatterns,
|
||||
basePath,
|
||||
test: (filename) => {
|
||||
let isMatched = false
|
||||
let file = filename
|
||||
// 如果指定basePath,则文件名称必须是以basePath开头
|
||||
if(basePath){
|
||||
if(path.isAbsolute(file)){
|
||||
if(!path.normalize(file).startsWith(basePath)){
|
||||
return debug ? [false,`!^${basePath}`] : false
|
||||
}else{
|
||||
isMatched = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if(finalPatterns.length===0){
|
||||
return debug ? [true,"*"] : true
|
||||
}else{
|
||||
for(const pattern of finalPatterns){
|
||||
pattern[0].lastIndex = 0
|
||||
if(pattern[1]===true){
|
||||
if(pattern[0].test(file)) return debug ? [false,pattern[0].toString()] : false
|
||||
}else{
|
||||
if(pattern[0].test(file)) return debug ? [true,pattern[0].toString()] : true
|
||||
}
|
||||
}
|
||||
}
|
||||
return debug ? [isMatched,"*"] : isMatched
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以floder为基准向上查找文件package.json,并返回package.json所在的文件夹
|
||||
* @param {*} folder 起始文件夹,如果没有指定,则取当前文件夹
|
||||
* @param {*} exclueCurrent 如果=true,则folder的父文件夹开始查找
|
||||
* @returns
|
||||
*/
|
||||
function getProjectRootFolder(folder="./",exclueCurrent=false){
|
||||
if(!path.isAbsolute(folder)){
|
||||
folder = path.join(process.cwd(),folder)
|
||||
}
|
||||
try{
|
||||
const pkgFile =exclueCurrent ?
|
||||
path.join(folder, "..", "package.json")
|
||||
: path.join(folder, "package.json")
|
||||
if(fs.existsSync(pkgFile)){
|
||||
return path.dirname(pkgFile)
|
||||
}
|
||||
const parent = path.dirname(folder)
|
||||
if(parent===folder) return null
|
||||
return getProjectRootFolder(parent,false)
|
||||
}catch(e){
|
||||
return process.cwd()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 自动获取当前项目的languages
|
||||
*
|
||||
* 1.
|
||||
*
|
||||
* @param {*} location
|
||||
*/
|
||||
function getProjectLanguageFolder(location="./"){
|
||||
// 绝对路径
|
||||
if(!path.isAbsolute(location)){
|
||||
location = path.join(process.cwd(),location)
|
||||
}
|
||||
|
||||
// 发现当前项目根目录
|
||||
const projectRoot = getProjectRootFolder(location)
|
||||
|
||||
const searchFolders = [
|
||||
path.join(location,"src","languages"),
|
||||
path.join(location,"languages")
|
||||
]
|
||||
|
||||
for(let folder of searchFolders){
|
||||
if(fs.existsSync(folder)){
|
||||
return folder
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据当前输入的文件夹位置自动确定源码文件夹位置
|
||||
*
|
||||
* - 如果没有指定,则取当前文件夹
|
||||
* - 如果指定是非绝对路径,则以当前文件夹作为base
|
||||
* - 查找pack
|
||||
* - 如果该文件夹中存在src,则取src下的文件夹
|
||||
* -
|
||||
*
|
||||
* @param {*} location
|
||||
* @returns
|
||||
*/
|
||||
function getProjectSourceFolder(location){
|
||||
if(!location) {
|
||||
location = process.cwd()
|
||||
}else{
|
||||
if(!path.isAbsolute(location)){
|
||||
location = path.join(process.cwd(),location)
|
||||
}
|
||||
}
|
||||
let projectRoot = getProjectRootFolder(location)
|
||||
// 如果当前工程存在src文件夹,则自动使用该文件夹作为源文件夹
|
||||
if(fs.existsSync(path.join(projectRoot,"src"))){
|
||||
projectRoot = path.join(projectRoot,"src")
|
||||
}
|
||||
return projectRoot
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取指定文件夹的package.json文件,如果当前文件夹没有package.json文件,则向上查找
|
||||
* @param {*} folder
|
||||
* @param {*} exclueCurrent = true 排除folder,从folder的父级开始查找
|
||||
* @returns
|
||||
*/
|
||||
function getCurrentPackageJson(folder,exclueCurrent=true){
|
||||
let projectFolder = getCurrentProjectRootFolder(folder,exclueCurrent)
|
||||
if(projectFolder){
|
||||
return fs.readJSONSync(path.join(projectFolder,"package.json"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 返回当前项目的模块类型
|
||||
*
|
||||
* 从当前文件夹开始向上查找package.json文件,并解析出语言包的类型
|
||||
*
|
||||
* @param {*} folder
|
||||
*/
|
||||
function findModuleType(folder){
|
||||
let packageJson = getCurrentPackageJson(folder)
|
||||
try{
|
||||
return packageJson.type || "commonjs"
|
||||
}catch(e){
|
||||
return "esm"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否已经安装了依赖
|
||||
*
|
||||
* isInstallDependent("@voerkai18n/runtime")
|
||||
*
|
||||
*/
|
||||
function isInstallDependent(url){
|
||||
try{
|
||||
// 简单判断是否存在该文件夹node_modules/@voerkai18n/runtime
|
||||
let projectFolder = getCurrentProjectRootFolder(process.cwd())
|
||||
if(fs.existsSync(path.join(projectFolder,"node_modules","@voerkai18n/runtime"))){
|
||||
return true
|
||||
}
|
||||
// 如果不存在,则尝试require
|
||||
require(url)
|
||||
}catch(e){
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是JSON对象
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
function isPlainObject(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(value){
|
||||
return !isNaN(parseInt(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测当前工程是否是git工程
|
||||
*/
|
||||
function isGitRepo(){
|
||||
return shelljs.exec("git status", {silent: true}).code === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单进行对象合并
|
||||
*
|
||||
* options={
|
||||
* array:0 , // 数组合并策略,0-替换,1-合并,2-去重合并
|
||||
* }
|
||||
*
|
||||
* @param {*} toObj
|
||||
* @param {*} formObj
|
||||
* @returns 合并后的对象
|
||||
*/
|
||||
function deepMerge(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(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(v){
|
||||
if (v === null) return 'Null'
|
||||
if (v === undefined) return 'Undefined'
|
||||
if(typeof(v)==="function") return "Function"
|
||||
return v.constructor && v.constructor.name;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 在当前工程自动安装@voerkai18n/runtime
|
||||
* @param {*} srcPath
|
||||
* @param {*} opts
|
||||
*/
|
||||
function installVoerkai18nRuntime(srcPath){
|
||||
const projectFolder = getCurrentProjectRootFolder(srcPath || process.cwd())
|
||||
if(fs.existsSync("pnpm-lock.yaml")){
|
||||
shelljs.exec("pnpm add @voerkai18n/runtime")
|
||||
}else if(fs.existsSync("yarn.lock")){
|
||||
shelljs.exec("yarn add @voerkai18n/runtime")
|
||||
}else{
|
||||
shelljs.exec("npm install @voerkai18n/runtime")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在指定文件夹下创建package.json文件
|
||||
* @param {*} targetPath
|
||||
* @param {*} moduleType
|
||||
* @returns
|
||||
*/
|
||||
function createPackageJsonFile(targetPath,moduleType="auto"){
|
||||
if(moduleType==="auto"){
|
||||
moduleType = findModuleType(targetPath)
|
||||
}
|
||||
const packageJsonFile = path.join(targetPath, "package.json")
|
||||
if(["esm","es","module"].includes(moduleType)){
|
||||
fs.writeFileSync(packageJsonFile,JSON.stringify({type:"module",license:"MIT"},null,4))
|
||||
if(moduleType==="module"){
|
||||
moduleType = "esm"
|
||||
}
|
||||
}else{
|
||||
fs.writeFileSync(packageJsonFile,JSON.stringify({license:"MIT"},null,4))
|
||||
}
|
||||
return moduleType
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
fileMatcher, // 文件名称匹配器
|
||||
getProjectRootFolder, // 查找获取项目根目录
|
||||
createPackageJsonFile, // 创建package.json文件
|
||||
getProjectSourceFolder, // 获取项目源码目录
|
||||
getCurrentPackageJson, // 查找获取当前项目package.json
|
||||
getProjectLanguageFolder, // 获取当前项目的languages目录
|
||||
findModuleType, // 获取当前项目的模块类型
|
||||
isInstallDependent, // 判断是否已经安装了依赖
|
||||
installVoerkai18nRuntime, // 在当前工程自动安装@voerkai18n/runtime
|
||||
isPlainObject, // 判断是否是普通对象
|
||||
isNumber, // 判断是否是数字
|
||||
deepMerge, // 深度合并对象
|
||||
getDataTypeName, // 获取指定变量类型名称
|
||||
isGitRepo, // 判断当前工程是否是git工程
|
||||
}
|
17
packages/utils/package.json
Normal file
17
packages/utils/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@voerkai18n/utils",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"release": "npm version patch && npm run build && npm publish -access public"
|
||||
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs-extra": "^10.0.1",
|
||||
"shelljs": "^0.8.5"
|
||||
}
|
||||
}
|
166
packages/vite/index.js
Normal file
166
packages/vite/index.js
Normal file
@ -0,0 +1,166 @@
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
const { fileMatcher,getProjectRootFolder,getProjectLanguageFolder } = require("@voerkai18n/utils")
|
||||
|
||||
//const TranslateRegex = /\bt\(\s*("|'){1}(?:((?<namespace>\w+)::))?(?<text>[^\1]*?)(?=(\1\s*\))|(\1\s*\,))/gm
|
||||
|
||||
const TranslateRegex =/(?<=\bt\(\s*("|'){1})(?<text>[^\1]*?)(?=(\1\s*\))|(\1\s*\,))/gm
|
||||
|
||||
// 匹配正则表达式
|
||||
const importTRegex = /^[^\w\r\n]*import\s*\{(.*)\bt\b(.*)\}\sfrom/gm
|
||||
|
||||
|
||||
/**
|
||||
* 读取idMap.js文件
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
function readIdMapFile(options){
|
||||
let { location } = options
|
||||
let searchIdMapFiles = []
|
||||
if(!path.isAbsolute(location)){
|
||||
location = path.join(process.cwd(),location)
|
||||
}
|
||||
searchIdMapFiles.push(path.join(location,"src","languages/idMap.js"))
|
||||
searchIdMapFiles.push(path.join(location,"languages/idMap.js"))
|
||||
searchIdMapFiles.push(path.join(location,"idMap.js"))
|
||||
|
||||
let projectRoot = getProjectRootFolder(location)
|
||||
searchIdMapFiles.push(path.join(projectRoot,"src","languages/idMap.js"))
|
||||
searchIdMapFiles.push(path.join(projectRoot,"languages/idMap.js"))
|
||||
searchIdMapFiles.push(path.join(projectRoot,"idMap.js"))
|
||||
|
||||
let idMapFile
|
||||
for( idMapFile of searchIdMapFiles){
|
||||
// 如果不存在idMap文件,则尝试从location/languages/中导入
|
||||
if(fs.existsSync(idMapFile)){
|
||||
try{
|
||||
// 由于idMap.js可能是esm或cjs,并且babel插件不支持异步
|
||||
// 当require(idMap.js)失败时,对esm模块尝试采用直接读取的方式
|
||||
return require(idMapFile)
|
||||
}catch(e){
|
||||
// 出错原因可能是因为无效require esm模块,由于idMap.js文件格式相对简单,因此尝试直接读取解析
|
||||
try{
|
||||
let idMapContent = fs.readFileSync(idMapFile).toString()
|
||||
idMapContent = idMapContent.trim().replace(/^\s*export\s*default\s/g,"")
|
||||
return JSON.parse(idMapContent)
|
||||
}catch{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 所有尝试完成后触发错误
|
||||
throw new Error(`${idMapFile}文件不存在,无法对翻译文本进行转换。\n原因可能是babel-plugin-voerkai18n插件的location参数未指向有效的语言包所在的目录。`)
|
||||
|
||||
}
|
||||
|
||||
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 replaceCode(code, idmap) {
|
||||
return code.replaceAll(TranslateRegex, (text) => {
|
||||
let message = escape(text)
|
||||
if(message in idmap) {
|
||||
return idmap[message]
|
||||
}else{
|
||||
return text
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定代码中是否导入了Translate函数
|
||||
* @param {*} code
|
||||
* @returns
|
||||
*/
|
||||
function hasImportTranslateFunction(code){
|
||||
return importTRegex.test(code)
|
||||
}
|
||||
|
||||
/**
|
||||
options = {
|
||||
location: "./languages",
|
||||
autoImport:true
|
||||
}
|
||||
|
||||
*/
|
||||
module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
let options = Object.assign({
|
||||
location: "./", // 指定当前工程目录
|
||||
autoImport: true, // 是否自动导入t函数
|
||||
debug:false, // 是否输出调试信息,当=true时,在控制台输出转换匹配的文件清单
|
||||
patterns:[
|
||||
"!(?<!.vue\?.*).(css|json|scss|less|sass)$", // 排除所有css文件
|
||||
/\.vue(\?.*)?/, // 所有vue文件
|
||||
] // 提取范围
|
||||
},opts)
|
||||
|
||||
let { debug,patterns,autoImport } = options
|
||||
|
||||
let projectRoot = getProjectRootFolder(options.location)
|
||||
let languageFolder = getProjectLanguageFolder(projectRoot)
|
||||
|
||||
if(debug){
|
||||
console.log("Project root: ",projectRoot)
|
||||
console.log("Language folder: ",languageFolder)
|
||||
}
|
||||
|
||||
const matcher = fileMatcher(patterns,{
|
||||
basePath:projectRoot,
|
||||
debug:debug
|
||||
})
|
||||
let idMap = readIdMapFile(options)
|
||||
|
||||
return {
|
||||
name: 'voerkai18n',
|
||||
async transform(src, id) {
|
||||
let [isMatched,pattern] = debug ? matcher.test(id) : [matcher.test(id),null]
|
||||
if(isMatched){
|
||||
if(debug){
|
||||
console.log(`File=${path.relative(projectRoot,id)}, pattern=[${pattern}], import from "${path.relative(path.dirname(id),languageFolder)}"`)
|
||||
}
|
||||
try{
|
||||
// 判断是否使用了t函数
|
||||
if(TranslateRegex.test(src)){
|
||||
let code = replaceCode(src,idMap)
|
||||
// 如果没有导入t函数,则尝试自动导入
|
||||
if(autoImport && !hasImportTranslateFunction(code)){
|
||||
let importSource = path.relative(path.dirname(id),languageFolder)
|
||||
if(!importSource.startsWith(".")){
|
||||
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}';`)
|
||||
}
|
||||
}
|
||||
return {
|
||||
code,
|
||||
map: null
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
console.warn(`vite-plugin-voerkai18n转换<${id}>文件出错:${e.message}`)
|
||||
}
|
||||
}
|
||||
return {
|
||||
code:src,
|
||||
map: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
packages/vite/package.json
Normal file
15
packages/vite/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@voerkai18n/vite",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"release": "npm version patch && pnpm publish --no-git-checks --access public"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@voerkai18n/utils": "workspace:^1.0.0"
|
||||
}
|
||||
}
|
6
packages/vite/readme.md
Normal file
6
packages/vite/readme.md
Normal file
@ -0,0 +1,6 @@
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
|
||||
|
||||
`@voerkai18n/vite`插件用来进行自动文本映射和导入`t`函数
|
||||
|
||||
源码与文档:[https://gitee.com/zhangfisher/voerka-i18n](https://gitee.com/zhangfisher/voerka-i18n)
|
117
packages/vite/utils.js
Normal file
117
packages/vite/utils.js
Normal file
@ -0,0 +1,117 @@
|
||||
const path = require("path")
|
||||
|
||||
/**
|
||||
*
|
||||
* 匹配指定路径或文件名称
|
||||
*
|
||||
* const matcher = fileMatcher([
|
||||
* "<pattern>", // 匹配正则表达式字符串
|
||||
* "!<pattern>", // 以!开头代表否定匹配
|
||||
* /正则表达式/
|
||||
* ],{
|
||||
* basePath:"<指定一个基准目录,所有不是以此开头的均视为不匹配>",
|
||||
* defaultPatterns:["<默认排除的模式>","<默认排除的模式>","<默认排除的模式>"],
|
||||
* debug:<true/>false,是否输出调试信息,当=true时,.test()方法返回[<true/false>,pattern] *
|
||||
* })
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {*} patterns
|
||||
* @param {*} basePath 如果指定basePath,则所有不是以basePath开头的文件都排除
|
||||
* @param {*} defaultPatterns 默认的匹配模式
|
||||
* @param {*} debug 是否输出调试信息
|
||||
*/
|
||||
|
||||
function fileMatcher(patterns,{basePath,defaultPatterns=[],debug=true}={}) {
|
||||
if(basePath) {
|
||||
basePath = path.normalize(basePath)
|
||||
}
|
||||
//[[pattern,exclude],[pattern,false],[pattern,true]]
|
||||
let finalPatterns = []
|
||||
let inputPatterns = Array.isArray(patterns) ? patterns : (patterns ? [patterns] : [])
|
||||
|
||||
// 默认排除模式
|
||||
if(defaultPatterns.length===0){
|
||||
finalPatterns.push([/.*\/node_modules\/.*/,true])
|
||||
finalPatterns.push([/.*\/languages\/.*/,true]) // 默认排除语言文件
|
||||
finalPatterns.push([/\.babelrc/,true])
|
||||
finalPatterns.push([/babel\.config\.js/,true])
|
||||
finalPatterns.push([/package\.json$/,true])
|
||||
finalPatterns.push([/vite\.config\.js$/,true])
|
||||
finalPatterns.push([/^plugin-vue:.*/,true])
|
||||
}
|
||||
|
||||
inputPatterns.forEach(pattern=>{
|
||||
if(typeof pattern === "string"){
|
||||
pattern.replaceAll("**",".*")
|
||||
pattern.replaceAll("?","[^\/]?")
|
||||
pattern.replaceAll(/(?<!\.)\*/g,"[^\/]*")
|
||||
// 以!开头的表示排除
|
||||
if(pattern.startsWith("!")){
|
||||
finalPatterns.unshift([new RegExp(pattern.substring(1),"g"),true])
|
||||
}else{
|
||||
finalPatterns.push([new RegExp(pattern,"g"),false])
|
||||
}
|
||||
}else{
|
||||
finalPatterns.push([pattern,false])
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
patterns:finalPatterns,
|
||||
basePath,
|
||||
test: (filename) => {
|
||||
let isMatched = false
|
||||
let file = filename
|
||||
// 如果指定basePath,则文件名称必须是以basePath开头
|
||||
if(basePath){
|
||||
if(path.isAbsolute(file)){
|
||||
if(!path.normalize(file).startsWith(basePath)){
|
||||
return debug ? [false,`!^${basePath}`] : false
|
||||
}else{
|
||||
isMatched = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if(finalPatterns.length===0){
|
||||
return debug ? [true,"*"] : true
|
||||
}else{
|
||||
for(const pattern of finalPatterns){
|
||||
pattern[0].lastIndex = 0
|
||||
if(pattern[1]===true){
|
||||
if(pattern[0].test(file)) return debug ? [false,pattern[0].toString()] : false
|
||||
}else{
|
||||
if(pattern[0].test(file)) return debug ? [true,pattern[0].toString()] : true
|
||||
}
|
||||
}
|
||||
}
|
||||
return debug ? [isMatched,"*"] : isMatched
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getProjectRootFolder(folder,exclueCurrent=false){
|
||||
if(!path.isAbsolute(folder)){
|
||||
folder = path.join(process.cwd(),folder)
|
||||
}
|
||||
try{
|
||||
const pkgFile =exclueCurrent ?
|
||||
path.join(folder, "..", "package.json")
|
||||
: path.join(folder, "package.json")
|
||||
if(fs.existsSync(pkgFile)){
|
||||
return path.dirname(pkgFile)
|
||||
}
|
||||
const parent = path.dirname(folder)
|
||||
if(parent===folder) return null
|
||||
return getProjectRootFolder(parent,false)
|
||||
}catch(e){
|
||||
return process.cwd()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fileMatcher,
|
||||
getProjectRootFolder
|
||||
}
|
@ -3,32 +3,55 @@
|
||||
import { createApp } from 'vue'
|
||||
import Root from './App.vue'
|
||||
import i18nPlugin from '@voerkai18n/vue'
|
||||
import { t, i18nScope } from './languages'
|
||||
import { t,i18nScope } from './languages'
|
||||
const app = createApp(Root)
|
||||
app.use(i18nPlugin,{ t,i18nScope })
|
||||
app.use(i18nPlugin,{ i18nScope })
|
||||
app.mount('#app')
|
||||
|
||||
|
||||
*/
|
||||
|
||||
import { computed,reactive,ref } from "vue"
|
||||
|
||||
export default {
|
||||
install: (app, opts={}) => {
|
||||
let options = Object.assign({
|
||||
t:message=>message,
|
||||
i18nScope:null,
|
||||
}, opts)
|
||||
i18nScope:null, // 当前作用域实例
|
||||
forceUpdate:true, // 当语言切换时是否强制重新渲染
|
||||
}, opts)
|
||||
|
||||
let i18nScope = options.i18nScope
|
||||
if(i18nScope===null){
|
||||
console.warn("@voerkai18n/vue: i18nScope is not provided, use default i18nScope")
|
||||
i18nScope = {change:()=>{}}
|
||||
}
|
||||
|
||||
// 插件只需要安装一次实例
|
||||
if(app.voerkai18n){
|
||||
return
|
||||
}
|
||||
|
||||
let translate = options.t
|
||||
if(typeof(translate)!=="function"){
|
||||
console.warn("@voerkai18n/vue: t function is not provided, use default t function")
|
||||
translate = message=>message
|
||||
}
|
||||
let activeLanguage = ref(i18nScope.global.activeLanguage)
|
||||
|
||||
// 当语言包发生变化时,强制重新渲染组件树
|
||||
i18nScope.global.on((newLanguage)=>{
|
||||
app._instance.update()
|
||||
activeLanguage.value = newLanguage
|
||||
})
|
||||
|
||||
// 全局翻译函数
|
||||
app.config.globalProperties.t = function(){
|
||||
return options.t(...arguments)
|
||||
}
|
||||
|
||||
}
|
||||
// 全局i18n对象
|
||||
app.voerkai18n = options.i18nScope.global
|
||||
app.provide('i18n', reactive({
|
||||
activeLanguage: computed({
|
||||
get: () => activeLanguage,
|
||||
set: (value) => i18nScope.global.change(value).then(()=>{
|
||||
if(options.forceUpdate){
|
||||
app._instance.update()
|
||||
}
|
||||
})
|
||||
}),
|
||||
languages:i18nScope.global.languages,
|
||||
defaultLanguage:i18nScope.global.defaultLanguage,
|
||||
}))
|
||||
}
|
||||
}
|
@ -1,16 +1,26 @@
|
||||
{
|
||||
"name": "@voerkai18n/vue",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"deepmerge": "^4.2.2",
|
||||
"gulp": "^4.0.2",
|
||||
"vinyl": "^2.2.1"
|
||||
}
|
||||
}
|
||||
"name": "@voerkai18n/vue",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"release": "npm version patch && pnpm publish --no-git-checks --access public"
|
||||
},
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./vite-plugin-voerkai18n": "./vite-plugin-voerkai18n.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"deepmerge": "^4.2.2",
|
||||
"gulp": "^4.0.2",
|
||||
"vinyl": "^2.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1",
|
||||
"vite": "^2.8.6",
|
||||
"vite-plugin-inspect": "^0.4.3"
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
|
||||
# @voerkai18n/vue
|
||||
@voerkai18n/vue插件,用来实现语言切换等功能。
|
||||
|
||||
源码与文档:[https://gitee.com/zhangfisher/voerka-i18n](https://gitee.com/zhangfisher/voerka-i18n)
|
3418
pnpm-lock.yaml
generated
3418
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
870
readme.md
870
readme.md
@ -1,7 +1,7 @@
|
||||
|
||||
# ** 测试阶段,有问题请issues **
|
||||
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n/stargazers) [](https://gitee.com/zhangfisher/voerka-i18n/stargazers)
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n/stargazers)
|
||||
|
||||
# 前言
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
```
|
||||
- **@voerkai18/runtime**
|
||||
|
||||
**可选的**,运行时,`@voerkai18/cli`的依赖。大部分情况下不需要手动安装。
|
||||
**可选的**,运行时,`@voerkai18/cli`的依赖。大部分情况下不需要手动安装,一般仅在开发库项目时采用独立的运行时依赖。
|
||||
|
||||
```javascript
|
||||
npm install --save @voerkai18/runtime
|
||||
@ -399,210 +399,7 @@ t("My name is { name | UpperCase | mr }",{name:"tom"})
|
||||
|
||||
`{data | f1 | f2 | f3(1)}`等效于` f3(f2(f1(data)),1)`
|
||||
|
||||
### 自定义格式化器
|
||||
|
||||
当我们使用`voerkai18n compile`编译后,会生成`languages/formatters.js`文件,可以在该文件中自定义您自己的格式化器。
|
||||
|
||||
`formatters.js`文件内容如下:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
// 在所有语言下生效的格式化器
|
||||
"*":{
|
||||
//[格式化名称]:(value)=>{...},
|
||||
//[格式化名称]:(value,arg)=>{...},
|
||||
},
|
||||
// 在所有语言下只作用于特定数据类型的格式化器
|
||||
$types:{
|
||||
// [数据类型名称]:(value)=>{...},
|
||||
// [数据类型名称]:(value)=>{...},
|
||||
},
|
||||
cn:{
|
||||
$types:{
|
||||
// 所有类型的默认格式化器
|
||||
"*":{
|
||||
},
|
||||
Date:{},
|
||||
Number:{},
|
||||
Boolean:{ },
|
||||
String:{},
|
||||
Array:{
|
||||
|
||||
},
|
||||
Object:{
|
||||
|
||||
}
|
||||
},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
//.....
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
// [数据类型名称]:(value)=>{...},
|
||||
},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
//.....更多的格式化器.....
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明:**
|
||||
|
||||
#### 格式化器函数
|
||||
|
||||
**每一个格式化器就是一个普通的同步函数**,不支持异步函数。
|
||||
|
||||
典型的无参数的格式化器:`(value)=>{....返回格式化的结果...}`。
|
||||
|
||||
带参数的格式化器:`(value,arg1,...)=>{....返回格式化的结果...}`,其中`value`是上一个格式化器的输出结果。
|
||||
|
||||
#### 类型格式化器
|
||||
|
||||
可以为每一种数据类型指定一个默认的格式化器,支持对`String`、`Date`、`Error`、`Object`、`Array`、`Boolean`、`Number`等数据类型的格式化。
|
||||
|
||||
当插值变量传入时,如果有定义了对应的的类型格式化器,会先调用该格式化器。
|
||||
|
||||
比如我们定义对`Boolean`类型格式化器,
|
||||
|
||||
```javascript
|
||||
//formatters.js
|
||||
|
||||
module.exports = {
|
||||
// 在所有语言下只作用于特定数据类型的格式化器
|
||||
$types:{
|
||||
Boolean:(value)=> value ? "ON" : "OFF"
|
||||
}
|
||||
}
|
||||
t("灯状态:{status}",true) // === 灯状态:ON
|
||||
t("灯状态:{status}",false) // === 灯状态:OFF
|
||||
```
|
||||
|
||||
在上例中,如果我们想在不同的语言环境下,翻译为不同的显示文本,则可以为不同的语言指定类型格式化器
|
||||
|
||||
```javascript
|
||||
//formatters.js
|
||||
module.exports = {
|
||||
cn:{
|
||||
$types:{
|
||||
Boolean:(value)=> value ? "开" : "关"
|
||||
}
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
Boolean:(value)=> value ? "ON" : "OFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
// 当切换到中文时
|
||||
t("灯状态:{status}",true) // === 灯状态:开
|
||||
t("灯状态:{status}",false) // === 灯状态:关
|
||||
// 当切换到英文时
|
||||
t("灯状态:{status}",true) // === 灯状态:ON
|
||||
t("灯状态:{status}",false) // === 灯状态:OFF
|
||||
```
|
||||
|
||||
**说明:**
|
||||
|
||||
- 完整的类型格式化器定义形式
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
"*":{
|
||||
$types:{...}
|
||||
},
|
||||
cn:{
|
||||
$types:{...}
|
||||
},
|
||||
en:{
|
||||
$types:{....}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在匹配应用格式化时会先在当前语言的`$types`中查找匹配的格式化器,如果找不到再上`*.$types`中查找。
|
||||
|
||||
- `*.$types`代表当所有语言中均没有定义时才匹配的类型格式化。
|
||||
|
||||
- 类型格式化器是默认执行的,不需要指定名称。
|
||||
|
||||
- 当前作用域的格式化器优先于全局的格式化器。(后续)
|
||||
|
||||
#### 通用的格式化器
|
||||
|
||||
类型格式化器只针对特定数据类型,并且会默认调用。而通用的格式化器需要使用`|`管道符进行显式调用。
|
||||
|
||||
同样的,通用的格式化器定义在`languages/formatters.js`中。
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
"*":{
|
||||
$types:{...},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
},
|
||||
cn:{
|
||||
$types:{...},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
},
|
||||
en:{
|
||||
$types:{....},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
[格式化名称]:(value,arg)=>{.....},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每一个格式化器均需要指定一个名称,在进行插值替换时会优先依据当前语言来匹配查找格式化器,如果找不到,再到键名为`*`中查找。
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
"*":{
|
||||
uppercase:(value)=>value
|
||||
},
|
||||
cn:{
|
||||
uppercase:(value)=>["一","二","三","四","五","六","七","八","九","十"][value-1]
|
||||
},
|
||||
en:{
|
||||
uppercase:(value)=>["One","Two","Three","Four","Five","Six","seven","eight","nine","ten"][value-1]
|
||||
},
|
||||
jp:{
|
||||
|
||||
}
|
||||
}
|
||||
// 当切换到中文时
|
||||
t("{value | uppercase}",1) // == 一
|
||||
t("{value | uppercase}",2) // == 二
|
||||
t("{value | uppercase}",3) // == 三
|
||||
// 当切换到英文时
|
||||
t("{value | uppercase}",1) // == One
|
||||
t("{value | uppercase}",2) // == Two
|
||||
t("{value | uppercase}",3) // == Three
|
||||
// 当切换到日文时,由于在该语言下没有定义uppercase格式式,因此到*中查找
|
||||
t("{value | uppercase}",1) // == 1
|
||||
t("{value | uppercase}",2) // == 2
|
||||
t("{value | uppercase}",3) // == 3
|
||||
```
|
||||
|
||||
### 作用域格式化器
|
||||
|
||||
定义在`languages/formatters.js`里面的格式化器仅在当前工程生效,也就是仅在当前作用域生效。一般由应用开发者自行扩展。
|
||||
|
||||
关于作用域的概念详见后续介绍。
|
||||
|
||||
### 全局格式化器
|
||||
|
||||
定义在`@voerkai18n/runtime`里面的格式化器则全局有效,在所有场合均可以使用,但是其优先级低于作用域内的同名格式化器。目前内置的格式化器有:
|
||||
|
||||
| 名称 | | 说明 |
|
||||
| ---- | ---- | ---- |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
|
||||
### 扩展格式化器
|
||||
|
||||
除了可以在当前项目`languages/formatters.js`自定义格式化器和`@voerkai18n/runtime`里面的全局格式化器外,单列了`@voerkai18n/formatters`项目用来包含了更多的格式化器。
|
||||
|
||||
作为开源项目,欢迎大家提交贡献更多的格式化器。
|
||||
|
||||
## 日期时间
|
||||
|
||||
@ -792,6 +589,181 @@ languages
|
||||
|
||||
`名称空间`仅仅是为了解决当翻译内容太多时的分类问题。
|
||||
|
||||
|
||||
|
||||
## 切换语言
|
||||
|
||||
可以通过全局单例或当前作用域实例切换语言。
|
||||
|
||||
```javascript
|
||||
import { i18nScope } from "./languages"
|
||||
|
||||
// 切换到英文
|
||||
await i18nScope.change("en")
|
||||
// VoerkaI18n是一个全局单例,可以直接访问
|
||||
VoerkaI18n.change("en")
|
||||
```
|
||||
|
||||
侦听语言切换事件:
|
||||
|
||||
```javascript
|
||||
import { i18nScope } from "./languages"
|
||||
|
||||
// 切换到英文
|
||||
i18nScope.on((newLanguage)=>{
|
||||
...
|
||||
})
|
||||
//
|
||||
VoerkaI18n.on((newLanguage)=>{
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
## Vue应用
|
||||
|
||||
在`Vue3`应用中引入`voerkai18n`来添加国际化应用需要由两个插件来简化应用。
|
||||
|
||||
- **@voerkai18n/vue**
|
||||
|
||||
**Vue插件**,在初始化Vue应用时引入,提供访问`当前语言`、`切换语言`、`自动更新`等功能。
|
||||
|
||||
- **@voerkai18n/vite**
|
||||
|
||||
**Vite插件**,在`vite.config.js`中配置,用来实现`自动文本映射`、`自动导入t函数`等功能。
|
||||
|
||||
|
||||
|
||||
`@voerkai18n/vue`和`@voerkai18n/vite`两件插件相互配合,安装配置好这两个插件后,就可以在Vue文件使用多语言`t`函数。
|
||||
|
||||
**重点:`t`函数会在使用`@voerkai18n/vite`插件后自动注入,因此在Vue文件中可以直接使用。**
|
||||
|
||||
```Vue
|
||||
<Script setup>
|
||||
// 如果没有在vite.config.js中配置`@voerkai18n/vite`插件,则需要手工导入t函数
|
||||
// import { t } from "./languages"
|
||||
</Script>
|
||||
<script>
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
username:"",
|
||||
password:"",
|
||||
title:t("认证")
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
login(){
|
||||
alert(t("登录"))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h1>{{ t("请输入用户名称") }}</h1>
|
||||
<div>
|
||||
<span>{{t("用户名:")}}</span><input type="text" :placeholder="t('邮件/手机号码/帐号')"/>
|
||||
<span>{{t("密码:")}}</span><input type="password" :placeholder="t('至少6位的密码')"/>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="login">{{t("登录")}}</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
**说明:**
|
||||
|
||||
- 事实上,就算没有`@voerkai18n/vue`和`@voerkai18n/vite`两件插件相互配合,只需要导入`t`函数也就可以直接使用。这两个插件只是很简单的封装而已。
|
||||
- 如果要在应用中进行`语言动态切换`,则需要在应用中引入`@voerkai18n/vue`,请参阅`@voerkai18n/vue`插件使用说明。
|
||||
- `@voerkai18n/vite`的使用请参阅后续说明。
|
||||
|
||||
## React应用
|
||||
|
||||
|
||||
|
||||
# 高级特性
|
||||
|
||||
## 运行时
|
||||
|
||||
`@voerkai18n/runtime`是`voerkai18n`的运行时依赖,支持两种依赖方式。
|
||||
|
||||
- **源码依赖**
|
||||
|
||||
默认情况下,运行`voerkai18n compile`时会在`languages`文件下生成运行时文件`runtime.js`,该文件被`languages/index.js`引入,里面是核心运行时`ES6`源代码(`@voerkai18n/runtime`源码),也就是在您的工程中是直接引入的运行时代码,因此就不需要额外安装`@voerkai18n/runtime`了。
|
||||
|
||||
此时,`@voerkai18n/runtime`源码就成为您工程是一部分。
|
||||
|
||||
- **库依赖**
|
||||
|
||||
当运行`voerkai18n compile --no-inline-runtime`时,就不会生成运行时文件`runtime.js`,而是采用`import "@voerkai18n/runtime`的方式导入运行时,此时会自动/手动安装`@voerkai18n/runtime`到运行依赖中。
|
||||
|
||||
|
||||
|
||||
**那么应该选择`源码依赖`还是`库依赖`呢?**
|
||||
|
||||
问题的重点在于,在`monorepo`工程或者`开发库`时,`源码依赖`会导致存在重复的运行时源码。而采用`库依赖`,则不存在此问题。因此:
|
||||
|
||||
- 普通应用采用`源码依赖`方式,运行`voerkai18n compile `来编译语言包。
|
||||
- `monorepo`工程或者`开发库`采用`库依赖`,`voerkai18n compile --no-inline-runtime`来编译语言包。
|
||||
|
||||
|
||||
|
||||
**注意:**
|
||||
|
||||
- `@voerkai18n/runtime`发布了`commonjs`和`esm`两个经过`babel/rollup`转码后的`ES5`版本。
|
||||
|
||||
- 每次运行`voerkai18n compile`时均会重新生成`runtime.js`源码文件,为了确保最新的运行时,请及时更新`@voerkai18n/cli`
|
||||
|
||||
- 当升级了`@voerkai18n/runtime`后,需要重新运行`voerkai18n compile`以重新生成`runtime.js`文件。
|
||||
|
||||
|
||||
|
||||
## 文本映射
|
||||
|
||||
虽然`VoerkaI18n`推荐采用`t("中华人民共和国万岁")`形式的符合直觉的翻译形式,而不是采用`t("xxxx.xxx")`这样不符合直觉的形式,但是为什么大部份的国际化方案均采用`t("xxxx.xxx")`形式?
|
||||
|
||||
在我们的方案中,t("中华人民共和国万岁")形式相当于采用原始文本进行查表,语言名形式如下:
|
||||
|
||||
```javascript
|
||||
// en.js
|
||||
{
|
||||
"中华人民共和国":"the people's Republic of China"
|
||||
}
|
||||
// jp.js
|
||||
{
|
||||
"中华人民共和国":"中華人民共和国"
|
||||
}
|
||||
```
|
||||
|
||||
很显然,直接使用文本内容作为`key`,虽然符合直觉,但是会造成大量的冗余信息。因此,`voerkai18n compile`会将之编译成如下:
|
||||
|
||||
```javascript
|
||||
//idMap.js
|
||||
{
|
||||
"1":"中华人民共和国万岁"
|
||||
}
|
||||
// en.js
|
||||
{
|
||||
"1":"Long live the people's Republic of China"
|
||||
}
|
||||
// jp.js
|
||||
{
|
||||
"2":"中華人民共和国"
|
||||
}
|
||||
```
|
||||
|
||||
如此,就消除了在`en.js`、`jp.js`文件中的冗余。但是在源代码文件中还存在`t("中华人民共和国万岁")`,整个运行环境中存在两份副本,一份在源代码文件中,一份在`idMap.js`中。
|
||||
|
||||
为了进一步减少重复内容,因此,我们需要将源代码文件中的`t("中华人民共和国万岁")`更改为`t("1")`,这样就能确保无重复冗余。但是,很显然,我们不可能手动来更改源代码文件,这就需要由`voerkai18n`提供的一个编译区插件来做这一件事了。
|
||||
|
||||
以`babel-plugin-voerkai18n`插件为例,该插件同时还完成一份任务,就是自动读取`voerkai18n compile`生成的`idMap.js`文件,然后将`t("中华人民共和国万岁")`自动更改为`t("1")`,这样就完全消除了重复冗余信息。
|
||||
|
||||
所以,在最终形成的代码中,实际上每一个t函数均是`t("1")`、`t("2")`、`t("3")`、`...`、`t("n")`的形式,最终代码还是采用了用`key`来进行转换,只不过这个过程是自动完成的而已。
|
||||
|
||||
**注意:**
|
||||
|
||||
- 如果没有启用`babel-plugin-voerkai18n`或`vite`等编译区插件,还是可以正常工作,但是会有一份默认语言的冗余信息存在。
|
||||
|
||||
## 多库联动
|
||||
|
||||
`voerkai18n `支持多个库国际化的联动和协作,即**当主程序切换语言时,所有引用依赖库也会跟随主程序进行语言切换**,整个切换过程对所有库开发都是透明的。
|
||||
@ -826,7 +798,243 @@ import { t } from "../../../languages"
|
||||
|
||||
作为国际化解决方案,一般工程的大部份源码中均会使用到翻译函数,这种使用体验比较差。
|
||||
|
||||
为此,我们提供了一个`babel`插件来自动完成翻译函数的自动引入。使用方法如下:
|
||||
为此,我们提供了一个`babel`插件来自动完成翻译函数的自动引入,详见后续`bable`插件、`vite`插件等介绍。
|
||||
|
||||
## 自定义格式化器
|
||||
|
||||
当我们使用`voerkai18n compile`编译后,会生成`languages/formatters.js`文件,可以在该文件中自定义您自己的格式化器。
|
||||
|
||||
`formatters.js`文件内容如下:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
// 在所有语言下生效的格式化器
|
||||
"*":{
|
||||
//[格式化名称]:(value)=>{...},
|
||||
//[格式化名称]:(value,arg)=>{...},
|
||||
},
|
||||
// 在所有语言下只作用于特定数据类型的格式化器
|
||||
$types:{
|
||||
// [数据类型名称]:(value)=>{...},
|
||||
// [数据类型名称]:(value)=>{...},
|
||||
},
|
||||
cn:{
|
||||
$types:{
|
||||
// 所有类型的默认格式化器
|
||||
"*":{
|
||||
},
|
||||
Date:{},
|
||||
Number:{},
|
||||
Boolean:{ },
|
||||
String:{},
|
||||
Array:{
|
||||
|
||||
},
|
||||
Object:{
|
||||
|
||||
}
|
||||
},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
//.....
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
// [数据类型名称]:(value)=>{...},
|
||||
},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
//.....更多的格式化器.....
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 格式化器函数
|
||||
|
||||
**每一个格式化器就是一个普通的同步函数**,不支持异步函数,格式化器函数可以支持无参数或有参数。
|
||||
|
||||
- 无参数的格式化器:`(value)=>{....返回格式化的结果...}`。
|
||||
|
||||
- 带参数的格式化器:`(value,arg1,...)=>{....返回格式化的结果...}`,其中`value`是上一个格式化器的输出结果。
|
||||
|
||||
### 类型格式化器
|
||||
|
||||
可以为每一种数据类型指定一个默认的格式化器,支持对`String`、`Date`、`Error`、`Object`、`Array`、`Boolean`、`Number`等数据类型的格式化。
|
||||
|
||||
当插值变量传入时,如果有定义了对应的的类型格式化器,会默认调用该格式化器对数据进行转换。
|
||||
|
||||
比如我们定义对`Boolean`类型格式化器,
|
||||
|
||||
```javascript
|
||||
//formatters.js
|
||||
|
||||
module.exports = {
|
||||
// 在所有语言下只作用于特定数据类型的格式化器
|
||||
$types:{
|
||||
Boolean:(value)=> value ? "ON" : "OFF"
|
||||
}
|
||||
}
|
||||
t("灯状态:{status}",true) // === 灯状态:ON
|
||||
t("灯状态:{status}",false) // === 灯状态:OFF
|
||||
```
|
||||
|
||||
在上例中,如果我们想在不同的语言环境下,翻译为不同的显示文本,则可以为不同的语言指定类型格式化器
|
||||
|
||||
```javascript
|
||||
//formatters.js
|
||||
module.exports = {
|
||||
cn:{
|
||||
$types:{
|
||||
Boolean:(value)=> value ? "开" : "关"
|
||||
}
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
Boolean:(value)=> value ? "ON" : "OFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
// 当切换到中文时
|
||||
t("灯状态:{status}",true) // === 灯状态:开
|
||||
t("灯状态:{status}",false) // === 灯状态:关
|
||||
// 当切换到英文时
|
||||
t("灯状态:{status}",true) // === 灯状态:ON
|
||||
t("灯状态:{status}",false) // === 灯状态:OFF
|
||||
```
|
||||
|
||||
**说明:**
|
||||
|
||||
- 完整的类型格式化器定义形式
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
"*":{
|
||||
$types:{...}
|
||||
},
|
||||
cn:{
|
||||
$types:{...}
|
||||
},
|
||||
en:{
|
||||
$types:{....}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在匹配应用格式化时会先在当前语言的`$types`中查找匹配的格式化器,如果找不到再上`*.$types`中查找。
|
||||
|
||||
- `*.$types`代表当所有语言中均没有定义时才匹配的类型格式化。
|
||||
|
||||
- 类型格式化器是**默认执行的,不需要指定名称**。
|
||||
|
||||
- 当前作用域的格式化器优先于全局的格式化器。
|
||||
|
||||
### 通用的格式化器
|
||||
|
||||
类型格式化器只针对特定数据类型,并且会默认调用。而通用的格式化器需要使用`|`管道符进行显式调用。
|
||||
|
||||
同样的,通用的格式化器定义在`languages/formatters.js`中。
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
"*":{
|
||||
$types:{...},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
},
|
||||
cn:{
|
||||
$types:{...},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
},
|
||||
en:{
|
||||
$types:{....},
|
||||
[格式化名称]:(value)=>{.....},
|
||||
[格式化名称]:(value,arg)=>{.....},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每一个格式化器均需要指定一个名称,在进行插值替换时会优先依据当前语言来匹配查找格式化器,如果找不到,再到键名为`*`中查找。
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
"*":{
|
||||
uppercase:(value)=>value
|
||||
},
|
||||
cn:{
|
||||
uppercase:(value)=>["一","二","三","四","五","六","七","八","九","十"][value-1]
|
||||
},
|
||||
en:{
|
||||
uppercase:(value)=>["One","Two","Three","Four","Five","Six","seven","eight","nine","ten"][value-1]
|
||||
},
|
||||
jp:{
|
||||
|
||||
}
|
||||
}
|
||||
// 当切换到中文时
|
||||
t("{value | uppercase}",1) // == 一
|
||||
t("{value | uppercase}",2) // == 二
|
||||
t("{value | uppercase}",3) // == 三
|
||||
// 当切换到英文时
|
||||
t("{value | uppercase}",1) // == One
|
||||
t("{value | uppercase}",2) // == Two
|
||||
t("{value | uppercase}",3) // == Three
|
||||
// 当切换到日文时,由于在该语言下没有定义uppercase格式式,因此到*中查找
|
||||
t("{value | uppercase}",1) // == 1
|
||||
t("{value | uppercase}",2) // == 2
|
||||
t("{value | uppercase}",3) // == 3
|
||||
```
|
||||
|
||||
### 作用域格式化器
|
||||
|
||||
定义在`languages/formatters.js`里面的格式化器仅在当前工程生效,也就是仅在当前作用域生效。一般由应用开发者自行扩展。
|
||||
|
||||
### 全局格式化器
|
||||
|
||||
定义在`@voerkai18n/runtime`里面的格式化器则全局有效,在所有场合均可以使用,但是其优先级低于作用域内的同名格式化器。目前内置的格式化器有:
|
||||
|
||||
| 名称 | | 说明 |
|
||||
| ---- | ---- | ---- |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
|
||||
### 扩展格式化器
|
||||
|
||||
除了可以在当前项目`languages/formatters.js`自定义格式化器和`@voerkai18n/runtime`里面的全局格式化器外,单列了`@voerkai18n/formatters`项目用来包含了更多的格式化器。
|
||||
|
||||
作为开源项目,欢迎大家提交贡献更多的格式化器。
|
||||
|
||||
## 语言包
|
||||
|
||||
当使用`webpack`、`rollup`、`esbuild`进行项目打包时,默认语言包采用静态加载,会被打包进行源码中,而其他语言则采用异步打包方式。在`languages/index.js`中。
|
||||
|
||||
```javascript
|
||||
const defaultMessages = require("./cn.js")
|
||||
const activeMessages = defaultMessages
|
||||
|
||||
// 语言作用域
|
||||
const scope = new i18nScope({
|
||||
default: defaultMessages, // 默认语言包
|
||||
messages : activeMessages, // 当前语言包
|
||||
....
|
||||
// 以下为每一种语言生成一个异步打包语句
|
||||
loaders:{
|
||||
"en" : ()=>import("./en.js")
|
||||
"de" : ()=>import("./de.js")
|
||||
"jp" : ()=>import("./jp.js")
|
||||
})
|
||||
```
|
||||
|
||||
# 扩展工具
|
||||
|
||||
## babel插件
|
||||
|
||||
全局安装`@voerkai18n/babel`插件用来进行自动导入`t`函数和自动文本映射。
|
||||
|
||||
```javascript
|
||||
> npm install -g @voerkai18n/babel
|
||||
> yarn global add @voerkai18n/babel
|
||||
> pnpm add -g @voerkai18n/babel
|
||||
```
|
||||
|
||||
使用方法如下:
|
||||
|
||||
- 在`babel.config.js`中配置插件
|
||||
|
||||
@ -840,7 +1048,6 @@ module.expors = {
|
||||
// 可选,指定语言文件存放的目录,即保存编译后的语言文件的文件夹
|
||||
// 可以指定相对路径,也可以指定绝对路径
|
||||
// location:"",
|
||||
|
||||
autoImport:"#/languages"
|
||||
}
|
||||
]
|
||||
@ -884,129 +1091,194 @@ module.expors = {
|
||||
|
||||
如`webpack`、`rollup`等打包工具也有类似的插件可以实现别名等转换,其目的就是让`babel-plugin-voerkai18n`插件能自动导入固定路径,而不是各种复杂的相对路径。
|
||||
|
||||
## 文本映射
|
||||
## Vue插件
|
||||
|
||||
虽然`VoerkaI18n`推荐采用`t("中华人民共和国万岁")`形式的符合直觉的翻译形式,而不是采用`t("xxxx.xxx")`这样不符合直觉的形式,但是为什么大部份的国际化方案均采用`t("xxxx.xxx")`形式?
|
||||
在`vue3`项目中可以安装`@voerkai18n/vue`来实现`枚举语言`、`语言切换`等功能。
|
||||
|
||||
在我们的方案中,t("中华人民共和国万岁")形式相当于采用原始文本进行查表,语言名形式如下:
|
||||
### **安装**
|
||||
|
||||
将`@voerkai18n/vue`安装为运行时依赖
|
||||
|
||||
```javascript
|
||||
// en.js
|
||||
{
|
||||
"中华人民共和国":"the people's Republic of China"
|
||||
npm install @voerkai18n/vue
|
||||
pnpm add @voerkai18n/vue
|
||||
yarn add @voerkai18n/vue
|
||||
```
|
||||
|
||||
### 启用插件
|
||||
|
||||
```javascript
|
||||
import { createApp } from 'vue'
|
||||
import Root from './App.vue'
|
||||
import i18nPlugin from '@voerkai18n/vue'
|
||||
import { i18nScope } from './languages'
|
||||
const app = createApp(Root)
|
||||
app.use(i18nPlugin,{ i18nScope }) // 重点,需要引入i18nScope
|
||||
app.mount('#app')
|
||||
```
|
||||
|
||||
插件安装成功后,在当前`Vue App`实例上`provide`一个`i18n`响应式实例。
|
||||
|
||||
### 注入`i18n`实例
|
||||
|
||||
接下来在组件中按需注入`i18n`实例,可以用来访问当前的`激活语言`、`默认语言`、`切换语言`等。
|
||||
|
||||
```javascript
|
||||
<script>
|
||||
import {reactive } from 'vue'
|
||||
export default {
|
||||
inject: ['i18n'], // 注入i18n实例,该实例由@voerkai18n/vue插件提供
|
||||
....
|
||||
}
|
||||
// jp.js
|
||||
{
|
||||
"中华人民共和国":"中華人民共和国"
|
||||
</script>
|
||||
```
|
||||
|
||||
声明`inject: ['i18n']`后在当前组件实例中就可以访问`this.i18n`,该实例是一个经过`reactive`封闭的响应式对象,其内容是:
|
||||
|
||||
```javascript
|
||||
this.i18n = {
|
||||
activeLanguage, // 当前激活语言,可以通过直接赋值来切换语言
|
||||
defaultLanguage, // 默认语言名称
|
||||
languages // 支持的语言列表=[{name,title},...]
|
||||
}
|
||||
```
|
||||
|
||||
很显然,直接使用文本内容作为`key`,虽然符合直觉,但是会造成大量的冗余信息。因此,`voerkai18n compile`会将之编译成如下:
|
||||
### 应用示例
|
||||
|
||||
```javascript
|
||||
//idMap.js
|
||||
{
|
||||
"1":"中华人民共和国万岁"
|
||||
}
|
||||
// en.js
|
||||
{
|
||||
"1":"Long live the people's Republic of China"
|
||||
}
|
||||
// jp.js
|
||||
{
|
||||
"2":"中華人民共和国"
|
||||
注入`i18n`实例后就可以在此基础上实现`激活语言`、`默认语言`、`切换语言`等功能。
|
||||
|
||||
```vue
|
||||
<script>
|
||||
import {reactive } from 'vue'
|
||||
export default {
|
||||
inject: ['i18n'], // 注入i18n实例,该实例由@voerkai18n/vue插件提供
|
||||
....
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div>当前语言:{{i18n.activeLanguage}}</div>
|
||||
<div>默认语言:{{i18n.defaultLanguage}}</div>
|
||||
<div>
|
||||
<button
|
||||
@click="i18n.activeLanguage=lng.name"
|
||||
v-for="lng of i18n.langauges">
|
||||
{{ lng.title }}
|
||||
</button>
|
||||
</div>
|
||||
</templage>
|
||||
```
|
||||
|
||||
如此,就消除了在`en.js`、`jp.js`文件中的冗余。但是在源代码文件中还存在`t("中华人民共和国万岁")`,整个运行环境中存在两份副本,一份在源代码文件中,一份在`idMap.js`中。
|
||||
### 插件参数
|
||||
|
||||
为了进一步减少重复内容,因此,我们需要将源代码文件中的`t("中华人民共和国万岁")`更改为`t("1")`,这样就能确保无重复冗余。但是,很显然,我们不可能手动来更改源代码文件,这就需要由babel插件来做这一件事了。
|
||||
|
||||
`babel-plugin-voerkai18n`插件同时还完成一份任务,就是自动读取`voerkai18n compile`生成的`idMap.js`文件,然后将`t("中华人民共和国万岁")`自动更改为`t("1")`,这样就完全消除了重复冗余信息。
|
||||
|
||||
所以,在最终形成的代码中,实际上每一个t函数均是`t("1")`、`t("2")`、`t("3")`、`...`、`t("n")`的形式,最终代码还是采用了用`key`来进行转换,只不过这个过程是自动完成的而已。
|
||||
|
||||
**注意:**如果没有启用`babel-plugin-voerkai18n`插件,还是可以正常工作,但是会有一份默认语言的冗余信息存在。
|
||||
|
||||
## 切换语言
|
||||
|
||||
可以通过全局单例或当前作用域实例切换语言。
|
||||
`@voerkai18n/vue`插件支持以下参数:
|
||||
|
||||
```javascript
|
||||
import { i18nScope } from "./languages"
|
||||
import { i18nScope } from './languages'
|
||||
app.use(i18nPlugin,{
|
||||
i18nScope, // 重点,需要引入当前作用域的i18nScope
|
||||
forceUpdate:true // 当语言切换时是否强制重新渲染
|
||||
})
|
||||
|
||||
// 切换到英文
|
||||
await i18nScope.change("en")
|
||||
// VoerkaI18n是一个全局单例,可以直接访问
|
||||
VoerkaI18n.change("en")
|
||||
```
|
||||
|
||||
侦听语言切换事件:
|
||||
- 当`forceUpdate=true`时,`@voerkai18n/vue`插件在切换语言时会调用`app._instance.update()`对整个应用进行强制重新渲染。大部分情况下,切换语言时强制对整个应用进行重新渲染的行为是符合预期的。您也可以能够通过设`forceUpdate=false`来禁用强制重新渲染,此时,界面就不会马上看到语言的切换,需要您自己控制进行重新渲染。
|
||||
-
|
||||
|
||||
## Vite插件
|
||||
|
||||
`@voerkai18n/babel`插件在`vite`应用中不能正常使用,需要使用`@voerkai18n/vite`插件来完成类似的功能,包括自动文本映射和自动导入`t`函数。
|
||||
|
||||
### 安装
|
||||
|
||||
`@voerkai18n/vite`只需要作为开发依赖安装即可。
|
||||
|
||||
```javascript
|
||||
import { i18nScope } from "./languages"
|
||||
npm install --save-dev @voerkai18n/vite
|
||||
yarn add -D @voerkai18n/vite
|
||||
pnpm add -D @voerkai18n/vite
|
||||
```
|
||||
|
||||
// 切换到英文
|
||||
i18nScope.on((newLanguage)=>{
|
||||
...
|
||||
### 启用插件
|
||||
|
||||
接下来在`vite.config.js`中配置启用`@voerkai18n/vite`插件。
|
||||
|
||||
```javascript
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import Inspect from 'vite-plugin-inspect'// 可选的
|
||||
import Voerkai18nPlugin from "@voerkai18n/vite"
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Inspect(), // 可选的
|
||||
Voerkai18nPlugin({debug:true}),
|
||||
vue()
|
||||
]
|
||||
})
|
||||
//
|
||||
VoerkaI18n.on((newLanguage)=>{
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
- ` vite-plugin-inspect`是开发`vite`插件时的调试插件,启用后就可以通过`localhost:3000/__inspect/ `查看Vue源码文件经过插件处理前后的内容,一般是Vite插件开发者使用。上例中安装后,就可以查看`Voerkai18nPlugin`对Vue文件干了什么事,可以加深理解,**正常使用不需要安装**。
|
||||
- `vite`插件
|
||||
|
||||
### 插件功能
|
||||
|
||||
`@voerkai18n/vite`插件配置启用后,`vite`在进行`dev`或`build`时,就会在`<script setup>....</script>`自动注入`import { t } from "languages" `,同时会扫描源代码文件(包括`vue`,`js`等),根据`idMap.js`文件里面的文本映射表,将`t('"xxxx")`转换成`t("<id>")`的形式。
|
||||
|
||||
不同于`@voerkai18n/babel`插件,`@voerkai18n/vite`插件不需要配置`location`和`autoImport`参数,能正确地处理导入`languages`路径。
|
||||
|
||||
### 插件参数
|
||||
|
||||
`vite`插件支持以下参数:
|
||||
|
||||
```javascript
|
||||
import Voerkai18nPlugin from "@voerkai18n/vite"
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Inspect(), // 可选的
|
||||
Voerkai18nPlugin({
|
||||
location: "./", // 可选的,指定当前工程目录
|
||||
autoImport: true, // 是否自动导入t函数
|
||||
debug:false, // 是否输出调试信息,当=true时,在控制台输出转换匹配的文件清单
|
||||
patterns:[
|
||||
"!(?<!.vue\?.*).(css|json|scss|less|sass)$", // 排除所有css文件
|
||||
/\.vue(\?.*)?/, // 所有vue文件
|
||||
]
|
||||
}),
|
||||
vue()
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## 加载语言包
|
||||
- `location`:可选的,用来指定当前工程目录,一般情况是不需要配置的,会自动取当前文件夹。并且假设`languages`文件夹在`<location>/src/languages`文件夹下。
|
||||
|
||||
当使用`webpack`、`rollup`进行项目打包时,默认语言包采用静态打包,会被打包进行源码中。而其他语言则采用异步打包方式。在`languages/index.js`中
|
||||
- `autoImport`:可选的,默认`true`,用来配置是否自动导入`t`函数。当vue文件没有指定导入时才会自动导入,并且根据当前vue文件的路径处理好导入位置。
|
||||
|
||||
```javascript
|
||||
const defaultMessages = require("./cn.js")
|
||||
const activeMessages = defaultMessages
|
||||
|
||||
// 语言作用域
|
||||
const scope = new i18nScope({
|
||||
default: defaultMessages, // 默认语言包
|
||||
messages : activeMessages, // 当前语言包
|
||||
....
|
||||
loaders:{
|
||||
"en" : ()=>import("./en.js")
|
||||
"de" : ()=>import("./de.js")
|
||||
"jp" : ()=>import("./jp.js")
|
||||
})
|
||||
```
|
||||
- `debug`:可选的,开启后会在控制台输出一些调试信息,对一般用户没有用。
|
||||
|
||||
## 运行时
|
||||
- `patterns`:可选的,一些正则表达式匹配规则,用来过滤匹配哪一些文件需要进行扫描和处理。默认的规则:
|
||||
|
||||
运行`voerkai18n compile`时会在`languages`文件下生成运行时文件`runtime.js`,该文件被`languages/index.js`引入,里面是核心运行时`ES6`源代码(`@voerkai18n/runtime`代码),也就是在您的工程中是直接引入的运行时代码,因此就不需要额外`@voerkai18n/runtime`了。
|
||||
|
||||
每次运行`voerkai18n compile`时就会自动生成`runtime.js`,请及时升级`@voerkai18n/cli`工程以更新运行时代码。
|
||||
|
||||
**重点:默认情况下是不需要额外安装`@voerkai18n/runtime`的。**
|
||||
|
||||
由于`runtime.js`是`ES6`代码,在某些情况下,您需要兼容性更好的代码时,就需要进行`babel`转码。比如在普通的`nodejs`应用中。`@voerkai18n/runtime`也提供转码后的发布版本。
|
||||
|
||||
当运行`voerkai18n compile --no-inline-runtime`时就不会生成`runtime.js`,而是直接引用的`@voerkai18n/runtime`,而`@voerkai18n/runtime`发布了`commonjs`和`esm`两个经过`babel/rollup`转码后的版本。
|
||||
|
||||
- 当运行`voerkai18n compile`并启用了`--no-inline-runtime`时,在您在工程中就需要额外安装`@voerkai18n/runtime`到运行依赖中。
|
||||
|
||||
- 当运行`voerkai18n compile --no-inline-runtime`时,不需要安装`@voerkai18n/runtime`。但是,您的运行环境需要支持`ES6`或者自行转码。大部分`vue/react`等应用均能支持转码。
|
||||
|
||||
## babel插件
|
||||
|
||||
全局安装`@voerkai18n/babel`插件用来进行自动导入t函数和自动文本映射。
|
||||
|
||||
```javascript
|
||||
> npm install -g @voerkai18n/babel
|
||||
> yarn global add @voerkai18n/babel
|
||||
> pnpm add -g @voerkai18n/babel
|
||||
```
|
||||
|
||||
然后在`babel.config.js`中使用,详见上节`自动导入翻译函数`介绍。
|
||||
|
||||
## Vue扩展
|
||||
```javascript
|
||||
const patterns={
|
||||
"!(?<!.vue\?.*).(css|json|scss|less|sass)$", // 排除所有css文件
|
||||
/\.vue(\?.*)?/, // 所有vue文件
|
||||
"!.*\/node_modules\/.*", // 排除node_modules
|
||||
"!/.*\/languages\/.*", // 默认排除语言文件
|
||||
"!\.babelrc", // 排除.babelrc
|
||||
"!babel\.config\.js", // 排除babel.config.js
|
||||
"!package\.json$", // 排除package.json
|
||||
"!vite\.config\.js", // 排除vite.config.js
|
||||
"!^plugin-vue:.*" // 排除plugin-vue
|
||||
}
|
||||
```
|
||||
|
||||
`patterns`的匹配规则语法支持`正则表达式字符串`和`正则表达式`两种,用来对经vite处理的文件名称进行匹配和处理。
|
||||
|
||||
- `正则表达式`比较容易理解,匹配上的就进行处理。
|
||||
- `正则表达式字符串`支持一些简单的语法扩展,包括:
|
||||
- 可以通过前置`!`符号来进行排除匹配。
|
||||
- 将`**`替换为`.*`,允许使用类似`"/code/apps/test/**/node_modules/**"`的形式来匹配连续路径。
|
||||
- 将`?`替换为`[^\/]?`
|
||||
- 将`*`替换为`[^\/]*`
|
||||
|
||||
## React扩展
|
||||
|
||||
|
@ -30,6 +30,7 @@ function expectBabelSuccess(result){
|
||||
expect(result.includes(`t("4"`)).toBeTruthy()
|
||||
expect(result.includes(`t("5"`)).toBeTruthy()
|
||||
}
|
||||
|
||||
test("翻译函数转换",done=>{
|
||||
babel.transform(code, {
|
||||
plugins: [
|
||||
@ -63,7 +64,7 @@ test("读取esm格式的idMap后进行翻译函数转换",done=>{
|
||||
[
|
||||
i18nPlugin,
|
||||
{
|
||||
location:path.join(__dirname, "../packages/demo/apps/lib1/languages"),
|
||||
location:path.join(__dirname, "../packages/apps/lib1/languages"),
|
||||
autoImport:"#/languages",
|
||||
moduleType:"esm",
|
||||
}
|
||||
@ -80,7 +81,7 @@ test("读取commonjs格式的idMap后进行翻译函数转换",done=>{
|
||||
[
|
||||
i18nPlugin,
|
||||
{
|
||||
location:path.join(__dirname, "../packages/demo/apps/lib2/languages"),
|
||||
location:path.join(__dirname, "../packages/apps/lib2/languages"),
|
||||
autoImport:"#/languages",
|
||||
moduleType:"esm",
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user