update
This commit is contained in:
parent
c8c30b39bf
commit
4a51739f03
27
demoapps/app/languages/cn.js
Normal file
27
demoapps/app/languages/cn.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export default {
|
||||||
|
"1": "a1:aaaaa",
|
||||||
|
"2": "no aaaaa",
|
||||||
|
"3": "bbbbb",
|
||||||
|
"4": "eeeeee",
|
||||||
|
"5": "x:from a",
|
||||||
|
"6": "中华人民共和国",
|
||||||
|
"7": "a2:aaaaa",
|
||||||
|
"8": "no erer",
|
||||||
|
"9": "no aaareraa",
|
||||||
|
"10": "no fdfd",
|
||||||
|
"11": "fdgfdgfdg",
|
||||||
|
"12": "aaaaa",
|
||||||
|
"13": "aaaaa1",
|
||||||
|
"14": "aaaaa2",
|
||||||
|
"15": "aaaaa 3",
|
||||||
|
"16": "ccccc",
|
||||||
|
"17": "cccccdc",
|
||||||
|
"18": "ddddd中国",
|
||||||
|
"19": "html-a",
|
||||||
|
"20": "html-b",
|
||||||
|
"21": "html-c",
|
||||||
|
"22": "from a",
|
||||||
|
"23": "from b",
|
||||||
|
"24": "from c1",
|
||||||
|
"25": "from c2"
|
||||||
|
}
|
27
demoapps/app/languages/de.js
Normal file
27
demoapps/app/languages/de.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export default {
|
||||||
|
"1": "德文:aaaaa",
|
||||||
|
"2": "no aaaaa",
|
||||||
|
"3": "bbbbb",
|
||||||
|
"4": "eeeeee",
|
||||||
|
"5": "x:from a",
|
||||||
|
"6": "中华人民共和国",
|
||||||
|
"7": "a2:aaaaa",
|
||||||
|
"8": "no erer",
|
||||||
|
"9": "no aaareraa",
|
||||||
|
"10": "no fdfd",
|
||||||
|
"11": "fdgfdgfdg",
|
||||||
|
"12": "aaaaa",
|
||||||
|
"13": "aaaaa1",
|
||||||
|
"14": "aaaaa2",
|
||||||
|
"15": "aaaaa 3",
|
||||||
|
"16": "ccccc",
|
||||||
|
"17": "cccccdc",
|
||||||
|
"18": "ddddd中国",
|
||||||
|
"19": "html-a",
|
||||||
|
"20": "html-b",
|
||||||
|
"21": "html-c",
|
||||||
|
"22": "from a",
|
||||||
|
"23": "from b",
|
||||||
|
"24": "from c1",
|
||||||
|
"25": "from c2"
|
||||||
|
}
|
27
demoapps/app/languages/en.js
Normal file
27
demoapps/app/languages/en.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export default {
|
||||||
|
"1": "a1:aaaaa",
|
||||||
|
"2": "no aaaaa",
|
||||||
|
"3": "bbbbb",
|
||||||
|
"4": "eeeeee",
|
||||||
|
"5": "x:from a",
|
||||||
|
"6": "中华人民共和国",
|
||||||
|
"7": "a2:aaaaa",
|
||||||
|
"8": "no erer",
|
||||||
|
"9": "no aaareraa",
|
||||||
|
"10": "no fdfd",
|
||||||
|
"11": "fdgfdgfdg",
|
||||||
|
"12": "aaaaa",
|
||||||
|
"13": "aaaaa1",
|
||||||
|
"14": "aaaaa2",
|
||||||
|
"15": "aaaaa 3",
|
||||||
|
"16": "ccccc",
|
||||||
|
"17": "cccccdc",
|
||||||
|
"18": "ddddd中国",
|
||||||
|
"19": "html-a",
|
||||||
|
"20": "html-b",
|
||||||
|
"21": "html-c",
|
||||||
|
"22": "from a",
|
||||||
|
"23": "from b",
|
||||||
|
"24": "from c1",
|
||||||
|
"25": "from c2"
|
||||||
|
}
|
42
demoapps/app/languages/index.js
Normal file
42
demoapps/app/languages/index.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
// 自动创建全局VoerkaI18n实例
|
||||||
|
if(!globalThis.VoerkaI18n){
|
||||||
|
globalThis.VoerkaI18n = new i18n(i18nSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
let scope = {
|
||||||
|
defaultLanguage: "cn", // 默认语言名称
|
||||||
|
default: defaultMessages, // 默认语言包
|
||||||
|
messages : defaultMessages, // 当前语言包
|
||||||
|
ids:messageIds, // 消息id映射列表
|
||||||
|
formatters:{
|
||||||
|
...formatters,
|
||||||
|
...i18nSettings.formatters || {}
|
||||||
|
},
|
||||||
|
loaders:{}, // 异步加载语言文件的函数列表
|
||||||
|
settings:{} // 引用全局VoerkaI18n实例的配置
|
||||||
|
}
|
||||||
|
|
||||||
|
let supportedlanguages = {}
|
||||||
|
|
||||||
|
messages["cn"]= defaultMessages
|
||||||
|
|
||||||
|
scope.loaders["en"] = ()=>import("./en.js")
|
||||||
|
|
||||||
|
scope.loaders["de"] = ()=>import("./de.js")
|
||||||
|
|
||||||
|
scope.loaders["jp"] = ()=>import("./jp.js")
|
||||||
|
|
||||||
|
|
||||||
|
const t = ()=> translate.bind(scope)(...arguments)
|
||||||
|
|
||||||
|
// 注册当前作用域到全局VoerkaI18n实例
|
||||||
|
VoerkaI18n.register(scope)
|
||||||
|
|
||||||
|
export scope
|
||||||
|
export t
|
27
demoapps/app/languages/jp.js
Normal file
27
demoapps/app/languages/jp.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export default {
|
||||||
|
"1": "a1:aaaaa",
|
||||||
|
"2": "no aaaaa",
|
||||||
|
"3": "bbbbb",
|
||||||
|
"4": "eeeeee",
|
||||||
|
"5": "x:from a",
|
||||||
|
"6": "中华人民共和国",
|
||||||
|
"7": "a2:aaaaa",
|
||||||
|
"8": "no erer",
|
||||||
|
"9": "no aaareraa",
|
||||||
|
"10": "no fdfd",
|
||||||
|
"11": "fdgfdgfdg",
|
||||||
|
"12": "aaaaa",
|
||||||
|
"13": "aaaaa1",
|
||||||
|
"14": "aaaaa2",
|
||||||
|
"15": "aaaaa 3",
|
||||||
|
"16": "ccccc",
|
||||||
|
"17": "cccccdc",
|
||||||
|
"18": "ddddd中国",
|
||||||
|
"19": "html-a",
|
||||||
|
"20": "html-b",
|
||||||
|
"21": "html-c",
|
||||||
|
"22": "from a",
|
||||||
|
"23": "from b",
|
||||||
|
"24": "from c1",
|
||||||
|
"25": "from c2"
|
||||||
|
}
|
27
demoapps/app/languages/messageIds.js
Normal file
27
demoapps/app/languages/messageIds.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export default {
|
||||||
|
"a1:aaaaa": 1,
|
||||||
|
"no aaaaa": 2,
|
||||||
|
"bbbbb": 3,
|
||||||
|
"eeeeee": 4,
|
||||||
|
"x:from a": 5,
|
||||||
|
"中华人民共和国": 6,
|
||||||
|
"a2:aaaaa": 7,
|
||||||
|
"no erer": 8,
|
||||||
|
"no aaareraa": 9,
|
||||||
|
"no fdfd": 10,
|
||||||
|
"fdgfdgfdg": 11,
|
||||||
|
"aaaaa": 12,
|
||||||
|
"aaaaa1": 13,
|
||||||
|
"aaaaa2": 14,
|
||||||
|
"aaaaa 3": 15,
|
||||||
|
"ccccc": 16,
|
||||||
|
"cccccdc": 17,
|
||||||
|
"ddddd中国": 18,
|
||||||
|
"html-a": 19,
|
||||||
|
"html-b": 20,
|
||||||
|
"html-c": 21,
|
||||||
|
"from a": 22,
|
||||||
|
"from b": 23,
|
||||||
|
"from c1": 24,
|
||||||
|
"from c2": 25
|
||||||
|
}
|
3
demoapps/app/languages/package.json
Normal file
3
demoapps/app/languages/package.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"type": "module"
|
||||||
|
}
|
31
demoapps/app/languages/settings.js
Normal file
31
demoapps/app/languages/settings.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export default {
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"name": "en",
|
||||||
|
"title": "英文"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cn",
|
||||||
|
"title": "中文",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "de",
|
||||||
|
"title": "德语"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "jp",
|
||||||
|
"title": "日本語"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultLanguage": "cn",
|
||||||
|
"activeLanguage": "cn",
|
||||||
|
"namespaces": {
|
||||||
|
"a": [
|
||||||
|
"a"
|
||||||
|
],
|
||||||
|
"b": [
|
||||||
|
"b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
91
demoapps/app/languages/translates/a.json
Normal file
91
demoapps/app/languages/translates/a.json
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"a1:aaaaa": {
|
||||||
|
"en": "a1:aaaaa",
|
||||||
|
"de": "德文:aaaaa",
|
||||||
|
"jp": "a1:aaaaa",
|
||||||
|
"$file": [
|
||||||
|
"a\\a1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no aaaaa": {
|
||||||
|
"en": "no aaaaa",
|
||||||
|
"de": "no aaaaa",
|
||||||
|
"jp": "no aaaaa",
|
||||||
|
"$file": [
|
||||||
|
"a\\a1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bbbbb": {
|
||||||
|
"en": "bbbbb",
|
||||||
|
"de": "bbbbb",
|
||||||
|
"jp": "bbbbb",
|
||||||
|
"$file": [
|
||||||
|
"a\\a1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eeeeee": {
|
||||||
|
"en": "eeeeee",
|
||||||
|
"de": "eeeeee",
|
||||||
|
"jp": "eeeeee",
|
||||||
|
"$file": [
|
||||||
|
"a\\a1.js",
|
||||||
|
"a\\a2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"x:from a": {
|
||||||
|
"en": "x:from a",
|
||||||
|
"de": "x:from a",
|
||||||
|
"jp": "x:from a",
|
||||||
|
"$file": [
|
||||||
|
"a\\a1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"中华人民共和国": {
|
||||||
|
"en": "中华人民共和国",
|
||||||
|
"de": "中华人民共和国",
|
||||||
|
"jp": "中华人民共和国",
|
||||||
|
"$file": [
|
||||||
|
"a\\a1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"a2:aaaaa": {
|
||||||
|
"en": "a2:aaaaa",
|
||||||
|
"de": "a2:aaaaa",
|
||||||
|
"jp": "a2:aaaaa",
|
||||||
|
"$file": [
|
||||||
|
"a\\a2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no erer": {
|
||||||
|
"en": "no erer",
|
||||||
|
"de": "no erer",
|
||||||
|
"jp": "no erer",
|
||||||
|
"$file": [
|
||||||
|
"a\\a2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no aaareraa": {
|
||||||
|
"en": "no aaareraa",
|
||||||
|
"de": "no aaareraa",
|
||||||
|
"jp": "no aaareraa",
|
||||||
|
"$file": [
|
||||||
|
"a\\a2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no fdfd": {
|
||||||
|
"en": "no fdfd",
|
||||||
|
"de": "no fdfd",
|
||||||
|
"jp": "no fdfd",
|
||||||
|
"$file": [
|
||||||
|
"a\\a2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fdgfdgfdg": {
|
||||||
|
"en": "fdgfdgfdg",
|
||||||
|
"de": "fdgfdgfdg",
|
||||||
|
"jp": "fdgfdgfdg",
|
||||||
|
"$file": [
|
||||||
|
"a\\a2.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
26
demoapps/app/languages/translates/b.json
Normal file
26
demoapps/app/languages/translates/b.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"aaaaa": {
|
||||||
|
"en": "aaaaa",
|
||||||
|
"de": "aaaaa",
|
||||||
|
"jp": "aaaaa",
|
||||||
|
"$file": [
|
||||||
|
"b\\b1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bbbbb": {
|
||||||
|
"en": "bbbbb",
|
||||||
|
"de": "bbbbb",
|
||||||
|
"jp": "bbbbb",
|
||||||
|
"$file": [
|
||||||
|
"b\\b1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eeeeee": {
|
||||||
|
"en": "eeeeee",
|
||||||
|
"de": "eeeeee",
|
||||||
|
"jp": "eeeeee",
|
||||||
|
"$file": [
|
||||||
|
"b\\b1.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
139
demoapps/app/languages/translates/default.json
Normal file
139
demoapps/app/languages/translates/default.json
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
"aaaaa": {
|
||||||
|
"en": "aaaaa",
|
||||||
|
"de": "aaaaa",
|
||||||
|
"jp": "aaaaa",
|
||||||
|
"$file": [
|
||||||
|
"index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"aaaaa1": {
|
||||||
|
"en": "aaaaa1",
|
||||||
|
"de": "aaaaa1",
|
||||||
|
"jp": "aaaaa1",
|
||||||
|
"$file": [
|
||||||
|
"index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"aaaaa2": {
|
||||||
|
"en": "aaaaa2",
|
||||||
|
"de": "aaaaa2",
|
||||||
|
"jp": "aaaaa2",
|
||||||
|
"$file": [
|
||||||
|
"index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"aaaaa 3": {
|
||||||
|
"en": "aaaaa 3",
|
||||||
|
"de": "aaaaa 3",
|
||||||
|
"jp": "aaaaa 3",
|
||||||
|
"$file": [
|
||||||
|
"index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bbbbb": {
|
||||||
|
"en": "bbbbb",
|
||||||
|
"de": "bbbbb",
|
||||||
|
"jp": "bbbbb",
|
||||||
|
"$file": [
|
||||||
|
"index.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eeeeee": {
|
||||||
|
"en": "eeeeee",
|
||||||
|
"de": "eeeeee",
|
||||||
|
"jp": "eeeeee",
|
||||||
|
"$file": [
|
||||||
|
"index.js",
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ccccc": {
|
||||||
|
"en": "ccccc",
|
||||||
|
"de": "ccccc",
|
||||||
|
"jp": "ccccc",
|
||||||
|
"$file": [
|
||||||
|
"c\\c1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cccccdc": {
|
||||||
|
"en": "cccccdc",
|
||||||
|
"de": "cccccdc",
|
||||||
|
"jp": "cccccdc",
|
||||||
|
"$file": [
|
||||||
|
"c\\c1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"a2:aaaaa": {
|
||||||
|
"en": "a2:aaaaa",
|
||||||
|
"de": "a2:aaaaa",
|
||||||
|
"jp": "a2:aaaaa",
|
||||||
|
"$file": [
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no erer": {
|
||||||
|
"en": "no erer",
|
||||||
|
"de": "no erer",
|
||||||
|
"jp": "no erer",
|
||||||
|
"$file": [
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no aaareraa": {
|
||||||
|
"en": "no aaareraa",
|
||||||
|
"de": "no aaareraa",
|
||||||
|
"jp": "no aaareraa",
|
||||||
|
"$file": [
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no fdfd": {
|
||||||
|
"en": "no fdfd",
|
||||||
|
"de": "no fdfd",
|
||||||
|
"jp": "no fdfd",
|
||||||
|
"$file": [
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fdgfdgfdg": {
|
||||||
|
"en": "fdgfdgfdg",
|
||||||
|
"de": "fdgfdgfdg",
|
||||||
|
"jp": "fdgfdgfdg",
|
||||||
|
"$file": [
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ddddd中国": {
|
||||||
|
"en": "ddddd中国",
|
||||||
|
"de": "ddddd中国",
|
||||||
|
"jp": "ddddd中国",
|
||||||
|
"$file": [
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"html-a": {
|
||||||
|
"en": "html-a",
|
||||||
|
"de": "html-a",
|
||||||
|
"jp": "html-a",
|
||||||
|
"$file": [
|
||||||
|
"c\\h1.html"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"html-b": {
|
||||||
|
"en": "html-b",
|
||||||
|
"de": "html-b",
|
||||||
|
"jp": "html-b",
|
||||||
|
"$file": [
|
||||||
|
"c\\h1.html"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"html-c": {
|
||||||
|
"en": "html-c",
|
||||||
|
"de": "html-c",
|
||||||
|
"jp": "html-c",
|
||||||
|
"$file": [
|
||||||
|
"c\\h1.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
34
demoapps/app/languages/translates/x.json
Normal file
34
demoapps/app/languages/translates/x.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"from a": {
|
||||||
|
"en": "from a",
|
||||||
|
"de": "from a",
|
||||||
|
"jp": "from a",
|
||||||
|
"$file": [
|
||||||
|
"a\\a2.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"from b": {
|
||||||
|
"en": "from b",
|
||||||
|
"de": "from b",
|
||||||
|
"jp": "from b",
|
||||||
|
"$file": [
|
||||||
|
"b\\b1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"from c1": {
|
||||||
|
"en": "from c1",
|
||||||
|
"de": "from c1",
|
||||||
|
"jp": "from c1",
|
||||||
|
"$file": [
|
||||||
|
"c\\c1.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"from c2": {
|
||||||
|
"en": "from c2",
|
||||||
|
"de": "from c2",
|
||||||
|
"jp": "from c2",
|
||||||
|
"$file": [
|
||||||
|
"c\\c2.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
194
jest.config.js
Normal file
194
jest.config.js
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* For a detailed explanation regarding each configuration property, visit:
|
||||||
|
* https://jestjs.io/docs/configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// All imported modules in your tests should be mocked automatically
|
||||||
|
// automock: false,
|
||||||
|
|
||||||
|
// Stop running tests after `n` failures
|
||||||
|
// bail: 0,
|
||||||
|
|
||||||
|
// The directory where Jest should store its cached dependency information
|
||||||
|
// cacheDirectory: "C:\\Users\\Administrator\\AppData\\Local\\Temp\\jest",
|
||||||
|
|
||||||
|
// Automatically clear mock calls and instances between every test
|
||||||
|
// clearMocks: false,
|
||||||
|
|
||||||
|
// Indicates whether the coverage information should be collected while executing the test
|
||||||
|
// collectCoverage: false,
|
||||||
|
|
||||||
|
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||||
|
// collectCoverageFrom: undefined,
|
||||||
|
|
||||||
|
// The directory where Jest should output its coverage files
|
||||||
|
// coverageDirectory: undefined,
|
||||||
|
|
||||||
|
// An array of regexp pattern strings used to skip coverage collection
|
||||||
|
// coveragePathIgnorePatterns: [
|
||||||
|
// "\\\\node_modules\\\\"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Indicates which provider should be used to instrument code for coverage
|
||||||
|
coverageProvider: "v8",
|
||||||
|
|
||||||
|
// A list of reporter names that Jest uses when writing coverage reports
|
||||||
|
// coverageReporters: [
|
||||||
|
// "json",
|
||||||
|
// "text",
|
||||||
|
// "lcov",
|
||||||
|
// "clover"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
|
// coverageThreshold: undefined,
|
||||||
|
|
||||||
|
// A path to a custom dependency extractor
|
||||||
|
// dependencyExtractor: undefined,
|
||||||
|
|
||||||
|
// Make calling deprecated APIs throw helpful error messages
|
||||||
|
// errorOnDeprecated: false,
|
||||||
|
|
||||||
|
// Force coverage collection from ignored files using an array of glob patterns
|
||||||
|
// forceCoverageMatch: [],
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once before all test suites
|
||||||
|
// globalSetup: undefined,
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once after all test suites
|
||||||
|
// globalTeardown: undefined,
|
||||||
|
|
||||||
|
// A set of global variables that need to be available in all test environments
|
||||||
|
// globals: {},
|
||||||
|
|
||||||
|
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||||
|
// maxWorkers: "50%",
|
||||||
|
|
||||||
|
// An array of directory names to be searched recursively up from the requiring module's location
|
||||||
|
// moduleDirectories: [
|
||||||
|
// "node_modules"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of file extensions your modules use
|
||||||
|
// moduleFileExtensions: [
|
||||||
|
// "js",
|
||||||
|
// "jsx",
|
||||||
|
// "ts",
|
||||||
|
// "tsx",
|
||||||
|
// "json",
|
||||||
|
// "node"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||||
|
// moduleNameMapper: {},
|
||||||
|
|
||||||
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
|
// modulePathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Activates notifications for test results
|
||||||
|
// notify: false,
|
||||||
|
|
||||||
|
// An enum that specifies notification mode. Requires { notify: true }
|
||||||
|
// notifyMode: "failure-change",
|
||||||
|
|
||||||
|
// A preset that is used as a base for Jest's configuration
|
||||||
|
// preset: undefined,
|
||||||
|
|
||||||
|
// Run tests from one or more projects
|
||||||
|
// projects: undefined,
|
||||||
|
|
||||||
|
// Use this configuration option to add custom reporters to Jest
|
||||||
|
// reporters: undefined,
|
||||||
|
|
||||||
|
// Automatically reset mock state between every test
|
||||||
|
// resetMocks: false,
|
||||||
|
|
||||||
|
// Reset the module registry before running each individual test
|
||||||
|
// resetModules: false,
|
||||||
|
|
||||||
|
// A path to a custom resolver
|
||||||
|
// resolver: undefined,
|
||||||
|
|
||||||
|
// Automatically restore mock state between every test
|
||||||
|
// restoreMocks: false,
|
||||||
|
|
||||||
|
// The root directory that Jest should scan for tests and modules within
|
||||||
|
// rootDir: undefined,
|
||||||
|
|
||||||
|
// A list of paths to directories that Jest should use to search for files in
|
||||||
|
// roots: [
|
||||||
|
// "<rootDir>"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Allows you to use a custom runner instead of Jest's default test runner
|
||||||
|
// runner: "jest-runner",
|
||||||
|
|
||||||
|
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||||
|
// setupFiles: [],
|
||||||
|
|
||||||
|
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||||
|
// setupFilesAfterEnv: [],
|
||||||
|
|
||||||
|
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||||
|
// slowTestThreshold: 5,
|
||||||
|
|
||||||
|
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||||
|
// snapshotSerializers: [],
|
||||||
|
|
||||||
|
// The test environment that will be used for testing
|
||||||
|
// testEnvironment: "jest-environment-node",
|
||||||
|
|
||||||
|
// Options that will be passed to the testEnvironment
|
||||||
|
// testEnvironmentOptions: {},
|
||||||
|
|
||||||
|
// Adds a location field to test results
|
||||||
|
// testLocationInResults: false,
|
||||||
|
|
||||||
|
// The glob patterns Jest uses to detect test files
|
||||||
|
// testMatch: [
|
||||||
|
// "**/__tests__/**/*.[jt]s?(x)",
|
||||||
|
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||||
|
// testPathIgnorePatterns: [
|
||||||
|
// "\\\\node_modules\\\\"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||||
|
// testRegex: [],
|
||||||
|
|
||||||
|
// This option allows the use of a custom results processor
|
||||||
|
// testResultsProcessor: undefined,
|
||||||
|
|
||||||
|
// This option allows use of a custom test runner
|
||||||
|
// testRunner: "jest-circus/runner",
|
||||||
|
|
||||||
|
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||||
|
// testURL: "http://localhost",
|
||||||
|
|
||||||
|
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||||
|
// timers: "real",
|
||||||
|
|
||||||
|
// A map from regular expressions to paths to transformers
|
||||||
|
// transform: undefined,
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||||
|
// transformIgnorePatterns: [
|
||||||
|
// "\\\\node_modules\\\\",
|
||||||
|
// "\\.pnp\\.[^\\\\]+$"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||||
|
// unmockedModulePathPatterns: undefined,
|
||||||
|
|
||||||
|
// Indicates whether each individual test should be reported during the run
|
||||||
|
// verbose: undefined,
|
||||||
|
|
||||||
|
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||||
|
// watchPathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Whether to use watchman for file crawling
|
||||||
|
// watchman: true,
|
||||||
|
};
|
@ -4,7 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "jest",
|
||||||
"extract": "",
|
"extract": "",
|
||||||
"babeldemo": "babel ./demodata/a/a1.js --plugins=./src/babel-plugin-voerkai18n.js"
|
"babeldemo": "babel ./demodata/a/a1.js --plugins=./src/babel-plugin-voerkai18n.js"
|
||||||
},
|
},
|
||||||
@ -21,6 +21,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.17.6",
|
"@babel/cli": "^7.17.6",
|
||||||
"@babel/core": "^7.17.5"
|
"@babel/core": "^7.17.5",
|
||||||
|
"jest": "^27.5.1",
|
||||||
|
"vinyl": "^2.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2049
pnpm-lock.yaml
generated
2049
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
43
readme.md
43
readme.md
@ -4,7 +4,30 @@
|
|||||||
|
|
||||||
## 翻译函数
|
## 翻译函数
|
||||||
|
|
||||||
默认提供一个全局的翻译函数`t`
|
默认提供翻译函数`t`用来进行翻译。
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
// 从当前语言包文件夹index.js中导入翻译函数
|
||||||
|
import { t } from "<myapp>/languages"
|
||||||
|
|
||||||
|
t("中华人民共和国")
|
||||||
|
|
||||||
|
// 位置插值变量
|
||||||
|
t("中华人民共和国{}","万岁")
|
||||||
|
t("中华人民共和国成立于{}年,首都{}",1949,"北京")
|
||||||
|
|
||||||
|
// 当仅有两个参数且第2个参数是{}类型时,启用字典插值变量
|
||||||
|
t("中华人民共和国成立于{year}年,首都{capital}",{year:1949,capital:"北京"})
|
||||||
|
|
||||||
|
// 插值变量可以是同步函数,在进行插值时调用。
|
||||||
|
t("中华人民共和国成立于{year}年,首都{capital}",()=>1949,"北京")
|
||||||
|
|
||||||
|
// 对插值变量启用格式化器
|
||||||
|
t("中华人民共和国成立于{date | year}年",{date:new Date('')})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 复数
|
## 复数
|
||||||
|
|
||||||
@ -69,7 +92,15 @@ t("我{}一辆车",2) // == "I have two cars"
|
|||||||
t("我{}一辆车",100) // == "I have 100 cars"
|
t("我{}一辆车",100) // == "I have 100 cars"
|
||||||
```
|
```
|
||||||
|
|
||||||
- **采用以$字符开头的命名插值变量**
|
- **复数命名插值变量**
|
||||||
|
|
||||||
|
当启用复数功能时,`t`函数需要知道根据哪个变量来决定采用何种复数形式。
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
t("{}有{}辆车","张三",0)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
以上采用位置插值变量时只能处理第一个位置插值复数,当翻译内容存在多个位置插值变量时,因为无法获取哪一个位置变量是数字,因此就不能有效处理。如:
|
以上采用位置插值变量时只能处理第一个位置插值复数,当翻译内容存在多个位置插值变量时,因为无法获取哪一个位置变量是数字,因此就不能有效处理。如:
|
||||||
```javascript
|
```javascript
|
||||||
@ -77,7 +108,7 @@ t("{}有{}辆车","张三",0)
|
|||||||
t("{}有{}辆车","张三",1)
|
t("{}有{}辆车","张三",1)
|
||||||
```
|
```
|
||||||
此种情况下就需要采用命名插值变量来处理。
|
此种情况下就需要采用命名插值变量来处理。
|
||||||
具体的方式是约定,当插值字典中存在以`$字符开头`的变量时,并且值是`数字`时,根据该变量来引用复数。以下例中,`t`函数根据`$count`值来处理复数。
|
具体的方式是约定当插值字典中存在以`$字符开头`的变量时,并且值是`数字`时,根据该变量来引用复数。以下例中,`t`函数根据`$count`值来处理复数。
|
||||||
```javascript
|
```javascript
|
||||||
t("{name}有{$count}辆车",{name:"Tom",$count:0}) // == "Tom don't have a car"
|
t("{name}有{$count}辆车",{name:"Tom",$count:0}) // == "Tom don't have a car"
|
||||||
t("{name}有{$count}辆车",{name:"Tom",$count:1}) // == "Tom have a car"
|
t("{name}有{$count}辆车",{name:"Tom",$count:1}) // == "Tom have a car"
|
||||||
@ -153,13 +184,11 @@ new VoerkaI18n({
|
|||||||
// languages/translates/default.json
|
// languages/translates/default.json
|
||||||
{
|
{
|
||||||
"今天是{date}":{
|
"今天是{date}":{
|
||||||
en:"Today is {date}", // 使用默认格式
|
en:"Today is {date}", // 使用默认的格式化器
|
||||||
},
|
},
|
||||||
"现在是北京时间:{date}":{
|
"现在是北京时间:{date}":{
|
||||||
en:"Now is {date.time}"
|
en:"Now is { date | time}" // 使用指定的格式化器time
|
||||||
},
|
},
|
||||||
"":{}
|
|
||||||
}
|
|
||||||
|
|
||||||
t("今天是{date}",{date:new Date()})
|
t("今天是{date}",{date:new Date()})
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ babel.transform(code, {
|
|||||||
{
|
{
|
||||||
location:"./languages" // 指定语言文件存放的目录,即保存编译后的语言文件的文件夹
|
location:"./languages" // 指定语言文件存放的目录,即保存编译后的语言文件的文件夹
|
||||||
}
|
}
|
||||||
]]
|
]
|
||||||
|
]
|
||||||
}, function(err, result) {
|
}, function(err, result) {
|
||||||
console.log(result.code)
|
console.log(result.code)
|
||||||
});
|
});
|
@ -4,4 +4,4 @@ const compile = require('./compile');
|
|||||||
const path = require("path")
|
const path = require("path")
|
||||||
|
|
||||||
|
|
||||||
compile(path.resolve(__dirname,"../demodata/languages"))
|
compile(path.resolve(__dirname,"../demoapps/app/languages"))
|
@ -9,48 +9,30 @@ if(!globalThis.VoerkaI18n){
|
|||||||
globalThis.VoerkaI18n = new i18n(i18nSettings)
|
globalThis.VoerkaI18n = new i18n(i18nSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
let scopeContext = {
|
let scope = {
|
||||||
messages : defaultMessages, // 当前语言的消息
|
defaultLanguage: "{{defaultLanguage}}", // 默认语言名称
|
||||||
ids:messageIds,
|
default: defaultMessages, // 默认语言包
|
||||||
|
messages : defaultMessages, // 当前语言包
|
||||||
|
idMap:messageIds, // 消息id映射列表
|
||||||
formatters:{
|
formatters:{
|
||||||
...formatters,
|
...formatters,
|
||||||
...i18nSettings.formatters || {}
|
...i18nSettings.formatters || {}
|
||||||
},
|
},
|
||||||
languageLoaders:{}
|
loaders:{}, // 异步加载语言文件的函数列表
|
||||||
|
i18nSettings:{} // 引用全局VoerkaI18n实例
|
||||||
}
|
}
|
||||||
|
|
||||||
let supportedlanguages = {}
|
let supportedlanguages = {}
|
||||||
|
|
||||||
messages["{{defaultLanguage}}"]= defaultMessages
|
messages["{{defaultLanguage}}"]= defaultMessages
|
||||||
{{each languages}}{{if $value.name !== defaultLanguage}}
|
{{each languages}}{{if $value.name !== defaultLanguage}}
|
||||||
scopeContext.languageLoaders["{{$value.name}}"] = ()=>import("./{{$value.name}}.js")
|
scope.loaders["{{$value.name}}"] = ()=>import("./{{$value.name}}.js")
|
||||||
{{/if}}{{/each}}
|
{{/if}}{{/each}}
|
||||||
|
|
||||||
const t = ()=> translate.bind(scopeContext)(...arguments)
|
const t = ()=> translate.bind(scope)(...arguments)
|
||||||
|
|
||||||
|
// 注册当前作用域到全局VoerkaI18n实例
|
||||||
|
VoerkaI18n.register(scope)
|
||||||
|
|
||||||
// 侦听语言切换事件
|
export scope
|
||||||
VoerkaI18n.on(async function(lang)=>{
|
|
||||||
|
|
||||||
if(lang === defaultLanguage){
|
|
||||||
scopeContext.messages = defaultMessages
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const loader = scopeContext.languageLoaders[lang]
|
|
||||||
|
|
||||||
if(typeof(loader) === "function"){
|
|
||||||
try{
|
|
||||||
scopeContext.messages = await loader()
|
|
||||||
}catch(e){
|
|
||||||
console.warn(`Error loading language ${lang} : ${e.message}`)
|
|
||||||
scopeContext.messages = defaultMessages
|
|
||||||
}
|
|
||||||
}else if(typeof(loader) === "object"){
|
|
||||||
scopeContext.messages = loader
|
|
||||||
}else{
|
|
||||||
scopeContext.messages = defaultMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
export t
|
export t
|
@ -3,7 +3,7 @@ const extract = require('./extract.plugin');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
|
||||||
const soucePath = path.join(__dirname,'../demodata')
|
const soucePath = path.join(__dirname,'../demoapps/app')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -26,4 +26,4 @@ gulp.src([
|
|||||||
"b":"b",
|
"b":"b",
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.pipe(gulp.dest(path.join(__dirname,'../demodata/languages')));
|
.pipe(gulp.dest(path.join(__dirname,'../demoapps/app/languages')));
|
@ -20,6 +20,20 @@ const DefaultTranslateExtractor = String.raw`\b{funcName}\(\s*("|'){1}(?:((?<nam
|
|||||||
// 从html文件标签中提取翻译文本
|
// 从html文件标签中提取翻译文本
|
||||||
const DefaultHtmlAttrExtractor = String.raw`\<(?<tagName>\w+)(.*?)(?<i18nKey>{attrName}\s*\=\s*)([\"\'']{1})(?<text>.*?)(\4){1}\s*(.*?)(\>|\/\>)`
|
const DefaultHtmlAttrExtractor = String.raw`\<(?<tagName>\w+)(.*?)(?<i18nKey>{attrName}\s*\=\s*)([\"\'']{1})(?<text>.*?)(\4){1}\s*(.*?)(\>|\/\>)`
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 返回filePath是否在nsPaths名称空间内
|
||||||
|
*
|
||||||
|
* inNamespace("a/b/c/xxx.js","a/b") == true
|
||||||
|
* inNamespace("a/c/c/xxx.js","a/b") == false
|
||||||
|
*
|
||||||
|
* @param {*} filePath 文件路径
|
||||||
|
* @param {*} nsPath 名称空间的路径
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function inNamespace(filePath,nsPath){
|
||||||
|
return !path.relative(nsPath,filePath).startsWith("..")
|
||||||
|
}
|
||||||
// 获取指定文件的名称空间
|
// 获取指定文件的名称空间
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -28,12 +42,12 @@ const DefaultHtmlAttrExtractor = String.raw`\<(?<tagName>\w+)(.*?)(?<i18nKey>{at
|
|||||||
*/
|
*/
|
||||||
function getFileNamespace(file,options){
|
function getFileNamespace(file,options){
|
||||||
const {output, namespaces } = options
|
const {output, namespaces } = options
|
||||||
const refPath = file.relative.toLowerCase() // 当前文件相对源文件夹的路径
|
const fileRefPath = file.relative.toLowerCase() // 当前文件相对源文件夹的路径
|
||||||
for(let [name,paths] of Object.entries(options.namespaces)){
|
for(let [name,paths] of Object.entries(options.namespaces)){
|
||||||
for(let path of paths){
|
for(let nsPath of paths){
|
||||||
if(typeof(path) === "string" && refPath.startsWith(path.toLowerCase())){
|
if(typeof(nsPath) === "string" && inNamespace(fileRefPath,nsPath)){
|
||||||
return name
|
return name
|
||||||
}else if(typeof path === "function" && path(file)===true){
|
}else if(typeof nsPath === "function" && nsPath(file)===true){
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,12 +284,6 @@ function normalizeLanguageOptions(options){
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Supported languages\t: {}",options.languages.map(item=>`${item.title}(${item.name})`))
|
|
||||||
logger.log("Default language\t: {}",options.defaultLanguage)
|
|
||||||
logger.log("Active language\t\t: {}",options.activeLanguage)
|
|
||||||
logger.log("Language namespaces\t: {}",Object.keys(options.namespaces).join(","))
|
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,10 +339,17 @@ function updateLanguageFile(fromTexts,toLangFile,options){
|
|||||||
})
|
})
|
||||||
fs.writeFileSync(toLangFile,JSON.stringify(targetTexts,null,4))
|
fs.writeFileSync(toLangFile,JSON.stringify(targetTexts,null,4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(options={}){
|
module.exports = function(options={}){
|
||||||
options = normalizeLanguageOptions(options)
|
options = normalizeLanguageOptions(options)
|
||||||
let {debug,output:{ path:outputPath, updateMode },languages} = options
|
let {debug,output:{ path:outputPath, updateMode },languages} = options
|
||||||
|
|
||||||
|
logger.log("Supported languages\t: {}",options.languages.map(item=>`${item.title}(${item.name})`))
|
||||||
|
logger.log("Default language\t: {}",options.defaultLanguage)
|
||||||
|
logger.log("Active language\t\t: {}",options.activeLanguage)
|
||||||
|
logger.log("Language namespaces\t: {}",Object.keys(options.namespaces).join(","))
|
||||||
|
|
||||||
// 保存提交提取的文本 = {}
|
// 保存提交提取的文本 = {}
|
||||||
let results = {}
|
let results = {}
|
||||||
let fileCount=0 // 文件总数
|
let fileCount=0 // 文件总数
|
||||||
@ -400,3 +415,7 @@ module.exports = function(options={}){
|
|||||||
callback()
|
callback()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports.getTranslateTexts = getTranslateTexts
|
||||||
|
module.exports.normalizeLanguageOptions = normalizeLanguageOptions
|
174
src/index.js
174
src/index.js
@ -1,9 +1,9 @@
|
|||||||
import deepMerge from "deepmerge"
|
import deepMerge from "deepmerge"
|
||||||
|
|
||||||
|
|
||||||
// 用来提取字符里面的插值变量参数
|
// 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter }
|
||||||
// let varRegexp = /\{\s*(?<var>\w*\.?\w*)\s*\}/g
|
// let varRegexp = /\{\s*(?<var>\w*\.?\w*)\s*\}/g
|
||||||
let varRegexp = /\{\s*((?<varname>\w+)?(\s*\|\s*(?<formatter>\w*))?)?\s*\}/g
|
let varWidthPipeRegexp = /\{\s*(?<varname>\w+)?(?<formatters>(\s*\|\s*\w+\s*)*)\s*\}/g
|
||||||
// 插值变量字符串替换正则
|
// 插值变量字符串替换正则
|
||||||
//let varReplaceRegexp =String.raw`\{\s*(?<var>{name}\.?\w*)\s*\}`
|
//let varReplaceRegexp =String.raw`\{\s*(?<var>{name}\.?\w*)\s*\}`
|
||||||
|
|
||||||
@ -44,17 +44,17 @@ function getDataTypeName(v){
|
|||||||
/**
|
/**
|
||||||
* 提取字符串中的插值变量
|
* 提取字符串中的插值变量
|
||||||
* @param {*} str
|
* @param {*} str
|
||||||
* @returns {Array} 变量名称列表
|
* @returns {Array} [[变量名称,[]],[变量名称,[formatter,formatter,...]],...]
|
||||||
*/
|
*/
|
||||||
function getInterpolatedVars(str){
|
function getInterpolatedVars(str){
|
||||||
let result = []
|
let result = []
|
||||||
let match
|
let match
|
||||||
while ((match = varRegexp.exec(str)) !== null) {
|
while ((match = varWidthPipeRegexp.exec(str)) !== null) {
|
||||||
if (match.index === varRegexp.lastIndex) {
|
if (match.index === varWidthPipeRegexp.lastIndex) {
|
||||||
varRegexp.lastIndex++;
|
varWidthPipeRegexp.lastIndex++;
|
||||||
}
|
}
|
||||||
if(match.groups.varname) {
|
if(match.groups.varname) {
|
||||||
result.push(match.groups.formatter ? match.groups.varname+"|"+match.groups.formatter : match.groups.varname)
|
result.push(match.groups.formatters ? [match.groups.varname,match.groups.formatters.trim().split("|")] : [match.groups.varname,[]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -93,15 +93,32 @@ function transformVarValue(value){
|
|||||||
- 普通位置参数替换
|
- 普通位置参数替换
|
||||||
replaceInterpolateVars("this is {a}+{b}",1,2) --> this is 1+2
|
replaceInterpolateVars("this is {a}+{b}",1,2) --> this is 1+2
|
||||||
-
|
-
|
||||||
|
this == scope == { formatters: {}, ... }
|
||||||
* @param {*} template
|
* @param {*} template
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function replaceInterpolateVars(template,...args) {
|
function replaceInterpolateVars(template,...args) {
|
||||||
|
const scope = this
|
||||||
let result=template
|
let result=template
|
||||||
if(!hasInterpolation(template)) return
|
if(!hasInterpolation(template)) return template
|
||||||
|
|
||||||
if(args.length===1 && typeof(args[0]) === "object" && !Array.isArray(args[0])){ // 变量插值
|
if(args.length===1 && typeof(args[0]) === "object" && !Array.isArray(args[0])){ // 变量插值
|
||||||
for(let name in args[0]){
|
// 取得里面的插值变量
|
||||||
|
let varNames = getInterpolatedVars(template)
|
||||||
|
let varValues = args[0]
|
||||||
|
if(varNames.length===0) return template
|
||||||
|
for(let [name,formatters] of varNames){
|
||||||
|
// 计算出变量值
|
||||||
|
let value = (name in varValues) ? varValues[name] : ''
|
||||||
|
if(formatters.length >0 ){
|
||||||
|
formatters.reduce((v,formatter)=>{
|
||||||
|
if(formatter in scope.formatters){
|
||||||
|
return scope.formatters[formatter](v)
|
||||||
|
}else{
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
},"")
|
||||||
|
}
|
||||||
// 如果变量中包括|管道符,则需要进行转换以适配更宽松的写法,比如data|time能匹配"data |time","data | time"等
|
// 如果变量中包括|管道符,则需要进行转换以适配更宽松的写法,比如data|time能匹配"data |time","data | time"等
|
||||||
let nameRegexp = name.includes("|") ? name.split("|").join("\\s*\\|\\s*") : name
|
let nameRegexp = name.includes("|") ? name.split("|").join("\\s*\\|\\s*") : name
|
||||||
result=result.replaceAll(new RegExp(varReplaceRegexp.replaceAll("{varname}",nameRegexp),"g"),transformVarValue(args[0][name]))
|
result=result.replaceAll(new RegExp(varReplaceRegexp.replaceAll("{varname}",nameRegexp),"g"),transformVarValue(args[0][name]))
|
||||||
@ -109,7 +126,7 @@ function replaceInterpolateVars(template,...args) {
|
|||||||
}else{ // 位置插值
|
}else{ // 位置插值
|
||||||
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args
|
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args
|
||||||
let i=0
|
let i=0
|
||||||
for(let match of result.match(varRegexp) || []){
|
for(let match of result.match(varWidthPipeRegexp) || []){
|
||||||
if(i<params.length){
|
if(i<params.length){
|
||||||
let param = transformVarValue(params[i])
|
let param = transformVarValue(params[i])
|
||||||
result=result.replace(match,param)
|
result=result.replace(match,param)
|
||||||
@ -130,6 +147,9 @@ export const defaultLanguageSettings = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMessageId(content){
|
||||||
|
return parseInt(content)>0
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 翻译函数
|
* 翻译函数
|
||||||
*
|
*
|
||||||
@ -140,41 +160,52 @@ export const defaultLanguageSettings = {
|
|||||||
* translate("I am {p}",{p:"man",namespace:""})
|
* translate("I am {p}",{p:"man",namespace:""})
|
||||||
* translate("I am {p}",{p:"man",namespace:""})
|
* translate("I am {p}",{p:"man",namespace:""})
|
||||||
* translate("total {count} items", {count:1}) //复数形式
|
* translate("total {count} items", {count:1}) //复数形式
|
||||||
* translate({count:1,plurals:'count'}) // 复数形式
|
|
||||||
* translate("total {} {} {} items",a,b,c) // 位置变量插值
|
* translate("total {} {} {} items",a,b,c) // 位置变量插值
|
||||||
|
*
|
||||||
|
* this===scope 当前绑定的scope
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function translate() {
|
export function translate(message) {
|
||||||
let content = arguments[0],options={}
|
const scope = this
|
||||||
|
const activeLanguage = scope.settings.activeLanguage
|
||||||
|
let vars={} // 插值变量
|
||||||
|
let pluralVars=[]
|
||||||
try{
|
try{
|
||||||
if(arguments.length === 2 && typeof(arguments[1])=='object'){
|
if(arguments.length === 2 && typeof(arguments[1])=='object'){
|
||||||
Object.assign(options,arguments[1])
|
Object.assign(vars,arguments[1])
|
||||||
|
Object.entries(vars).forEach(([key,value])=>{
|
||||||
|
if(typeof(value)==="function"){
|
||||||
|
vars[key] =try{value()}catch(e){value}
|
||||||
|
}
|
||||||
|
if(key.startsWith("$")) pluralVars.push(key) // 复数变量
|
||||||
|
})
|
||||||
}else if(arguments.length >= 2){
|
}else if(arguments.length >= 2){
|
||||||
options=[...arguments].splice(1)
|
vars = [...arguments].splice(1).map(arg=>typeof(arg)==="function" ? arg() : arg)
|
||||||
}
|
}
|
||||||
// 默认语言是中文,不需要查询加载,只需要做插值变换即可
|
|
||||||
if(this.language === this.defaultLanguage){
|
// 默认语言,不需要查询加载,只需要做插值变换即可
|
||||||
return this._replaceVars(content,options)
|
if(activeLanguage === scope.defaultLanguage){
|
||||||
}else{
|
// 当源文件运用了babel插件后会将原始文本内容转换为msgId
|
||||||
let result = this.messages[this.language][content]
|
if(isMessageId(message)){
|
||||||
if(content in this.messages[this.language]){
|
message = scope.default[message] || message
|
||||||
// 复数形式,需要通过plurals来指定内容中包括的复数插值
|
|
||||||
if(Array.isArray(result)){
|
|
||||||
let plurals = options.plurals
|
|
||||||
if(typeof(plurals) == 'string' && (plurals in options)){
|
|
||||||
return options[plurals]>1 ? result[1].params(options) : result[0].params(options)
|
|
||||||
}else{
|
|
||||||
return this._replaceVars(result[0],options)
|
|
||||||
}
|
}
|
||||||
|
return replaceInterpolateVars.call(scope,message,vars)
|
||||||
}else{
|
}else{
|
||||||
return this._replaceVars(result,options);
|
// 1. 获取翻译后的文本内容
|
||||||
}
|
// 如果没有启用babel插件时,需要先将文本内容转换为msgId
|
||||||
}else{
|
let msgId = isMessageId(message) ? message : scope.idMap[message]
|
||||||
return this._replaceVars(result,options)
|
message = scope.messages[msgId] || msgId
|
||||||
|
|
||||||
|
// 处理复数
|
||||||
|
if(Array.isArray(message)){
|
||||||
|
|
||||||
|
}else{ // 普通
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
return content
|
return message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +244,7 @@ export class I18n{
|
|||||||
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){
|
on(callback){
|
||||||
this.callbacks.push(callback)
|
this.callbacks.push(callback)
|
||||||
}
|
}
|
||||||
@ -228,44 +259,76 @@ export class I18n{
|
|||||||
offAll(){
|
offAll(){
|
||||||
this.callbacks=[]
|
this.callbacks=[]
|
||||||
}
|
}
|
||||||
_triggerCallback(){
|
/**
|
||||||
this.callbacks.forEach(callback=>{
|
* 切换语言时触发语言切换事件回调
|
||||||
if(typeof(callback)=="function"){
|
*/
|
||||||
callback.call(this,this.language)
|
async _triggerChangeEvents(newLanguage){
|
||||||
|
try{
|
||||||
|
await this._updateScopes(newLanguage)
|
||||||
|
await (Promise.allSettled || Promise.all)(this.callbacks.map(async cb=>await cb(newLanguage)))
|
||||||
|
}catch(e){
|
||||||
|
console.warn("Error while executing language change events",e.message)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 切换语言
|
* 切换语言
|
||||||
*/
|
*/
|
||||||
async change(value){
|
async change(value){
|
||||||
if(value in this.languages){
|
if(value in this.languages){
|
||||||
//
|
await this._triggerChangeEvents(value)
|
||||||
let asyncMsgLoaders = this._scopes.map(scope=>scope[value]).filter(loader=>{
|
|
||||||
loader!=null}
|
|
||||||
)
|
|
||||||
// 加载所有
|
|
||||||
if(asyncMsgLoaders.length>0){
|
|
||||||
await Promise.all(asyncMsgLoaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
this._settings.activeLanguage = value
|
this._settings.activeLanguage = value
|
||||||
this._triggerCallback()
|
|
||||||
}else{
|
}else{
|
||||||
throw new Error("Not supported language:"+value)
|
throw new Error("Not supported language:"+value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 当切换语言时调用此方法来加载更新语言包
|
||||||
|
* @param {*} newLanguage
|
||||||
|
*/
|
||||||
|
async _updateScopes(newLanguage){
|
||||||
|
// 并发执行所有作用域语言包的加载
|
||||||
|
try{
|
||||||
|
await (Promise.allSettled || Promise.all)(this._scopes.map(scope=>{
|
||||||
|
return async ()=>{
|
||||||
|
// 默认语言,所有均默认语言均采用静态加载方式,只需要简单的替换即可
|
||||||
|
if(newLanguage === scope.defaultLanguage){
|
||||||
|
scope.messages = scope.default
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 异步加载语言文件
|
||||||
|
const loader = scope.loaders[newLanguage]
|
||||||
|
if(typeof(loader) === "function"){
|
||||||
|
try{
|
||||||
|
scope.messages = await loader()
|
||||||
|
}catch(e){
|
||||||
|
console.warn(`Error loading language ${newLanguage} : ${e.message}`)
|
||||||
|
scope.messages = defaultMessages // 出错时回退到默认语言
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
scope.messages = defaultMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}catch(e){
|
||||||
|
console.warn("Error while refreshing scope:",e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* 注册一个新的作用域
|
* 注册一个新的作用域
|
||||||
*
|
*
|
||||||
* 每一个库均对应一个作用域,每个作用域可以有多个语言包,且对应一个翻译函数
|
* 每一个库均对应一个作用域,每个作用域可以有多个语言包,且对应一个翻译函数
|
||||||
* scope={
|
* scope={
|
||||||
* "<默认语言>":{
|
* defaultLanguage:"cn",
|
||||||
* "<id>":"<text>",
|
default: defaultMessages, // 转换文本信息
|
||||||
* },
|
messages : defaultMessages, // 当前语言的消息
|
||||||
* "<语言1>":()=>import(),
|
idMap:messageIds,
|
||||||
* "<语言2>":()=>import(),
|
formatters:{
|
||||||
|
...formatters,
|
||||||
|
...i18nSettings.formatters || {}
|
||||||
|
},
|
||||||
|
loaders:{}, // 异步加载语言文件的函数列表
|
||||||
|
settings:{} // 引用全局VoerkaI18n实例的配置
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* 除了默认语言外,其他语言采用动态加载的方式
|
* 除了默认语言外,其他语言采用动态加载的方式
|
||||||
@ -273,6 +336,7 @@ export class I18n{
|
|||||||
* @param {*} scope
|
* @param {*} scope
|
||||||
*/
|
*/
|
||||||
register(scope){
|
register(scope){
|
||||||
|
scope.i18nSettings = this.settings
|
||||||
this._scopes.push(scope)
|
this._scopes.push(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
src/utils.js
16
src/utils.js
@ -125,15 +125,15 @@ function replaceInterpolateVars(template,...args) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const str = "I am {name}, I am {age} years old. you are {name},Now is {date},time={date | time}?"
|
// const str = "I am {name}, I am {age} years old. you are {name},Now is {date},time={date | time}?"
|
||||||
|
|
||||||
console.log("vars=",getInterpolatedVars(str).join())
|
// console.log("vars=",getInterpolatedVars(str).join())
|
||||||
|
|
||||||
console.log(replaceInterpolateVars(str,{name:"tom",age:18,date:new Date(),"date|time":new Date().getTime()}))
|
// console.log(replaceInterpolateVars(str,{name:"tom",age:18,date:new Date(),"date|time":new Date().getTime()}))
|
||||||
console.log(replaceInterpolateVars(str,"tom",18,"jack"))
|
// console.log(replaceInterpolateVars(str,"tom",18,"jack"))
|
||||||
console.log(replaceInterpolateVars(str,["tom",18,"jack",1,2]))
|
// console.log(replaceInterpolateVars(str,["tom",18,"jack",1,2]))
|
||||||
console.log(replaceInterpolateVars(str,"tom",18,()=>"bob"))
|
// console.log(replaceInterpolateVars(str,"tom",18,()=>"bob"))
|
||||||
console.log(replaceInterpolateVars(str,"tom",[1,2],{a:1},1,2))
|
// console.log(replaceInterpolateVars(str,"tom",[1,2],{a:1},1,2))
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getMessageId,
|
getMessageId,
|
||||||
@ -141,3 +141,5 @@ module.exports = {
|
|||||||
getInterpolatedVars,
|
getInterpolatedVars,
|
||||||
replaceInterpolateVars
|
replaceInterpolateVars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
0
test/compile.test.js
Normal file
0
test/compile.test.js
Normal file
48
test/extract.test.js
Normal file
48
test/extract.test.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const extract = require("../src/extract.plugin");
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const path = require('path');
|
||||||
|
const Vinyl = require('vinyl');
|
||||||
|
const { getTranslateTexts, normalizeLanguageOptions } = require("../src/extract.plugin");
|
||||||
|
|
||||||
|
|
||||||
|
const languages = [{name:'en',title:"英文"},{name:'cn',title:"中文",default:true},{name:'de',title:"德语"},{name:'jp',title:"日本語"}]
|
||||||
|
|
||||||
|
test("扫描提取翻译文本",(done)=>{
|
||||||
|
const file = new Vinyl({cwd: '/',base: '/test/',path: '/test/file.js',contents: Buffer.from("")});
|
||||||
|
const texts = getTranslateTexts(`t("a")\nt('b')\nt("c",1)\nt("d","a" )`,file,normalizeLanguageOptions({
|
||||||
|
languages
|
||||||
|
})).default // 默认名称空间
|
||||||
|
expect(Object.keys(texts).join()).toBe("a,b,c,d")
|
||||||
|
Object.entries(texts).forEach(([text,langs])=>{
|
||||||
|
if(!text.startsWith("$")){
|
||||||
|
expect(langs["en"]).toEqual(text)
|
||||||
|
expect(langs["jp"]).toEqual(text)
|
||||||
|
expect(langs["de"]).toEqual(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test("启用名称空间后扫描提取翻译文本",(done)=>{
|
||||||
|
const file = new Vinyl({base: '/test/',path: '/test/a/b/file.js',contents: Buffer.from("")});
|
||||||
|
const texts = getTranslateTexts(`t("a")\nt('b')\nt("c",1)\nt("d","a" )`,file,normalizeLanguageOptions({
|
||||||
|
languages,
|
||||||
|
namespaces:{
|
||||||
|
"core":"a/b", // 名称空间
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
expect("core" in texts).toBeFalsy()
|
||||||
|
expect("core" in texts).toBeTruthy()
|
||||||
|
expect(Object.keys(texts.core).join()).toBe("a,b,c,d")
|
||||||
|
Object.entries(texts.core).forEach(([text,langs])=>{
|
||||||
|
if(!text.startsWith("$")){
|
||||||
|
expect(langs["en"]).toEqual(text)
|
||||||
|
expect(langs["jp"]).toEqual(text)
|
||||||
|
expect(langs["de"]).toEqual(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
18
test/translate.test.js
Normal file
18
test/translate.test.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import { translate } from "../src/index.js"
|
||||||
|
|
||||||
|
|
||||||
|
test("默认语言翻译",done=>{
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("启用位置插值变量翻译",done=>{
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test("启用字典插值变量翻译",done=>{
|
||||||
|
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user