@zwa73/utils 1.0.136 → 1.0.138
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/UtilFileTools.d.ts +2 -2
- package/dist/UtilFileTools.js +1 -1
- package/dist/UtilFunctions.d.ts +3 -3
- package/dist/UtilFunctions.js +2 -3
- package/dist/UtilI18n.d.ts +105 -0
- package/dist/UtilI18n.js +196 -0
- package/dist/UtilInterfaces.d.ts +1 -1
- package/package.json +2 -1
- package/src/UtilFileTools.ts +2 -2
- package/src/UtilFunctions.ts +5 -5
- package/src/UtilI18n.ts +258 -0
- package/src/UtilInterfaces.ts +1 -1
package/dist/UtilFileTools.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ type LoadJsonFileOpt<T> = Partial<{
|
|
|
34
34
|
}>;
|
|
35
35
|
/**json文件写入选项 */
|
|
36
36
|
type WriteJsonFileOpt = Partial<{
|
|
37
|
-
/**使用紧凑风格 */
|
|
37
|
+
/**使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误 */
|
|
38
38
|
compress: boolean;
|
|
39
39
|
/**不自动修改扩展名为json */
|
|
40
40
|
forceExt: boolean;
|
|
@@ -118,7 +118,7 @@ export declare namespace UtilFT {
|
|
|
118
118
|
* @param filePath - 文件路径
|
|
119
119
|
* @param token - 所要写入的JToken
|
|
120
120
|
* @param opt - 可选参数
|
|
121
|
-
* @param opt.compress - 使用紧凑风格
|
|
121
|
+
* @param opt.compress - 使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误
|
|
122
122
|
* @param opt.forceExt - 不自动修改扩展名为json
|
|
123
123
|
*/
|
|
124
124
|
function writeJSONFile(filePath: string, token: JToken, opt?: WriteJsonFileOpt): Promise<void>;
|
package/dist/UtilFileTools.js
CHANGED
|
@@ -181,7 +181,7 @@ var UtilFT;
|
|
|
181
181
|
* @param filePath - 文件路径
|
|
182
182
|
* @param token - 所要写入的JToken
|
|
183
183
|
* @param opt - 可选参数
|
|
184
|
-
* @param opt.compress - 使用紧凑风格
|
|
184
|
+
* @param opt.compress - 使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误
|
|
185
185
|
* @param opt.forceExt - 不自动修改扩展名为json
|
|
186
186
|
*/
|
|
187
187
|
async function writeJSONFile(filePath, token, opt) {
|
package/dist/UtilFunctions.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ type ExecOpt = Partial<{
|
|
|
10
10
|
}>;
|
|
11
11
|
/**序列化选项 */
|
|
12
12
|
type StringifyOpt = Partial<{
|
|
13
|
-
/**使用紧凑风格 */
|
|
13
|
+
/**使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误 */
|
|
14
14
|
compress: boolean;
|
|
15
15
|
/**插入的空格 数字为空格数量 默认为制表符\t */
|
|
16
16
|
space: string | number | null | undefined;
|
|
@@ -132,7 +132,7 @@ export declare class UtilFunc {
|
|
|
132
132
|
* @param token - 待转换的Token
|
|
133
133
|
* @param opt - 可选参数
|
|
134
134
|
* @param opt.space - 插入的空格 数字为空格数量 默认为制表符\t
|
|
135
|
-
* @param opt.compress - 使用紧凑风格
|
|
135
|
+
* @param opt.compress - 使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误
|
|
136
136
|
* @returns 转换完成的字符串
|
|
137
137
|
*/
|
|
138
138
|
static stringifyJToken(token: JToken | IJData, opt?: StringifyOpt): string;
|
|
@@ -291,6 +291,6 @@ export declare class UtilFunc {
|
|
|
291
291
|
* @param expiry - 缓存的有效期 毫秒 默认为Infinity, 表示缓存永不过期。
|
|
292
292
|
* @returns 返回一个新的函数,这个函数在调用时会尝试从缓存中获取结果,如果缓存不存在或已过期,就会调用原函数并缓存其结果。
|
|
293
293
|
*/
|
|
294
|
-
static memoize<T extends (...args: JToken[]) => any>(fn: T, expiry?: number): T;
|
|
294
|
+
static memoize<T extends (...args: UnionToIntersection<JToken>[]) => any>(fn: T, expiry?: number): T;
|
|
295
295
|
}
|
|
296
296
|
export {};
|
package/dist/UtilFunctions.js
CHANGED
|
@@ -379,7 +379,7 @@ class UtilFunc {
|
|
|
379
379
|
* @param token - 待转换的Token
|
|
380
380
|
* @param opt - 可选参数
|
|
381
381
|
* @param opt.space - 插入的空格 数字为空格数量 默认为制表符\t
|
|
382
|
-
* @param opt.compress - 使用紧凑风格
|
|
382
|
+
* @param opt.compress - 使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误
|
|
383
383
|
* @returns 转换完成的字符串
|
|
384
384
|
*/
|
|
385
385
|
static stringifyJToken(token, opt) {
|
|
@@ -401,8 +401,7 @@ class UtilFunc {
|
|
|
401
401
|
return value;
|
|
402
402
|
};
|
|
403
403
|
return JSON.stringify(token, compressReplacer, space)
|
|
404
|
-
.replace(/"@@@(.*?)@@@"/g,
|
|
405
|
-
.replace(/\\([\\"])/g, '$1');
|
|
404
|
+
.replace(/"@@@(.*?)@@@"/g, (match, p1) => p1.replace(/\\([\\"])/g, '$1'));
|
|
406
405
|
}
|
|
407
406
|
/**代办表 用于队列处理等待 */
|
|
408
407
|
static pendingMap = {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { PRecord } from "./UtilInterfaces";
|
|
2
|
+
/**语言:地区 表 */
|
|
3
|
+
declare const I18nFlagTable: {
|
|
4
|
+
readonly zh: readonly ["CN", "TW"];
|
|
5
|
+
readonly en: readonly ["US"];
|
|
6
|
+
};
|
|
7
|
+
/**语言标签 */
|
|
8
|
+
export type LangFlag = {
|
|
9
|
+
[P in keyof typeof I18nFlagTable]: `${P}-${typeof I18nFlagTable[P][number]}`;
|
|
10
|
+
}[keyof typeof I18nFlagTable] | keyof typeof I18nFlagTable;
|
|
11
|
+
/**源语言文本数据 */
|
|
12
|
+
export type I18nOrigTextData = {
|
|
13
|
+
/**原文 */
|
|
14
|
+
original: string;
|
|
15
|
+
/**出现位置 */
|
|
16
|
+
position?: string;
|
|
17
|
+
/**特殊说明 */
|
|
18
|
+
desc?: string;
|
|
19
|
+
/**无效的 默认false */
|
|
20
|
+
invalid?: boolean;
|
|
21
|
+
/**特殊标记 */
|
|
22
|
+
mark?: string;
|
|
23
|
+
/**抓取时间戳 */
|
|
24
|
+
scan_time: string;
|
|
25
|
+
/**抓取方式 */
|
|
26
|
+
source: 'Regex' | 'Ast' | 'Runtime' | 'Manual';
|
|
27
|
+
};
|
|
28
|
+
/**I18n源语言数据 */
|
|
29
|
+
export type I18nOrigTable = {
|
|
30
|
+
/**基础语言 */
|
|
31
|
+
base_lang: LangFlag;
|
|
32
|
+
texts: I18nOrigTextData[];
|
|
33
|
+
};
|
|
34
|
+
/**单语言I18N数据 */
|
|
35
|
+
export type I18nSingleTable = {
|
|
36
|
+
/**基础语言 */
|
|
37
|
+
base_lang: LangFlag;
|
|
38
|
+
/**目标语言 */
|
|
39
|
+
target_lang: LangFlag;
|
|
40
|
+
/**更改时间戳 */
|
|
41
|
+
modify_time: string;
|
|
42
|
+
/**文本翻译表 */
|
|
43
|
+
translate_table: Record<string, string | null>;
|
|
44
|
+
};
|
|
45
|
+
/**处理过的i18n文本数据 */
|
|
46
|
+
export type I18nTextData = I18nOrigTextData & {
|
|
47
|
+
lang_table: PRecord<LangFlag, string>;
|
|
48
|
+
};
|
|
49
|
+
/**处理过的I18n数据 */
|
|
50
|
+
export type I18nTable = Omit<I18nOrigTable, 'texts'> & {
|
|
51
|
+
text_table: Record<string, I18nTextData>;
|
|
52
|
+
};
|
|
53
|
+
/**运行时I18n工具
|
|
54
|
+
* 需先调用 init初始化
|
|
55
|
+
*/
|
|
56
|
+
export declare class SI18n {
|
|
57
|
+
private static _table?;
|
|
58
|
+
private static _lang?;
|
|
59
|
+
private static _dataDir?;
|
|
60
|
+
private static _vaildLang?;
|
|
61
|
+
/**解析i18n索引 */
|
|
62
|
+
static parseI18nKey(i18nKey: string): {
|
|
63
|
+
base: string;
|
|
64
|
+
mark: string | undefined;
|
|
65
|
+
};
|
|
66
|
+
/**格式化文本与mark为i18n索引key */
|
|
67
|
+
static formatI18nKey(str: string, mark?: string): string;
|
|
68
|
+
/**特殊标记
|
|
69
|
+
* 若添加在文本末尾则不会作为原文文本显示, 仅供索引
|
|
70
|
+
* @param mk - 特殊标记文本
|
|
71
|
+
*/
|
|
72
|
+
static mark(mk: string): `%%%${string}%%%`;
|
|
73
|
+
/**初始化函数,加载国际化数据,设置语言,并在进程退出前保存数据。
|
|
74
|
+
* @param i18nDataDir - 国际化数据的路径。
|
|
75
|
+
* @param lang - 要设置的语言。
|
|
76
|
+
*/
|
|
77
|
+
static init(i18nDataDir: string, lang: LangFlag): void;
|
|
78
|
+
/**根据提供的字符串,返回对应的国际化字符串
|
|
79
|
+
* @param text - 字符串数组。
|
|
80
|
+
* @returns 对应的国际化字符串。
|
|
81
|
+
*/
|
|
82
|
+
static t(text: string): string;
|
|
83
|
+
/**根据提供的模板字符串和值,返回对应的国际化字符串。
|
|
84
|
+
* @param strings - 模板字符串数组。
|
|
85
|
+
* @param values - 值数组。
|
|
86
|
+
* @returns 对应的国际化字符串。
|
|
87
|
+
*/
|
|
88
|
+
static t(strings: TemplateStringsArray, ...values: any[]): string;
|
|
89
|
+
/**根据提供的键和语言,返回对应的国际化字符串。
|
|
90
|
+
* 如果没有找到对应的字符串,将会抛出错误。
|
|
91
|
+
* @param key - 要查找的键。
|
|
92
|
+
* @param lang - 要查找的语言。
|
|
93
|
+
* @returns 对应的国际化字符串。
|
|
94
|
+
*/
|
|
95
|
+
static getI18n(key: string, lang: LangFlag): string;
|
|
96
|
+
/**尝试获取指定语言的翻译,如果没有找到,将回滚到其他可用的语言。
|
|
97
|
+
* @param lang - 要查找的语言。
|
|
98
|
+
* @param dat - 包含翻译的数据对象。
|
|
99
|
+
* @returns 找到的翻译,如果没有找到,将返回原始字符串。
|
|
100
|
+
*/
|
|
101
|
+
static tryAndRollback(lang: LangFlag, dat: I18nTable['text_table'][string]): string;
|
|
102
|
+
/**保存数据 */
|
|
103
|
+
static saveTable(): Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
export {};
|
package/dist/UtilI18n.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SI18n = void 0;
|
|
7
|
+
const UtilFileTools_1 = require("./UtilFileTools");
|
|
8
|
+
const UtilFunctions_1 = require("./UtilFunctions");
|
|
9
|
+
const UtilLogger_1 = require("./UtilLogger");
|
|
10
|
+
const pathe_1 = __importDefault(require("pathe"));
|
|
11
|
+
/**语言:地区 表 */
|
|
12
|
+
const I18nFlagTable = {
|
|
13
|
+
"zh": ["CN", "TW"],
|
|
14
|
+
"en": ["US"]
|
|
15
|
+
};
|
|
16
|
+
/**语言标签列表 */
|
|
17
|
+
const LangFlagList = Object.entries(I18nFlagTable)
|
|
18
|
+
.map(([k, v]) => `${k}-${v}`)
|
|
19
|
+
.concat(Object.keys(I18nFlagTable));
|
|
20
|
+
const MarkRegex = /%%%([^%]+)%%%$/;
|
|
21
|
+
const BaseFile = 'base_lang.json';
|
|
22
|
+
const LangDir = 'lang';
|
|
23
|
+
/**运行时I18n工具
|
|
24
|
+
* 需先调用 init初始化
|
|
25
|
+
*/
|
|
26
|
+
class SI18n {
|
|
27
|
+
static _table;
|
|
28
|
+
static _lang;
|
|
29
|
+
static _dataDir;
|
|
30
|
+
static _vaildLang;
|
|
31
|
+
/**解析i18n索引 */
|
|
32
|
+
static parseI18nKey(i18nKey) {
|
|
33
|
+
const match = i18nKey.match(/([\s\S]+?)(%%%[^%]+%%%)?$/);
|
|
34
|
+
const base = match[1];
|
|
35
|
+
const mark = match[2] == '' || match[2] == undefined
|
|
36
|
+
? undefined
|
|
37
|
+
: match[2].match(/^%%%([^%]+)%%%$/)[1];
|
|
38
|
+
return { base, mark };
|
|
39
|
+
}
|
|
40
|
+
/**格式化文本与mark为i18n索引key */
|
|
41
|
+
static formatI18nKey(str, mark) {
|
|
42
|
+
return str + (mark == undefined ? '' : SI18n.mark(mark));
|
|
43
|
+
}
|
|
44
|
+
/**特殊标记
|
|
45
|
+
* 若添加在文本末尾则不会作为原文文本显示, 仅供索引
|
|
46
|
+
* @param mk - 特殊标记文本
|
|
47
|
+
*/
|
|
48
|
+
static mark(mk) {
|
|
49
|
+
return `%%%${mk}%%%`;
|
|
50
|
+
}
|
|
51
|
+
/**初始化函数,加载国际化数据,设置语言,并在进程退出前保存数据。
|
|
52
|
+
* @param i18nDataDir - 国际化数据的路径。
|
|
53
|
+
* @param lang - 要设置的语言。
|
|
54
|
+
*/
|
|
55
|
+
static init(i18nDataDir, lang) {
|
|
56
|
+
const date = new Date().toISOString();
|
|
57
|
+
const mergePath = pathe_1.default.join(i18nDataDir, BaseFile);
|
|
58
|
+
const table = UtilFileTools_1.UtilFT.loadJSONFileSync(mergePath, { default: {
|
|
59
|
+
base_lang: 'zh-CN',
|
|
60
|
+
texts: [],
|
|
61
|
+
} });
|
|
62
|
+
const { texts, ...rest } = table;
|
|
63
|
+
SI18n._table = {
|
|
64
|
+
...rest,
|
|
65
|
+
text_table: texts.reduce((acc, cur) => ({ ...acc, [SI18n.formatI18nKey(cur.original, cur.mark)]: { ...cur, lang_table: {} } }), {}),
|
|
66
|
+
};
|
|
67
|
+
SI18n._vaildLang = [];
|
|
68
|
+
//覆盖入单语言文件
|
|
69
|
+
const singleDir = pathe_1.default.join(i18nDataDir, LangDir);
|
|
70
|
+
UtilFileTools_1.UtilFT.fileSearchGlobSync(singleDir, `**/${lang}*.json`)
|
|
71
|
+
.map(p => UtilFileTools_1.UtilFT.loadJSONFileSync(p))
|
|
72
|
+
.forEach(t => {
|
|
73
|
+
SI18n._vaildLang?.push(t.target_lang);
|
|
74
|
+
return Object.entries(t.translate_table)
|
|
75
|
+
.forEach(([k, v]) => {
|
|
76
|
+
if (v == null)
|
|
77
|
+
return;
|
|
78
|
+
const ttable = SI18n._table.text_table;
|
|
79
|
+
ttable[k] ??= {
|
|
80
|
+
original: k,
|
|
81
|
+
scan_time: date,
|
|
82
|
+
source: 'Runtime',
|
|
83
|
+
lang_table: {}
|
|
84
|
+
};
|
|
85
|
+
const ltable = ttable[k].lang_table;
|
|
86
|
+
ltable[t.target_lang] ??= v;
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
if (!SI18n._vaildLang.includes(lang))
|
|
90
|
+
SI18n._vaildLang.push(lang);
|
|
91
|
+
SI18n._lang = lang;
|
|
92
|
+
SI18n._dataDir = i18nDataDir;
|
|
93
|
+
}
|
|
94
|
+
static t(strings, ...values) {
|
|
95
|
+
if (typeof strings == 'string') {
|
|
96
|
+
const p = SI18n.parseI18nKey(strings);
|
|
97
|
+
return SI18n.getI18n(p.base.replace(/%/g, '\\%') + (p.mark == undefined ? '' : SI18n.mark(p.mark)), SI18n._lang);
|
|
98
|
+
}
|
|
99
|
+
let i18nKey = ``;
|
|
100
|
+
const vakyeKeys = values.map((v, i) => `%${i}`);
|
|
101
|
+
for (let i = 0; i < values.length; i++)
|
|
102
|
+
i18nKey += strings[i].replace(/%/g, '\\%') + vakyeKeys[i];
|
|
103
|
+
i18nKey += strings[strings.length - 1];
|
|
104
|
+
//检查是否为mark
|
|
105
|
+
if (MarkRegex.test(String(values[values.length - 1])) && strings[strings.length - 1] == '')
|
|
106
|
+
i18nKey = i18nKey.replace(vakyeKeys[vakyeKeys.length - 1], '') + String(values[values.length - 1]);
|
|
107
|
+
//console.log(1,i18nKey)
|
|
108
|
+
let trans = SI18n.getI18n(i18nKey, SI18n._lang);
|
|
109
|
+
//console.log(2,trans)
|
|
110
|
+
vakyeKeys.forEach((k, i) => trans = trans.replace(new RegExp(`((?<!\\\\)${k})`), String(values[i])));
|
|
111
|
+
//console.log(3,trans)
|
|
112
|
+
return trans.replace(/\\%/g, '%');
|
|
113
|
+
}
|
|
114
|
+
/**根据提供的键和语言,返回对应的国际化字符串。
|
|
115
|
+
* 如果没有找到对应的字符串,将会抛出错误。
|
|
116
|
+
* @param key - 要查找的键。
|
|
117
|
+
* @param lang - 要查找的语言。
|
|
118
|
+
* @returns 对应的国际化字符串。
|
|
119
|
+
*/
|
|
120
|
+
static getI18n(key, lang) {
|
|
121
|
+
if (SI18n._table == undefined)
|
|
122
|
+
UtilFunctions_1.UtilFunc.throwError('SI18n 未初始化');
|
|
123
|
+
let obj = SI18n._table.text_table[key];
|
|
124
|
+
if (obj == undefined) {
|
|
125
|
+
UtilLogger_1.SLogger.warn(`SI18n 未在 ${SI18n._dataDir} 找到 ${key} 对应的数据, 已自动添加`);
|
|
126
|
+
const p = SI18n.parseI18nKey(key);
|
|
127
|
+
SI18n._table.text_table[key] = {
|
|
128
|
+
original: p.base,
|
|
129
|
+
scan_time: new Date().toISOString(),
|
|
130
|
+
lang_table: {},
|
|
131
|
+
mark: p.mark,
|
|
132
|
+
source: 'Runtime'
|
|
133
|
+
};
|
|
134
|
+
obj = SI18n._table.text_table[key];
|
|
135
|
+
}
|
|
136
|
+
if (obj.invalid === true)
|
|
137
|
+
return obj.original;
|
|
138
|
+
return SI18n.tryAndRollback(lang, obj);
|
|
139
|
+
}
|
|
140
|
+
/**尝试获取指定语言的翻译,如果没有找到,将回滚到其他可用的语言。
|
|
141
|
+
* @param lang - 要查找的语言。
|
|
142
|
+
* @param dat - 包含翻译的数据对象。
|
|
143
|
+
* @returns 找到的翻译,如果没有找到,将返回原始字符串。
|
|
144
|
+
*/
|
|
145
|
+
static tryAndRollback(lang, dat) {
|
|
146
|
+
const langTable = {
|
|
147
|
+
[SI18n._table.base_lang]: dat.original,
|
|
148
|
+
...dat.lang_table
|
|
149
|
+
};
|
|
150
|
+
if (langTable[lang] != undefined)
|
|
151
|
+
return langTable[lang];
|
|
152
|
+
const l1 = lang.match(/^([^-]+)/)[1];
|
|
153
|
+
const rb = Object.entries(langTable).find(([k, v]) => k.includes(l1));
|
|
154
|
+
if (rb)
|
|
155
|
+
return rb[1];
|
|
156
|
+
return dat.original;
|
|
157
|
+
}
|
|
158
|
+
/**保存数据 */
|
|
159
|
+
static async saveTable() {
|
|
160
|
+
const { text_table, ...rest } = SI18n._table;
|
|
161
|
+
const date = new Date().toISOString();
|
|
162
|
+
//console.log(text_table)
|
|
163
|
+
//保存原始文本数据
|
|
164
|
+
const tb = {
|
|
165
|
+
...rest,
|
|
166
|
+
texts: Object.values(text_table).map((d) => {
|
|
167
|
+
const { lang_table, ...rest } = d;
|
|
168
|
+
return rest;
|
|
169
|
+
})
|
|
170
|
+
};
|
|
171
|
+
const mergePath = pathe_1.default.join(SI18n._dataDir, BaseFile);
|
|
172
|
+
await UtilFileTools_1.UtilFT.writeJSONFile(mergePath, tb);
|
|
173
|
+
//保存所有语言数据
|
|
174
|
+
const tbMap = {};
|
|
175
|
+
Object.values(text_table).forEach(d => {
|
|
176
|
+
if (d.invalid === true)
|
|
177
|
+
return;
|
|
178
|
+
SI18n._vaildLang?.forEach((vl) => {
|
|
179
|
+
const lf = vl;
|
|
180
|
+
tbMap[lf] ??= {
|
|
181
|
+
base_lang: rest.base_lang,
|
|
182
|
+
modify_time: date,
|
|
183
|
+
target_lang: lf,
|
|
184
|
+
translate_table: {}
|
|
185
|
+
};
|
|
186
|
+
const v = d.lang_table[lf];
|
|
187
|
+
tbMap[lf].translate_table[SI18n.formatI18nKey(d.original, d.mark)] = v ?? null;
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
await Promise.all(Object.values(tbMap).map(async (t) => {
|
|
191
|
+
const singlePath = pathe_1.default.join(SI18n._dataDir, LangDir, t.target_lang);
|
|
192
|
+
return await UtilFileTools_1.UtilFT.writeJSONFile(singlePath, t);
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.SI18n = SI18n;
|
package/dist/UtilInterfaces.d.ts
CHANGED
|
@@ -199,7 +199,7 @@ export type SchemaString = `${string}SchemaString`;
|
|
|
199
199
|
/**需要初始化 */
|
|
200
200
|
export type NeedInit = {
|
|
201
201
|
/**完成初始化的标记 */
|
|
202
|
-
inited: Promise<
|
|
202
|
+
inited: Promise<any>;
|
|
203
203
|
};
|
|
204
204
|
/**可选的record */
|
|
205
205
|
export type PRecord<K extends Keyable, V> = Partial<Record<K, V>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zwa73/utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.138",
|
|
4
4
|
"description": "my utils",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@zwa73/dev-utils": "^1.0.50",
|
|
37
37
|
"jest": "^29.7.0",
|
|
38
38
|
"ts-jest": "^29.1.2",
|
|
39
|
+
"ts-morph": "^23.0.0",
|
|
39
40
|
"tsc-alias": "^1.8.8",
|
|
40
41
|
"typescript": "^5.3.3"
|
|
41
42
|
}
|
package/src/UtilFileTools.ts
CHANGED
|
@@ -46,7 +46,7 @@ type LoadJsonFileOpt<T> = Partial<{
|
|
|
46
46
|
|
|
47
47
|
/**json文件写入选项 */
|
|
48
48
|
type WriteJsonFileOpt = Partial<{
|
|
49
|
-
/**使用紧凑风格 */
|
|
49
|
+
/**使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误 */
|
|
50
50
|
compress:boolean;
|
|
51
51
|
/**不自动修改扩展名为json */
|
|
52
52
|
forceExt:boolean;
|
|
@@ -234,7 +234,7 @@ export async function loadJSONFile<T extends JToken>(filePath: string,opt?:LoadJ
|
|
|
234
234
|
* @param filePath - 文件路径
|
|
235
235
|
* @param token - 所要写入的JToken
|
|
236
236
|
* @param opt - 可选参数
|
|
237
|
-
* @param opt.compress - 使用紧凑风格
|
|
237
|
+
* @param opt.compress - 使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误
|
|
238
238
|
* @param opt.forceExt - 不自动修改扩展名为json
|
|
239
239
|
*/
|
|
240
240
|
export async function writeJSONFile(
|
package/src/UtilFunctions.ts
CHANGED
|
@@ -20,7 +20,7 @@ type ExecOpt = Partial<{
|
|
|
20
20
|
|
|
21
21
|
/**序列化选项 */
|
|
22
22
|
type StringifyOpt = Partial<{
|
|
23
|
-
/**使用紧凑风格 */
|
|
23
|
+
/**使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误 */
|
|
24
24
|
compress:boolean;
|
|
25
25
|
/**插入的空格 数字为空格数量 默认为制表符\t */
|
|
26
26
|
space:string|number|null|undefined;
|
|
@@ -441,7 +441,7 @@ static mapEntries<T extends AnyRecord>
|
|
|
441
441
|
* @param token - 待转换的Token
|
|
442
442
|
* @param opt - 可选参数
|
|
443
443
|
* @param opt.space - 插入的空格 数字为空格数量 默认为制表符\t
|
|
444
|
-
* @param opt.compress - 使用紧凑风格
|
|
444
|
+
* @param opt.compress - 使用紧凑风格 将会用/@@@.+@@@/作为特殊标记, 原始文本内若出现相同格式将会产生错误
|
|
445
445
|
* @returns 转换完成的字符串
|
|
446
446
|
*/
|
|
447
447
|
static stringifyJToken(token:JToken|IJData,opt?:StringifyOpt){
|
|
@@ -464,8 +464,8 @@ static stringifyJToken(token:JToken|IJData,opt?:StringifyOpt){
|
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
return JSON.stringify(token,compressReplacer,space)
|
|
467
|
-
.replace(/"@@@(.*?)@@@"/g
|
|
468
|
-
|
|
467
|
+
.replace(/"@@@(.*?)@@@"/g, (match, p1) =>
|
|
468
|
+
p1.replace(/\\([\\"])/g, '$1'));
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
/**代办表 用于队列处理等待 */
|
|
@@ -822,7 +822,7 @@ static ivk<T extends ()=>any>(fn:T): ReturnType<T> {
|
|
|
822
822
|
* @param expiry - 缓存的有效期 毫秒 默认为Infinity, 表示缓存永不过期。
|
|
823
823
|
* @returns 返回一个新的函数,这个函数在调用时会尝试从缓存中获取结果,如果缓存不存在或已过期,就会调用原函数并缓存其结果。
|
|
824
824
|
*/
|
|
825
|
-
static memoize<T extends (...args:JToken[])=>any> (fn: T, expiry = Infinity): T {
|
|
825
|
+
static memoize<T extends (...args:UnionToIntersection<JToken>[])=>any> (fn: T, expiry = Infinity): T {
|
|
826
826
|
const cache = UtilFunc.cachePool.get(fn) ?? UtilFunc.ivk(()=>{
|
|
827
827
|
const c = new Map();
|
|
828
828
|
UtilFunc.cachePool.set(fn, c);
|
package/src/UtilI18n.ts
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { UtilFT } from "./UtilFileTools";
|
|
2
|
+
import { UtilFunc } from "./UtilFunctions";
|
|
3
|
+
import { PRecord } from "./UtilInterfaces";
|
|
4
|
+
import { SLogger } from "./UtilLogger";
|
|
5
|
+
import path from 'pathe';
|
|
6
|
+
|
|
7
|
+
/**语言:地区 表 */
|
|
8
|
+
const I18nFlagTable = {
|
|
9
|
+
"zh":["CN","TW"],
|
|
10
|
+
"en":["US"]
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
/**语言标签 */
|
|
14
|
+
export type LangFlag = {
|
|
15
|
+
[P in keyof typeof I18nFlagTable]: `${P}-${typeof I18nFlagTable[P][number]}`
|
|
16
|
+
}[keyof typeof I18nFlagTable]|keyof typeof I18nFlagTable;
|
|
17
|
+
|
|
18
|
+
/**语言标签列表 */
|
|
19
|
+
const LangFlagList = Object.entries(I18nFlagTable)
|
|
20
|
+
.map(([k,v])=>`${k}-${v}` as LangFlag)
|
|
21
|
+
.concat(Object.keys(I18nFlagTable) as LangFlag[]);
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**源语言文本数据 */
|
|
26
|
+
export type I18nOrigTextData = {
|
|
27
|
+
/**原文 */
|
|
28
|
+
original:string;
|
|
29
|
+
/**出现位置 */
|
|
30
|
+
position?:string;
|
|
31
|
+
/**特殊说明 */
|
|
32
|
+
desc?:string;
|
|
33
|
+
/**无效的 默认false */
|
|
34
|
+
invalid?:boolean;
|
|
35
|
+
/**特殊标记 */
|
|
36
|
+
mark?:string;
|
|
37
|
+
/**抓取时间戳 */
|
|
38
|
+
scan_time:string;
|
|
39
|
+
/**抓取方式 */
|
|
40
|
+
source:'Regex'|'Ast'|'Runtime'|'Manual'
|
|
41
|
+
};
|
|
42
|
+
/**I18n源语言数据 */
|
|
43
|
+
export type I18nOrigTable = {
|
|
44
|
+
/**基础语言 */
|
|
45
|
+
base_lang:LangFlag;
|
|
46
|
+
texts:I18nOrigTextData[]
|
|
47
|
+
}
|
|
48
|
+
/**单语言I18N数据 */
|
|
49
|
+
export type I18nSingleTable = {
|
|
50
|
+
/**基础语言 */
|
|
51
|
+
base_lang:LangFlag;
|
|
52
|
+
/**目标语言 */
|
|
53
|
+
target_lang:LangFlag;
|
|
54
|
+
/**更改时间戳 */
|
|
55
|
+
modify_time:string;
|
|
56
|
+
/**文本翻译表 */
|
|
57
|
+
translate_table:Record<string,string|null>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**处理过的i18n文本数据 */
|
|
62
|
+
export type I18nTextData = I18nOrigTextData&{lang_table:PRecord<LangFlag,string>};
|
|
63
|
+
/**处理过的I18n数据 */
|
|
64
|
+
export type I18nTable = Omit<I18nOrigTable,'texts'>&{
|
|
65
|
+
text_table:Record<string,I18nTextData>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const MarkRegex = /%%%([^%]+)%%%$/;
|
|
70
|
+
const BaseFile = 'base_lang.json';
|
|
71
|
+
const LangDir = 'lang';
|
|
72
|
+
/**运行时I18n工具
|
|
73
|
+
* 需先调用 init初始化
|
|
74
|
+
*/
|
|
75
|
+
export class SI18n {
|
|
76
|
+
private static _table?:I18nTable;
|
|
77
|
+
private static _lang?:LangFlag;
|
|
78
|
+
private static _dataDir?:string;
|
|
79
|
+
private static _vaildLang?:LangFlag[];
|
|
80
|
+
/**解析i18n索引 */
|
|
81
|
+
static parseI18nKey(i18nKey:string){
|
|
82
|
+
const match = i18nKey.match(/([\s\S]+?)(%%%[^%]+%%%)?$/)!;
|
|
83
|
+
const base = match[1];
|
|
84
|
+
const mark = match[2]==''||match[2]==undefined
|
|
85
|
+
? undefined
|
|
86
|
+
: match[2].match(/^%%%([^%]+)%%%$/)![1];
|
|
87
|
+
return {base,mark};
|
|
88
|
+
}
|
|
89
|
+
/**格式化文本与mark为i18n索引key */
|
|
90
|
+
static formatI18nKey(str:string,mark?:string){
|
|
91
|
+
return str + (mark==undefined ? '' : SI18n.mark(mark));
|
|
92
|
+
}
|
|
93
|
+
/**特殊标记
|
|
94
|
+
* 若添加在文本末尾则不会作为原文文本显示, 仅供索引
|
|
95
|
+
* @param mk - 特殊标记文本
|
|
96
|
+
*/
|
|
97
|
+
static mark(mk:string):`%%%${string}%%%`{
|
|
98
|
+
return `%%%${mk}%%%`
|
|
99
|
+
}
|
|
100
|
+
/**初始化函数,加载国际化数据,设置语言,并在进程退出前保存数据。
|
|
101
|
+
* @param i18nDataDir - 国际化数据的路径。
|
|
102
|
+
* @param lang - 要设置的语言。
|
|
103
|
+
*/
|
|
104
|
+
static init(i18nDataDir:string,lang:LangFlag){
|
|
105
|
+
const date = new Date().toISOString();
|
|
106
|
+
const mergePath = path.join(i18nDataDir,BaseFile);
|
|
107
|
+
const table = UtilFT.loadJSONFileSync<I18nOrigTable>(mergePath,{default:{
|
|
108
|
+
base_lang:'zh-CN',
|
|
109
|
+
texts:[],
|
|
110
|
+
}});
|
|
111
|
+
const {texts,...rest} = table;
|
|
112
|
+
SI18n._table={
|
|
113
|
+
...rest,
|
|
114
|
+
text_table:texts.reduce((acc, cur) =>
|
|
115
|
+
({...acc,[SI18n.formatI18nKey(cur.original,cur.mark)]:{...cur,lang_table:{}}}),
|
|
116
|
+
{} as Record<string,I18nTextData>),
|
|
117
|
+
}
|
|
118
|
+
SI18n._vaildLang=[];
|
|
119
|
+
//覆盖入单语言文件
|
|
120
|
+
const singleDir = path.join(i18nDataDir,LangDir);
|
|
121
|
+
UtilFT.fileSearchGlobSync(singleDir,`**/${lang}*.json`)
|
|
122
|
+
.map(p=>UtilFT.loadJSONFileSync(p) as I18nSingleTable)
|
|
123
|
+
.forEach(t=>{
|
|
124
|
+
SI18n._vaildLang?.push(t.target_lang);
|
|
125
|
+
return Object.entries(t.translate_table)
|
|
126
|
+
.forEach(([k,v])=>{
|
|
127
|
+
if(v==null) return;
|
|
128
|
+
const ttable = SI18n._table!.text_table;
|
|
129
|
+
ttable[k] ??= {
|
|
130
|
+
original:k,
|
|
131
|
+
scan_time:date,
|
|
132
|
+
source:'Runtime',
|
|
133
|
+
lang_table:{}
|
|
134
|
+
}
|
|
135
|
+
const ltable = ttable[k].lang_table;
|
|
136
|
+
ltable[t.target_lang] ??= v;
|
|
137
|
+
})
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if(!SI18n._vaildLang.includes(lang))
|
|
142
|
+
SI18n._vaildLang.push(lang);
|
|
143
|
+
SI18n._lang = lang;
|
|
144
|
+
SI18n._dataDir = i18nDataDir;
|
|
145
|
+
}
|
|
146
|
+
/**根据提供的字符串,返回对应的国际化字符串
|
|
147
|
+
* @param text - 字符串数组。
|
|
148
|
+
* @returns 对应的国际化字符串。
|
|
149
|
+
*/
|
|
150
|
+
static t(text:string):string;
|
|
151
|
+
/**根据提供的模板字符串和值,返回对应的国际化字符串。
|
|
152
|
+
* @param strings - 模板字符串数组。
|
|
153
|
+
* @param values - 值数组。
|
|
154
|
+
* @returns 对应的国际化字符串。
|
|
155
|
+
*/
|
|
156
|
+
static t(strings:TemplateStringsArray,...values:any[]):string;
|
|
157
|
+
static t(strings:string|TemplateStringsArray,...values:any[]){
|
|
158
|
+
if(typeof strings =='string'){
|
|
159
|
+
const p = SI18n.parseI18nKey(strings);
|
|
160
|
+
return SI18n.getI18n(p.base.replace(/%/g,'\\%')+(p.mark==undefined?'':SI18n.mark(p.mark)), SI18n._lang!);
|
|
161
|
+
}
|
|
162
|
+
let i18nKey = ``;
|
|
163
|
+
const vakyeKeys = values.map((v,i)=>`%${i}`);
|
|
164
|
+
for (let i = 0; i < values.length; i++)
|
|
165
|
+
i18nKey += strings[i].replace(/%/g,'\\%') + vakyeKeys[i];
|
|
166
|
+
i18nKey += strings[strings.length - 1];
|
|
167
|
+
|
|
168
|
+
//检查是否为mark
|
|
169
|
+
if(MarkRegex.test(String(values[values.length-1])) && strings[strings.length - 1]=='')
|
|
170
|
+
i18nKey = i18nKey.replace(vakyeKeys[vakyeKeys.length - 1],'') + String(values[values.length-1]);
|
|
171
|
+
//console.log(1,i18nKey)
|
|
172
|
+
let trans = SI18n.getI18n(i18nKey, SI18n._lang!);
|
|
173
|
+
//console.log(2,trans)
|
|
174
|
+
vakyeKeys.forEach((k,i)=>trans = trans.replace(new RegExp(
|
|
175
|
+
`((?<!\\\\)${k})`
|
|
176
|
+
),String(values[i])));
|
|
177
|
+
//console.log(3,trans)
|
|
178
|
+
return trans.replace(/\\%/g,'%');
|
|
179
|
+
}
|
|
180
|
+
/**根据提供的键和语言,返回对应的国际化字符串。
|
|
181
|
+
* 如果没有找到对应的字符串,将会抛出错误。
|
|
182
|
+
* @param key - 要查找的键。
|
|
183
|
+
* @param lang - 要查找的语言。
|
|
184
|
+
* @returns 对应的国际化字符串。
|
|
185
|
+
*/
|
|
186
|
+
static getI18n(key:string,lang:LangFlag){
|
|
187
|
+
if(SI18n._table==undefined) UtilFunc.throwError('SI18n 未初始化');
|
|
188
|
+
let obj = SI18n._table.text_table[key];
|
|
189
|
+
if(obj==undefined){
|
|
190
|
+
SLogger.warn(`SI18n 未在 ${SI18n._dataDir} 找到 ${key} 对应的数据, 已自动添加`);
|
|
191
|
+
const p = SI18n.parseI18nKey(key);
|
|
192
|
+
SI18n._table.text_table[key] = {
|
|
193
|
+
original:p.base,
|
|
194
|
+
scan_time:new Date().toISOString(),
|
|
195
|
+
lang_table:{},
|
|
196
|
+
mark:p.mark,
|
|
197
|
+
source:'Runtime'
|
|
198
|
+
}
|
|
199
|
+
obj = SI18n._table.text_table[key];
|
|
200
|
+
}
|
|
201
|
+
if(obj.invalid===true) return obj.original;
|
|
202
|
+
return SI18n.tryAndRollback(lang,obj);
|
|
203
|
+
}
|
|
204
|
+
/**尝试获取指定语言的翻译,如果没有找到,将回滚到其他可用的语言。
|
|
205
|
+
* @param lang - 要查找的语言。
|
|
206
|
+
* @param dat - 包含翻译的数据对象。
|
|
207
|
+
* @returns 找到的翻译,如果没有找到,将返回原始字符串。
|
|
208
|
+
*/
|
|
209
|
+
static tryAndRollback(lang:LangFlag,dat:I18nTable['text_table'][string]){
|
|
210
|
+
const langTable:PRecord<LangFlag,string> = {
|
|
211
|
+
[SI18n._table!.base_lang]:dat.original,
|
|
212
|
+
...dat.lang_table
|
|
213
|
+
};
|
|
214
|
+
if(langTable[lang]!=undefined) return langTable[lang]!;
|
|
215
|
+
const l1 = lang.match(/^([^-]+)/)![1];
|
|
216
|
+
const rb = Object.entries(langTable).find(([k,v])=>k.includes(l1));
|
|
217
|
+
if(rb) return rb[1];
|
|
218
|
+
return dat.original;
|
|
219
|
+
}
|
|
220
|
+
/**保存数据 */
|
|
221
|
+
static async saveTable(){
|
|
222
|
+
const {text_table,...rest} = SI18n._table!;
|
|
223
|
+
const date = new Date().toISOString();
|
|
224
|
+
//console.log(text_table)
|
|
225
|
+
|
|
226
|
+
//保存原始文本数据
|
|
227
|
+
const tb:I18nOrigTable = {
|
|
228
|
+
...rest,
|
|
229
|
+
texts:Object.values(text_table).map((d)=>{
|
|
230
|
+
const {lang_table,...rest} = d;
|
|
231
|
+
return rest;
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
const mergePath = path.join(SI18n._dataDir!,BaseFile);
|
|
235
|
+
await UtilFT.writeJSONFile(mergePath,tb);
|
|
236
|
+
|
|
237
|
+
//保存所有语言数据
|
|
238
|
+
const tbMap:PRecord<LangFlag,I18nSingleTable> = {};
|
|
239
|
+
Object.values(text_table).forEach(d=>{
|
|
240
|
+
if(d.invalid===true) return;
|
|
241
|
+
SI18n._vaildLang?.forEach((vl)=>{
|
|
242
|
+
const lf = vl as LangFlag;
|
|
243
|
+
tbMap[lf] ??={
|
|
244
|
+
base_lang:rest.base_lang,
|
|
245
|
+
modify_time:date,
|
|
246
|
+
target_lang:lf,
|
|
247
|
+
translate_table:{}
|
|
248
|
+
}
|
|
249
|
+
const v = d.lang_table[lf];
|
|
250
|
+
tbMap[lf]!.translate_table[SI18n.formatI18nKey(d.original,d.mark)] = v ?? null;
|
|
251
|
+
})
|
|
252
|
+
});
|
|
253
|
+
await Promise.all(Object.values(tbMap).map(async (t)=>{
|
|
254
|
+
const singlePath = path.join(SI18n._dataDir!,LangDir,t.target_lang);
|
|
255
|
+
return await UtilFT.writeJSONFile(singlePath,t);
|
|
256
|
+
}))
|
|
257
|
+
}
|
|
258
|
+
}
|