更新插值替换函数
This commit is contained in:
parent
47a4fd1c99
commit
25f548535b
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
module.exports = {
|
||||
"1": "a1:aaaaa",
|
||||
"2": "no aaaaa",
|
||||
"3": "bbbbb",
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
module.exports = {
|
||||
"1": "德文:aaaaa",
|
||||
"2": "no aaaaa",
|
||||
"3": "bbbbb",
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
module.exports = {
|
||||
"1": "a1:aaaaa",
|
||||
"2": "no aaaaa",
|
||||
"3": "bbbbb",
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
module.exports = {
|
||||
"a1:aaaaa": 1,
|
||||
"no aaaaa": 2,
|
||||
"bbbbb": 3,
|
@ -1,8 +1,10 @@
|
||||
import messageIds from "./messageIds"
|
||||
import { translate,i18n } from "voerka-i18n"
|
||||
import defaultMessages from "./cn.js"
|
||||
import i18nSettings from "./settings.js"
|
||||
import formatters from "voerka-i18n/formatters"
|
||||
|
||||
const messageIds = require("./idMap")
|
||||
const { translate,i18n } = require("voerka-i18n")
|
||||
const defaultMessages = require("./cn.js")
|
||||
const i18nSettings = require("./settings.js")
|
||||
const formatters = require("../formatters")
|
||||
|
||||
|
||||
// 自动创建全局VoerkaI18n实例
|
||||
if(!globalThis.VoerkaI18n){
|
||||
@ -13,13 +15,10 @@ let scope = {
|
||||
defaultLanguage: "cn", // 默认语言名称
|
||||
default: defaultMessages, // 默认语言包
|
||||
messages : defaultMessages, // 当前语言包
|
||||
ids:messageIds, // 消息id映射列表
|
||||
formatters:{
|
||||
...formatters,
|
||||
...i18nSettings.formatters || {}
|
||||
},
|
||||
loaders:{}, // 异步加载语言文件的函数列表
|
||||
settings:{} // 引用全局VoerkaI18n实例的配置
|
||||
idMap:messageIds, // 消息id映射列表
|
||||
formatters, // 当前作用域的格式化函数列表
|
||||
loaders:{}, // 异步加载语言文件的函数列表
|
||||
global:{} // 引用全局VoerkaI18n配置
|
||||
}
|
||||
|
||||
let supportedlanguages = {}
|
||||
@ -38,5 +37,7 @@ const t = ()=> translate.bind(scope)(...arguments)
|
||||
// 注册当前作用域到全局VoerkaI18n实例
|
||||
VoerkaI18n.register(scope)
|
||||
|
||||
export scope
|
||||
export t
|
||||
|
||||
module.exports.scope = scope
|
||||
module.exports.t = t
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
module.exports = {
|
||||
"1": "a1:aaaaa",
|
||||
"2": "no aaaaa",
|
||||
"3": "bbbbb",
|
||||
|
222
pnpm-lock.yaml
generated
222
pnpm-lock.yaml
generated
@ -56,8 +56,8 @@ packages:
|
||||
slash: 2.0.0
|
||||
source-map: 0.5.7
|
||||
optionalDependencies:
|
||||
'@nicolo-ribaudo/chokidar-2': registry.npmmirror.com/@nicolo-ribaudo/chokidar-2/2.1.8-no-fsevents.3
|
||||
chokidar: registry.npmmirror.com/chokidar/3.5.3
|
||||
'@nicolo-ribaudo/chokidar-2': 2.1.8-no-fsevents.3
|
||||
chokidar: 3.5.3
|
||||
dev: true
|
||||
|
||||
/@babel/code-frame/7.16.7:
|
||||
@ -531,7 +531,7 @@ packages:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
graceful-fs: 4.2.9
|
||||
source-map: registry.npmmirror.com/source-map/0.6.1
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/@jest/test-result/27.5.1:
|
||||
@ -606,6 +606,12 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.11
|
||||
dev: true
|
||||
|
||||
/@nicolo-ribaudo/chokidar-2/2.1.8-no-fsevents.3:
|
||||
resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@sinonjs/commons/1.8.3:
|
||||
resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==}
|
||||
dependencies:
|
||||
@ -1059,6 +1065,14 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/bindings/1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/brace-expansion/1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
@ -1193,6 +1207,42 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/chokidar/2.1.8:
|
||||
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
|
||||
deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies
|
||||
dependencies:
|
||||
anymatch: 2.0.0
|
||||
async-each: 1.0.3
|
||||
braces: 2.3.2
|
||||
glob-parent: 3.1.0
|
||||
inherits: 2.0.4
|
||||
is-binary-path: 1.0.1
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
path-is-absolute: 1.0.1
|
||||
readdirp: 2.2.1
|
||||
upath: 1.2.0
|
||||
optionalDependencies:
|
||||
fsevents: 1.2.13
|
||||
dev: false
|
||||
|
||||
/chokidar/3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
anymatch: 3.1.2
|
||||
braces: 3.0.2
|
||||
glob-parent: 5.1.2
|
||||
is-binary-path: 2.1.0
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/ci-info/3.3.0:
|
||||
resolution: {integrity: sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==}
|
||||
dev: true
|
||||
@ -1614,7 +1664,7 @@ packages:
|
||||
esutils: 2.0.3
|
||||
optionator: 0.8.3
|
||||
optionalDependencies:
|
||||
source-map: registry.npmmirror.com/source-map/0.6.1
|
||||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
/escodegen/2.0.0:
|
||||
@ -1627,7 +1677,7 @@ packages:
|
||||
esutils: 2.0.3
|
||||
optionator: 0.8.3
|
||||
optionalDependencies:
|
||||
source-map: registry.npmmirror.com/source-map/0.6.1
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/esprima/4.0.1:
|
||||
@ -1765,6 +1815,12 @@ packages:
|
||||
bser: 2.1.1
|
||||
dev: true
|
||||
|
||||
/file-uri-to-path/1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/fill-range/4.0.0:
|
||||
resolution: {integrity: sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -1884,6 +1940,26 @@ packages:
|
||||
/fs.realpath/1.0.0:
|
||||
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
|
||||
|
||||
/fsevents/1.2.13:
|
||||
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==}
|
||||
engines: {node: '>= 4.0'}
|
||||
os: [darwin]
|
||||
deprecated: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
nan: 2.15.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/fsevents/2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/function-bind/1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
|
||||
@ -1961,7 +2037,7 @@ packages:
|
||||
dependencies:
|
||||
anymatch: 2.0.0
|
||||
async-done: 1.3.2
|
||||
chokidar: registry.npmmirror.com/chokidar/2.1.8
|
||||
chokidar: 2.1.8
|
||||
is-negated-glob: 1.0.0
|
||||
just-debounce: 1.1.0
|
||||
normalize-path: 3.0.0
|
||||
@ -2477,7 +2553,7 @@ packages:
|
||||
dependencies:
|
||||
debug: 4.3.3
|
||||
istanbul-lib-coverage: 3.2.0
|
||||
source-map: registry.npmmirror.com/source-map/0.6.1
|
||||
source-map: 0.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@ -2676,7 +2752,7 @@ packages:
|
||||
micromatch: 4.0.4
|
||||
walker: 1.0.8
|
||||
optionalDependencies:
|
||||
fsevents: registry.npmmirror.com/fsevents/2.3.2
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/jest-jasmine2/27.5.1:
|
||||
@ -3304,6 +3380,12 @@ packages:
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: false
|
||||
|
||||
/nan/2.15.0:
|
||||
resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/nanomatch/1.2.13:
|
||||
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -4009,7 +4091,7 @@ packages:
|
||||
define-property: 0.2.5
|
||||
extend-shallow: 2.0.1
|
||||
map-cache: 0.2.2
|
||||
source-map: registry.npmmirror.com/source-map/0.5.7
|
||||
source-map: 0.5.7
|
||||
source-map-resolve: 0.5.3
|
||||
use: 3.1.1
|
||||
dev: false
|
||||
@ -4029,7 +4111,7 @@ packages:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: registry.npmmirror.com/source-map/0.6.1
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/source-map-url/0.4.1:
|
||||
@ -4045,6 +4127,11 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/source-map/0.7.3:
|
||||
resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/sparkles/1.0.1:
|
||||
resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
@ -4466,7 +4553,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/istanbul-lib-coverage': 2.0.4
|
||||
convert-source-map: 1.8.0
|
||||
source-map: registry.npmmirror.com/source-map/0.7.3
|
||||
source-map: 0.7.3
|
||||
dev: true
|
||||
|
||||
/v8flags/3.2.0:
|
||||
@ -4727,70 +4814,12 @@ packages:
|
||||
regenerator-runtime: registry.npmmirror.com/regenerator-runtime/0.13.9
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@nicolo-ribaudo/chokidar-2/2.1.8-no-fsevents.3:
|
||||
resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz}
|
||||
name: '@nicolo-ribaudo/chokidar-2'
|
||||
version: 2.1.8-no-fsevents.3
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/ansicolor/1.1.100:
|
||||
resolution: {integrity: sha512-Jl0pxRfa9WaQVUX57AB8/V2my6FJxrOR1Pp2qqFbig20QB4HzUoQ48THTKAgHlUCJeQm/s2WoOPcoIDhyCL/kw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansicolor/-/ansicolor-1.1.100.tgz}
|
||||
name: ansicolor
|
||||
version: 1.1.100
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/bindings/1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz}
|
||||
name: bindings
|
||||
version: 1.5.0
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
file-uri-to-path: registry.npmmirror.com/file-uri-to-path/1.0.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/chokidar/2.1.8:
|
||||
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz}
|
||||
name: chokidar
|
||||
version: 2.1.8
|
||||
deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies
|
||||
dependencies:
|
||||
anymatch: 2.0.0
|
||||
async-each: 1.0.3
|
||||
braces: 2.3.2
|
||||
glob-parent: 3.1.0
|
||||
inherits: 2.0.4
|
||||
is-binary-path: 1.0.1
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
path-is-absolute: 1.0.1
|
||||
readdirp: 2.2.1
|
||||
upath: 1.2.0
|
||||
optionalDependencies:
|
||||
fsevents: registry.npmmirror.com/fsevents/1.2.13
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/chokidar/3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz}
|
||||
name: chokidar
|
||||
version: 3.5.3
|
||||
engines: {node: '>= 8.10.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
anymatch: 3.1.2
|
||||
braces: 3.0.2
|
||||
glob-parent: 5.1.2
|
||||
is-binary-path: 2.1.0
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: registry.npmmirror.com/fsevents/2.3.2
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/core-js-pure/3.21.1:
|
||||
resolution: {integrity: sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.21.1.tgz}
|
||||
name: core-js-pure
|
||||
@ -4818,52 +4847,12 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/file-uri-to-path/1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz}
|
||||
name: file-uri-to-path
|
||||
version: 1.0.0
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/fsevents/1.2.13:
|
||||
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz}
|
||||
name: fsevents
|
||||
version: 1.2.13
|
||||
engines: {node: '>= 4.0'}
|
||||
os: [darwin]
|
||||
deprecated: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
bindings: registry.npmmirror.com/bindings/1.5.0
|
||||
nan: registry.npmmirror.com/nan/2.15.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/fsevents/2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz}
|
||||
name: fsevents
|
||||
version: 2.3.2
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/jju/1.4.0:
|
||||
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jju/-/jju-1.4.0.tgz}
|
||||
name: jju
|
||||
version: 1.4.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/nan/2.15.0:
|
||||
resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nan/-/nan-2.15.0.tgz}
|
||||
name: nan
|
||||
version: 2.15.0
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/readjson/2.2.2:
|
||||
resolution: {integrity: sha512-PdeC9tsmLWBiL8vMhJvocq+OezQ3HhsH2HrN7YkhfYcTjQSa/iraB15A7Qvt7Xpr0Yd2rDNt6GbFwVQDg3HcAw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/readjson/-/readjson-2.2.2.tgz}
|
||||
name: readjson
|
||||
@ -4880,25 +4869,12 @@ packages:
|
||||
version: 0.13.9
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/source-map/0.5.7:
|
||||
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz}
|
||||
name: source-map
|
||||
version: 0.5.7
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/source-map/0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz}
|
||||
name: source-map
|
||||
version: 0.6.1
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
registry.npmmirror.com/source-map/0.7.3:
|
||||
resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.7.3.tgz}
|
||||
name: source-map
|
||||
version: 0.7.3
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/try-catch/3.0.0:
|
||||
resolution: {integrity: sha512-3uAqUnoemzca1ENvZ72EVimR+E8lqBbzwZ9v4CEbLjkaV3Q+FtdmPUt7jRtoSoTiYjyIMxEkf6YgUpe/voJ1ng==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/try-catch/-/try-catch-3.0.0.tgz}
|
||||
|
@ -15,7 +15,10 @@ t("中华人民共和国")
|
||||
// 位置插值变量
|
||||
t("中华人民共和国{}","万岁")
|
||||
t("中华人民共和国成立于{}年,首都{}",1949,"北京")
|
||||
// 当仅有两个参数且第2个参数是[]类型时,自动展开第一个参数进行位置插值
|
||||
t("中华人民共和国成立于{year}年,首都{capital}",[1949,"北京"])
|
||||
|
||||
、
|
||||
// 当仅有两个参数且第2个参数是{}类型时,启用字典插值变量
|
||||
t("中华人民共和国成立于{year}年,首都{capital}",{year:1949,capital:"北京"})
|
||||
|
||||
|
@ -49,18 +49,22 @@ function normalizeCompileOptions(opts={}) {
|
||||
let options = Object.assign({
|
||||
input:null, // 指定要编译的文件夹,即extract输出的语言文件夹
|
||||
output:null, // 指定编译后的语言文件夹,如果没有指定,则使用input目录
|
||||
formatters:{}, // 对插值变量进行格式化的函数列表
|
||||
moduleType:"esm" // 指定编译后的语言文件的模块类型,取值common,cjs,esm,es
|
||||
}, opts)
|
||||
if(options.moduleType==="es") options.moduleType = "esm"
|
||||
if(options.moduleType==="cjs") options.moduleType = "common"
|
||||
if(["common","cjs","esm","es"].includes(options.moduleType)) options.moduleType = "esm"
|
||||
return opts;
|
||||
}
|
||||
|
||||
module.exports = function compile(langFolder,opts={}){
|
||||
let options = normalizeCompileOptions(opts);
|
||||
let { output } = options;
|
||||
const options = normalizeCompileOptions(opts);
|
||||
const { output,moduleType } = options;
|
||||
|
||||
//1. 加载多语言配置文件
|
||||
import(`file:///${path.join(langFolder,"settings.js")}`).then(module=>{
|
||||
let { languages,defaultLanguage,activeLanguage,namespaces } = module.default;
|
||||
const langSettings = module.default;
|
||||
let { languages,defaultLanguage,activeLanguage,namespaces } = langSettings
|
||||
|
||||
// 1. 合并生成最终的语言文件
|
||||
let messages = {} ,msgId =1
|
||||
@ -90,19 +94,33 @@ module.exports = function compile(langFolder,opts={}){
|
||||
Object.entries(messages).forEach(([message,translatedMsgs])=>{
|
||||
langMessages[translatedMsgs.$id] = lang.name in translatedMsgs ? translatedMsgs[lang.name] : message
|
||||
})
|
||||
const langFile = path.join(langFolder,`${lang.name}.js`)
|
||||
// 为每一种语言生成一个语言文件
|
||||
fs.writeFileSync(path.join(langFolder,`${lang.name}.js`),`export default ${JSON.stringify(langMessages,null,4)}`)
|
||||
if(moduleType==="esm"){
|
||||
fs.writeFileSync(langFile,`export default ${JSON.stringify(langMessages,null,4)}`)
|
||||
}else{
|
||||
fs.writeFileSync(langFile,`module.exports = ${JSON.stringify(langMessages,null,4)}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 4. 生成id映射文件
|
||||
fs.writeFileSync(path.join(langFolder,"messageIds.js"),`export default ${JSON.stringify(messageIds,null,4)}`)
|
||||
const idMapFile = path.join(langFolder,"idMap.js")
|
||||
if(moduleType==="esm"){
|
||||
fs.writeFileSync(idMapFile,`export default ${JSON.stringify(messageIds,null,4)}`)
|
||||
}else{
|
||||
fs.writeFileSync(idMapFile,`module.exports = ${JSON.stringify(messageIds,null,4)}`)
|
||||
}
|
||||
|
||||
const hasFormatters = fs.existsSync(path.join(langFolder,"formatters.js"))
|
||||
// 生成编译后的访问入口文件
|
||||
const entryContent = artTemplate(path.join(__dirname,"entry.template.js"), {languages,defaultLanguage,activeLanguage,namespaces } )
|
||||
fs.writeFileSync(path.join(langFolder,"index.js"),entryContent)
|
||||
// 5. 生成编译后的访问入口文件
|
||||
const entryFile = path.join(langFolder,"index.js")
|
||||
const entryContent = artTemplate(path.join(__dirname,"templates","entry.js"), {languages,defaultLanguage,activeLanguage,namespaces,moduleType } )
|
||||
fs.writeFileSync(entryFile,entryContent)
|
||||
|
||||
// 6 . 生成编译后的格式化函数文件
|
||||
const formattersFile = path.join(langFolder,"formatters.js")
|
||||
if(!fs.existsSync(formattersFile)){
|
||||
const formattersContent = artTemplate(path.join(__dirname,"templates","formatters.js"), {languages,defaultLanguage,activeLanguage,namespaces,moduleType } )
|
||||
fs.writeFileSync(formattersFile,formattersContent)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
@ -3,20 +3,23 @@
|
||||
*
|
||||
* 使用方法:
|
||||
*
|
||||
* 在translates/xxx.json文件中进行翻译时,可以对插值变量进行格式化,
|
||||
*
|
||||
* {
|
||||
* "Now is {date}":{
|
||||
* "zh-CN":"现在是{date|time}"
|
||||
* "My birthday is {date}":{
|
||||
* "zh-CN":"我的生日是{date|time}"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* t("My birthday is {date}",new Date(1975,11,25))
|
||||
*
|
||||
*
|
||||
*/
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
module.exports = {
|
||||
cn:{
|
||||
"*":{ // 适用于所有类型的格式化器
|
||||
default:null // 默认格式化器
|
||||
},
|
||||
Date:{
|
||||
default:(value)=>dayjs(value).format("YYYY年MM年DD日"), // 默认的格式化器
|
||||
time:(value)=>dayjs(value).format("HH:mm:ss"),
|
||||
|
362
src/index.js
362
src/index.js
@ -3,9 +3,15 @@ const formatters = require("./formatters")
|
||||
|
||||
|
||||
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
|
||||
// let varRegexp = /\{\s*(?<var>\w*\.?\w*)\s*\}/g
|
||||
let varWidthPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w+\s*)*)\s*\}/g
|
||||
// 不支持参数: let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*\s*)*)\s*\}/g
|
||||
|
||||
// 支持参数: { var | formatter(x,x,..) | formatter }
|
||||
let varWithPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w*(\(.*\)){0,1}\s*)*)\s*\}/g
|
||||
|
||||
|
||||
|
||||
// 插值变量字符串替换正则
|
||||
|
||||
//let varReplaceRegexp =String.raw`\{\s*(?<var>{name}\.?\w*)\s*\}`
|
||||
|
||||
|
||||
@ -42,27 +48,87 @@ function getDataTypeName(v){
|
||||
return v.constructor && v.constructor.name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
通过正则表达式对原始文本内容进行解析匹配后得到的
|
||||
formatters="| aaa(1,1) | bbb "
|
||||
|
||||
需要统一解析为
|
||||
|
||||
[
|
||||
[aaa,[1,1]], // [formatter'name,[args,...]]
|
||||
[bbb,[]],
|
||||
]
|
||||
|
||||
formatters="| aaa(1,1,"dddd") | bbb "
|
||||
|
||||
目前对参数采用简单的split(",")来解析,因为无法正确解析aaa(1,1,"dd,,dd")形式的参数
|
||||
在此场景下基本够用了,如果需要支持更复杂的参数解析,可以后续考虑使用正则表达式来解析
|
||||
|
||||
@returns [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]]
|
||||
*/
|
||||
function parseFormatters(formatters){
|
||||
if(!formatters) return []
|
||||
// 1. 先解析为 ["aaa()","bbb"]形式
|
||||
let result = formatters.trim().substr(1).trim().split("|").map(r=>r.trim())
|
||||
|
||||
// 2. 解析格式化器参数
|
||||
return result.map(formatter=>{
|
||||
let firstIndex = formatter.indexOf("(")
|
||||
let lastIndex = formatter.lastIndexOf(")")
|
||||
if(firstIndex!==-1 && lastIndex!==-1){ // 带参数的格式化器
|
||||
const argsContent = formatter.substr(firstIndex+1,lastIndex-firstIndex-1).trim()
|
||||
let args = argsContent=="" ? [] : argsContent.split(",").map(arg=>{
|
||||
arg = arg.trim()
|
||||
if(!isNaN(parseInt(arg))){
|
||||
return parseInt(arg) // 数字
|
||||
}else if((arg.startsWith('\"') && arg.endsWith('\"')) || (arg.startsWith('\'') && arg.endsWith('\'')) ){
|
||||
return arg.substr(1,arg.length-2) // 字符串
|
||||
}else if(arg.toLowerCase()==="true" || arg.toLowerCase()==="false"){
|
||||
return arg.toLowerCase()==="true" // 布尔值
|
||||
}else if((arg.startsWith('{') && arg.endsWith('}')) || (arg.startsWith('[') && arg.endsWith(']'))){
|
||||
try{
|
||||
return JSON.parse(arg)
|
||||
}catch(e){
|
||||
return String(arg)
|
||||
}
|
||||
}else{
|
||||
return String(arg)
|
||||
}
|
||||
})
|
||||
return [formatter.substr(0,firstIndex),args]
|
||||
}else{// 不带参数的格式化器
|
||||
return [formatter,[]]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取字符串中的插值变量
|
||||
* @param {*} str
|
||||
* @param {*} isFull =true 保留所有插值变量 =false 进行去重
|
||||
* @returns {Array} [[变量名称,[]],[变量名称,[formatter,formatter,...]],...]
|
||||
*/
|
||||
module.exports.getInterpolatedVars = function(str){
|
||||
let results = []
|
||||
let match
|
||||
while ((match = varWidthPipeRegexp.exec(str)) !== null) {
|
||||
if (match.index === varWidthPipeRegexp.lastIndex) {
|
||||
varWidthPipeRegexp.lastIndex++;
|
||||
function getInterpolatedVars(str,isFull=false){
|
||||
let results = [], match
|
||||
while ((match = varWithPipeRegexp.exec(str)) !== null) {
|
||||
if (match.index === varWithPipeRegexp.lastIndex) {
|
||||
varWithPipeRegexp.lastIndex++;
|
||||
}
|
||||
const varname = match.groups.varname
|
||||
const formatters = match.groups.formatters ? match.groups.formatters.trim().substr(1).trim().split("|").map(r=>r.trim()) : []
|
||||
if(varname) {
|
||||
const varDefine = formatters ? [varname,formatters] : [varname,[]]
|
||||
if(results.findIndex(item=>item[0]===varDefine[0] && item[1].join()===varDefine[1].join()) === -1) results.push(varDefine)
|
||||
const varname = match.groups.varname || ""
|
||||
// 解析格式化器和参数 = [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]]
|
||||
const formatters = parseFormatters(match.groups.formatters)
|
||||
const varInfo = [varname,formatters]
|
||||
if(isFull) {
|
||||
results.push([varname,formatters] )
|
||||
}else{
|
||||
if(results.findIndex(item=>item[0]===varInfo[0] && item[1].join()===varInfo[1].join()) === -1) results.push(varInfo)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将要翻译内容提供了一个非文本内容时进行默认的转换
|
||||
* - 对函数则执行并取返回结果()
|
||||
@ -72,7 +138,7 @@ module.exports.getInterpolatedVars = function(str){
|
||||
* @param {*} value
|
||||
* @returns
|
||||
*/
|
||||
function transformVarValue(value){
|
||||
function transformToString(value){
|
||||
let result = value
|
||||
if(typeof(result)==="function") result = value()
|
||||
if(!(typeof(result)==="string")){
|
||||
@ -84,65 +150,217 @@ function transformVarValue(value){
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// 缓存数据类型的格式化器,避免每次都调用getDataTypeDefaultFormatter
|
||||
let datatypeFormattersCache ={
|
||||
$activeLanguage:null,
|
||||
}
|
||||
/**
|
||||
* 取得指定数据类型的默认格式化器
|
||||
*
|
||||
* 可以为每一个数据类型指定一个格式化器,当传入插值变量时,会自动调用该格式化器来对值进行格式化转换
|
||||
|
||||
const formatters = {
|
||||
"*":{
|
||||
$types:{...} // 在所有语言下只作用于特定数据类型的格式化器
|
||||
}, // 在所有语言下生效的格式化器
|
||||
cn:{
|
||||
$types:{
|
||||
[数据类型]:(value)=>{...},
|
||||
},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
},
|
||||
}
|
||||
* @param {*} scope
|
||||
* @param {*} activeLanguage
|
||||
* @param {*} dataType 数字类型
|
||||
* @returns {Function} 格式化函数
|
||||
*/
|
||||
function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){
|
||||
if(datatypeFormattersCache.$activeLanguage === activeLanguage) {
|
||||
if(dataType in datatypeFormattersCache) return datatypeFormattersCache[dataType]
|
||||
}else{// 清空缓存
|
||||
datatypeFormattersCache = { $activeLanguage:activeLanguage }
|
||||
}
|
||||
|
||||
// 先在当前作用域中查找,再在全局查找
|
||||
const targets = [scope.formatters,scope.global.formatters]
|
||||
for(const target of targets){
|
||||
if(activeLanguage in target){
|
||||
// 在当前语言的$types中查找
|
||||
let formatters = target[activeLanguage].$types || {}
|
||||
for(let [name,formatter] of Object.entries(formatters)){
|
||||
if(name === dataType && typeof(formatter)==="function") {
|
||||
datatypeFormattersCache[dataType] = formatter
|
||||
return formatter
|
||||
}
|
||||
}
|
||||
}
|
||||
// 在所有语言的$types中查找
|
||||
let formatters = target["*"].$types || {}
|
||||
for(let [name,formatter] of Object.entries(formatters)){
|
||||
if(name === dataType && typeof(formatter)==="function") {
|
||||
datatypeFormattersCache[dataType] = formatter
|
||||
return formatter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定名称的格式化器函数
|
||||
* @param {*} scope
|
||||
* @param {*} activeLanguage
|
||||
* @param {*} name 格式化器名称
|
||||
* @returns {Function} 格式化函数
|
||||
*/
|
||||
let formattersCache = { $activeLanguage:null}
|
||||
function getFormatter(scope,activeLanguage,name){
|
||||
if(formattersCache.$activeLanguage === activeLanguage) {
|
||||
if(name in formattersCache) return formattersCache[dataType]
|
||||
}else{ // 当切换语言时需要清空缓存
|
||||
formattersCache = { $activeLanguage:activeLanguage }
|
||||
}
|
||||
// 先在当前作用域中查找,再在全局查找
|
||||
const targets = [scope.formatters,scope.global.formatters]
|
||||
for(const target of targets){
|
||||
// 优先在当前语言查找
|
||||
if(activeLanguage in target){
|
||||
let formatters = target[activeLanguage] || {}
|
||||
if((name in formatters) && typeof(formatters[name])==="function") return formattersCache[name] = formatters[name]
|
||||
}
|
||||
// 在所有语言的$types中查找
|
||||
let formatters = target["*"] || {}
|
||||
if((name in formatters) && typeof(formatters[name])==="function") return formattersCache[name] = formatters[name]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行格式化器并返回结果
|
||||
* @param {*} value
|
||||
* @param {*} formatters
|
||||
*/
|
||||
function executeFormatter(value,formatters){
|
||||
if(formatters.length===0) return value
|
||||
let result = value
|
||||
try{
|
||||
for(let formatter of formatters){
|
||||
if(typeof(formatter) === "function") {
|
||||
result = formatter(result)
|
||||
}else{ // 碰到无效的格式化器时,直接返回
|
||||
return result
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
console.error(`Error while execute i18n formatter for ${value}: ${e.message} ` )
|
||||
}
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 将 [[格式化器名称,[参数,参数,...]],[格式化器名称,[参数,参数,...]]]格式化器转化为
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {*} scope
|
||||
* @param {*} activeLanguage
|
||||
* @param {*} formatters
|
||||
*/
|
||||
function buildFormatters(scope,activeLanguage,formatters){
|
||||
let results = []
|
||||
for(let formatter of formatters){
|
||||
if(formatter[0]){
|
||||
results.push((v)=>{
|
||||
return getFormatter(scope,activeLanguage,formatter[0])(v,...formatter[1])
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串可以进行变量插值替换,
|
||||
* replaceInterpolateVars("<模板字符串>",{变量名称:变量值,变量名称:变量值,...})
|
||||
* replaceInterpolateVars("<模板字符串>",[变量值,变量值,...])
|
||||
* replaceInterpolateVars("<模板字符串>",变量值,变量值,...])
|
||||
* replaceInterpolatedVars("<模板字符串>",{变量名称:变量值,变量名称:变量值,...})
|
||||
* replaceInterpolatedVars("<模板字符串>",[变量值,变量值,...])
|
||||
* replaceInterpolatedVars("<模板字符串>",变量值,变量值,...])
|
||||
*
|
||||
- 当只有两个参数并且第2个参数是{}时,将第2个参数视为命名变量的字典
|
||||
replaceInterpolateVars("this is {a}+{b},{a:1,b:2}) --> this is 1+2
|
||||
replaceInterpolatedVars("this is {a}+{b},{a:1,b:2}) --> this is 1+2
|
||||
- 当只有两个参数并且第2个参数是[]时,将第2个参数视为位置参数
|
||||
replaceInterpolateVars"this is {}+{}",[1,2]) --> this is 1+2
|
||||
replaceInterpolatedVars"this is {}+{}",[1,2]) --> this is 1+2
|
||||
- 普通位置参数替换
|
||||
replaceInterpolateVars("this is {a}+{b}",1,2) --> this is 1+2
|
||||
replaceInterpolatedVars("this is {a}+{b}",1,2) --> this is 1+2
|
||||
-
|
||||
this == scope == { formatters: {}, ... }
|
||||
* @param {*} template
|
||||
* @returns
|
||||
*/
|
||||
module.exports.replaceInterpolateVars = function(template,...args) {
|
||||
function replaceInterpolatedVars(template,...args) {
|
||||
const scope = this
|
||||
const activeLanguage = scope.activeLanguage
|
||||
// 当前激活语言
|
||||
const activeLanguage = scope.global.activeLanguage
|
||||
let result=template
|
||||
if(!hasInterpolation(template)) return template
|
||||
// 变量插值
|
||||
// ****************************变量插值****************************
|
||||
if(args.length===1 && typeof(args[0]) === "object" && !Array.isArray(args[0])){
|
||||
// 格式化器只能用在字典变量中
|
||||
// {var1:[formatter,formatter,...],var2:[formatter,formatter,...],...}
|
||||
let varNames = getInterpolatedVars(template)
|
||||
// 读取模板字符串中的插值变量列表
|
||||
// [[var1:[formatter,formatter,...]],[var2:[formatter,formatter,...]],...}
|
||||
let interpVars = getInterpolatedVars(template)
|
||||
let varValues = args[0]
|
||||
if(varNames.length===0) return template
|
||||
for(let [name,formatters] of varNames){
|
||||
// 计算出变量值
|
||||
if(interpVars.length===0) return template // 没有变量插值则的返回原字符串
|
||||
// 开始处理插值变量
|
||||
for(let [name,formatters] of interpVars){
|
||||
// 1. 取得格式化器函数列表 formatters=[[格式化器名称,[参数,参数,...]],[格式化器名称,[参数,参数,...]]]
|
||||
const formatterFuncs = buildFormatters(scope,activeLanguage,formatters)
|
||||
// 2. 取变量值
|
||||
let value = (name in varValues) ? varValues[name] : ''
|
||||
// 针对每种数据类型的默认格式化器
|
||||
let dataType= getDataTypeName(value)
|
||||
if(dataType in scope.formatters[activeLanguage]){
|
||||
const formatter = scope.formatters[activeLanguage][dataType]
|
||||
formatters.splice(0,0,formatter)
|
||||
}
|
||||
if(formatters.length > 0 ){
|
||||
formatters.reduce((v,formatter)=>{
|
||||
if(formatter in scope.formatters){
|
||||
return scope.formatters[formatter](v)
|
||||
}else{
|
||||
return v
|
||||
}
|
||||
},value)
|
||||
// 3. 查找每种数据类型默认格式化器,并添加到formatters最前面,默认数据类型格式化器优先级最高
|
||||
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value))
|
||||
if(defaultFormatter){
|
||||
formatterFuncs.splice(0,0,defaultFormatter)
|
||||
}
|
||||
|
||||
// 4. 执行格式化器
|
||||
value = executeFormatter(value,formatterFuncs)
|
||||
|
||||
// 5. 进行值替换
|
||||
// 如果变量中包括|管道符,则需要进行转换以适配更宽松的写法,比如data|time能匹配"data |time","data | time"等
|
||||
let nameRegexp =new RegExp(`${name}\\s*\\|\\s*${formatters.join("\\s*\\|\\s*")}`,"g")
|
||||
result=result.replaceAll(nameRegexp,"gm"),transformVarValue(value))
|
||||
let nameRegexp =
|
||||
if(formatters.length===0){
|
||||
nameRegexp = new RegExp(String.raw`\{\s*${name}\s*\}`,"gm")
|
||||
}else{
|
||||
nameRegexp = new RegExp(`${name}\\s*\\|\\s*${formatters.join("\\s*\\|\\s*")}`,"gm")
|
||||
}
|
||||
|
||||
result= result.replaceAll(nameRegexp,transformToString(value))
|
||||
}
|
||||
}else{ // 位置插值
|
||||
|
||||
}else{
|
||||
// ****************************位置插值****************************
|
||||
// 如果只有一个Array参数,则认为是位置变量列表,进行展开
|
||||
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args
|
||||
|
||||
// 取得模板字符串中的插值变量列表 , 包含命令变量和位置变量
|
||||
let interpVars = getInterpolatedVars(template,true)
|
||||
if(interpVars.length===0) return template // 没有变量插值则的返回原字符串
|
||||
|
||||
let i=0
|
||||
for(let match of result.match(varWidthPipeRegexp) || []){
|
||||
for(let match of result.match(varWithPipeRegexp) || []){
|
||||
if(i<params.length){
|
||||
let param = transformVarValue(params[i])
|
||||
result=result.replace(match,param)
|
||||
let value = params[i]
|
||||
const formatterFuncs = buildFormatters(scope,activeLanguage,interpVars[i][1])
|
||||
// 执行默认的数据类型格式化器
|
||||
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value))
|
||||
if(defaultFormatter){
|
||||
formatterFuncs.splice(0,0,defaultFormatter)
|
||||
}
|
||||
value = executeFormatter(value,formatterFuncs)
|
||||
result=result.replace(match,transformToString(value))
|
||||
i+=1
|
||||
}else{
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,7 +368,7 @@ module.exports.replaceInterpolateVars = function(template,...args) {
|
||||
}
|
||||
|
||||
// 默认语言配置
|
||||
module.exports.defaultLanguageSettings = {
|
||||
const defaultLanguageSettings = {
|
||||
defaultLanguage: "cn",
|
||||
activeLanguage: "cn",
|
||||
languages:{
|
||||
@ -178,23 +396,25 @@ function isMessageId(content){
|
||||
* this===scope 当前绑定的scope
|
||||
*
|
||||
*/
|
||||
module.exports.translate = function(message) {
|
||||
function translate(message) {
|
||||
const scope = this
|
||||
const activeLanguage = scope.settings.activeLanguage
|
||||
let vars={} // 插值变量
|
||||
let pluralVars=[]
|
||||
let vars={} // 插值变量
|
||||
let pluralVars=[] // 复数变量
|
||||
try{
|
||||
// 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用
|
||||
if(arguments.length === 2 && typeof(arguments[1])=='object'){
|
||||
Object.assign(vars,arguments[1])
|
||||
Object.entries(vars).forEach(([key,value])=>{
|
||||
Object.entries(vars).forEach(([name,value])=>{
|
||||
if(typeof(value)==="function"){
|
||||
try{
|
||||
vars[key] = value()
|
||||
vars[name] = value()
|
||||
}catch(e){
|
||||
vars[key] = value
|
||||
vars[name] = value
|
||||
}
|
||||
}
|
||||
if(key.startsWith("$")) pluralVars.push(key) // 复数变量
|
||||
// 复数变量
|
||||
if(name.startsWith("$")) pluralVars.push(name)
|
||||
})
|
||||
}else if(arguments.length >= 2){
|
||||
vars = [...arguments].splice(1).map(arg=>typeof(arg)==="function" ? arg() : arg)
|
||||
@ -206,7 +426,7 @@ module.exports.translate = function(message) {
|
||||
if(isMessageId(message)){
|
||||
message = scope.default[message] || message
|
||||
}
|
||||
return replaceInterpolateVars.call(scope,message,vars)
|
||||
return replaceInterpolatedVars.call(scope,message,vars)
|
||||
}else{
|
||||
// 1. 获取翻译后的文本内容
|
||||
// 如果没有启用babel插件时,需要先将文本内容转换为msgId
|
||||
@ -243,24 +463,26 @@ module.exports.translate = function(message) {
|
||||
* VoerkaI18n.off("change",(language)=>{})
|
||||
*
|
||||
* */
|
||||
module.exports.i18n = class I18n{
|
||||
static instance = null; // 单例引用
|
||||
callbacks = [] // 当切换语言时的回调事件
|
||||
class I18n{
|
||||
static instance = null; // 单例引用
|
||||
callbacks = [] // 当切换语言时的回调事件
|
||||
constructor(settings={}){
|
||||
if(i18n.instance==null){
|
||||
this.reset()
|
||||
i18n.instance = this;
|
||||
}
|
||||
this._settings = deepMerge(defaultLanguageSettings,settings)
|
||||
this._scopes=[] // [{cn:{...},en:Promise,de:Promise},{...},{...}]
|
||||
this._scopes=[]
|
||||
return i18n.instance;
|
||||
}
|
||||
get settings(){ return this._settings }
|
||||
get scopes(){ return this._scopes }
|
||||
// 当前激活语言
|
||||
get language(){return this._settings.activeLanguage}
|
||||
get activeLanguage(){ return this._settings.activeLanguage}
|
||||
// 默认语言
|
||||
get defaultLanguage(){return this.this._settings.defaultLanguage}
|
||||
get defaultLanguage(){ return this.this._settings.defaultLanguage}
|
||||
// 支持的语言列表
|
||||
get languages(){return this._settings.languages}
|
||||
get languages(){ return this._settings.languages}
|
||||
// 订阅语言切换事件
|
||||
on(callback){
|
||||
this.callbacks.push(callback)
|
||||
@ -353,8 +575,16 @@ module.exports.translate = function(message) {
|
||||
* @param {*} scope
|
||||
*/
|
||||
register(scope){
|
||||
scope.i18nSettings = this.settings
|
||||
scope.global = this._settings
|
||||
this._scopes.push(scope)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports ={
|
||||
getInterpolatedVars,
|
||||
replaceInterpolatedVars,
|
||||
I18n,
|
||||
translate,
|
||||
defaultLanguageSettings
|
||||
}
|
@ -1,8 +1,16 @@
|
||||
import messageIds from "./messageIds"
|
||||
{{if moduleTye === "esm"}}
|
||||
import messageIds from "./idMap.js"
|
||||
import { translate,i18n } from "voerka-i18n"
|
||||
import defaultMessages from "./{{defaultLanguage}}.js"
|
||||
import i18nSettings from "./settings.js"
|
||||
import formatters from "voerka-i18n/formatters"
|
||||
import formatters from "../formatters"
|
||||
{{else}}
|
||||
const messageIds = require("./idMap")
|
||||
const { translate,i18n } = require("voerka-i18n")
|
||||
const defaultMessages = require("./{{defaultLanguage}}.js")
|
||||
const i18nSettings = require("./settings.js")
|
||||
const formatters = require("../formatters")
|
||||
{{/if}}
|
||||
|
||||
// 自动创建全局VoerkaI18n实例
|
||||
if(!globalThis.VoerkaI18n){
|
||||
@ -13,13 +21,10 @@ let scope = {
|
||||
defaultLanguage: "{{defaultLanguage}}", // 默认语言名称
|
||||
default: defaultMessages, // 默认语言包
|
||||
messages : defaultMessages, // 当前语言包
|
||||
idMap:messageIds, // 消息id映射列表
|
||||
formatters:{
|
||||
...formatters,
|
||||
...i18nSettings.formatters || {}
|
||||
},
|
||||
idMap:messageIds, // 消息id映射列表
|
||||
formatters, // 当前作用域的格式化函数列表
|
||||
loaders:{}, // 异步加载语言文件的函数列表
|
||||
i18nSettings:{} // 引用全局VoerkaI18n实例
|
||||
global:{} // 引用全局VoerkaI18n配置,注册后自动引用
|
||||
}
|
||||
|
||||
let supportedlanguages = {}
|
||||
@ -34,5 +39,10 @@ const t = ()=> translate.bind(scope)(...arguments)
|
||||
// 注册当前作用域到全局VoerkaI18n实例
|
||||
VoerkaI18n.register(scope)
|
||||
|
||||
{{if moduleTye === "esm"}}
|
||||
export scope
|
||||
export t
|
||||
{{else}}
|
||||
module.exports.scope = scope
|
||||
module.exports.t = t
|
||||
{{/if}}
|
83
src/templates/formatters.js
Normal file
83
src/templates/formatters.js
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
|
||||
格式化器用来对翻译文本内容中的插值变量进行格式化,
|
||||
比如将一个数字格式化为货币格式,或者将一个日期格式化为友好的日期格式。
|
||||
|
||||
- 以下定义了一些格式化器,在中文场景下,会启用这些格式化器。
|
||||
import dayjs from "dayjs";
|
||||
const formatters = {
|
||||
"*":{ // 在所有语言下生效的格式化器
|
||||
$types:{...} // 只作用于特定数据类型的默认格式化器
|
||||
},
|
||||
cn:{
|
||||
// 只作用于特定数据类型的格式化器
|
||||
$types:{
|
||||
Date:(value)=>dayjs(value).format("YYYY年MM月DD日 HH:mm:ss"),
|
||||
},
|
||||
date:(value)=>dayjs(value).format("YYYY年MM月DD日")
|
||||
bjTime:(value)=>"北京时间"+ value,
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
Date:(value)=>dayjs(value).format("YYYY/MM/DD HH:mm:ss"), // 默认的格式化器
|
||||
},
|
||||
date:(value)=>dayjs(value).format("YYYY/MM/DD")
|
||||
bjTime:(value)=>"BeiJing "+ value,
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
[格式化器名称]:(value)=>{...},
|
||||
}
|
||||
}
|
||||
- 在翻译函数中使用格式化器的方法,示例如下:
|
||||
|
||||
t("Now is { value | date | bjTime }",{value: new Date()})
|
||||
其等效于:
|
||||
t(`Now is ${bjTime(date(value))",{value: new Date()})
|
||||
由于value分别经过两个管道符转换,上一个管道符的输出作为下一个管道符的输入,可以多次使用管道符。
|
||||
|
||||
最终的输出结果:
|
||||
中文: "现在是北京时间2022年3月1日"
|
||||
英文: "Now is BeiJing 2022/03/01"
|
||||
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
const formatters = {
|
||||
"*":{ }, // 在所有语言下生效的格式化器
|
||||
$types:{ } // 在所有语言下只作用于特定数据类型的格式化器
|
||||
{{each languages}}
|
||||
{{$value.name}}:{
|
||||
$types:{
|
||||
"*":{
|
||||
|
||||
},
|
||||
Date:{
|
||||
|
||||
},
|
||||
Number:{
|
||||
|
||||
},
|
||||
String:{
|
||||
|
||||
},
|
||||
Array:{
|
||||
|
||||
},
|
||||
Object:{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
{{if moduleTye === "esm"}}
|
||||
export default formatters
|
||||
{{else}}
|
||||
module.exports = formatters
|
||||
{{/if}}
|
||||
|
@ -1,7 +1,74 @@
|
||||
const { getInterpolatedVars, replaceInterpolateVars} = require('../src/index.js')
|
||||
const dayjs = require('dayjs');
|
||||
const { getInterpolatedVars, replaceInterpolatedVars} = require('../src/index.js')
|
||||
|
||||
const scope1 ={
|
||||
defaultLanguage: "cn", // 默认语言名称
|
||||
default: { // 默认语言包
|
||||
|
||||
},
|
||||
messages : { // 当前语言包
|
||||
|
||||
},
|
||||
idMap:{ // 消息id映射列表
|
||||
|
||||
},
|
||||
formatters:{ // 当前作用域的格式化函数列表
|
||||
"*":{
|
||||
$types:{
|
||||
Date:(v)=>dayjs(v).format('YYYY/MM/DD'),
|
||||
Boolean:(v)=>v?"True":"False",
|
||||
},
|
||||
upper:(v)=>v.toUpperCase(),
|
||||
lower:(v)=>v.toLowerCase(),
|
||||
padStart:(v,len,pad)=>v.padStart(len,pad),
|
||||
padStart:(v,len,pad)=>v.padStart(len,pad),
|
||||
},
|
||||
cn:{
|
||||
$types:{
|
||||
Date:(v)=>dayjs(v).format('YYYY年MM月DD日'),
|
||||
Boolean:(v)=>v?"是":"否",
|
||||
}
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
loaders:{}, // 异步加载语言文件的函数列表
|
||||
global:{// 引用全局VoerkaI18n配置
|
||||
defaultLanguage: "cn",
|
||||
activeLanguage: "cn",
|
||||
languages:[
|
||||
{name:"cn",title:"中文",default:true},
|
||||
{name:"en",title:"英文"},
|
||||
{name:"de",title:"德语"},
|
||||
{name:"jp",title:"日语"}
|
||||
],
|
||||
formatters:{ // 当前作用域的格式化函数列表
|
||||
"*":{
|
||||
$types:{
|
||||
|
||||
}
|
||||
},
|
||||
cn:{
|
||||
$types:{
|
||||
|
||||
}
|
||||
},
|
||||
en:{
|
||||
$types:{
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const replaceVars = replaceInterpolatedVars.bind(scope1)
|
||||
|
||||
|
||||
test("获取表达式中的插值变量",done=>{
|
||||
test("获取翻译内容中的插值变量",done=>{
|
||||
const results = getInterpolatedVars("中华人民共和国成立于{date | year | time }年,首都是{city}市");
|
||||
expect(results.map(r=>r[0]).join(",")).toBe("date,city");
|
||||
expect(results[0][0]).toEqual("date");
|
||||
@ -10,7 +77,8 @@ test("获取表达式中的插值变量",done=>{
|
||||
expect(results[1][1]).toEqual([]);
|
||||
done()
|
||||
})
|
||||
test("表达式中定义了重复的插值变量",done=>{
|
||||
|
||||
test("获取翻译内容中定义了重复的插值变量",done=>{
|
||||
const results = getInterpolatedVars("{a}{a}{a|x}{a|x}{a|x|y}{a|x|y}");
|
||||
expect(results.length).toEqual(3);
|
||||
expect(results[0][0]).toEqual("a");
|
||||
@ -21,11 +89,23 @@ test("表达式中定义了重复的插值变量",done=>{
|
||||
expect(results[2][1]).toEqual(["x","y"]);
|
||||
done()
|
||||
})
|
||||
// test("替代表达式中的插值变量",done=>{
|
||||
// const results = replaceInterpolateVars("中华人民共和国成立于{date}年,首都是{city}市",{
|
||||
// date:1949,
|
||||
// city:"北京"
|
||||
// });
|
||||
// expect(results).toBe("中华人民共和国成立于1949年,首都是北京市");
|
||||
// done()
|
||||
// }
|
||||
|
||||
test("替换翻译内容的位置插值变量",done=>{
|
||||
|
||||
expect(replaceVars("{}{}{}",1,2,3)).toBe("123");
|
||||
expect(replaceVars("{a}{b}{c}",1,2,3)).toBe("123");
|
||||
// 定义了一些无效的格式化器,直接忽略
|
||||
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3)).toBe("123");
|
||||
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3,4,5,6)).toBe("123");
|
||||
expect(replaceVars("{ a|}{b|dd}{c|}{}",1,2,3)).toBe("123{}");
|
||||
// 数据值进行
|
||||
expect(replaceVars("{}{}{}",1,"2",true)).toBe("12true");
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
test("替换翻译内容的命名插值变量",done=>{
|
||||
expect(replaceVars("{a}{b}{c}",{a:1,b:2,c:3})).toBe("123");
|
||||
expect(replaceVars("{a}{b}{c}{a}{b}{c}",{a:1,b:"2",c:3})).toBe("123123");
|
||||
done()
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user