mm_tpl 2.7.1 → 2.7.2
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/README.md +870 -37
- package/README_EN.md +881 -38
- package/doc//347/233/256/345/275/225/347/273/223/346/236/204.md +165 -0
- package/doc//350/256/276/350/256/241/346/200/273/347/273/223.md +381 -0
- package/index.js +46 -471
- package/lib/asset.js +129 -0
- package/lib/css.js +34 -0
- package/lib/engine.js +1256 -0
- package/lib/hook.js +302 -0
- package/lib/js.js +34 -0
- package/lib/lang.js +184 -0
- package/lib/memory.js +103 -0
- package/lib/sqlite.js +176 -0
- package/lib/template.js +125 -0
- package/lib/tpl_rule.js +288 -0
- package/lib/util.js +79 -0
- package/package.json +14 -2
- package/hook.js +0 -302
- package/tpl_rule.js +0 -287
package/lib/hook.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
|
|
2
|
+
const {
|
|
3
|
+
types
|
|
4
|
+
} = require("util");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 钩子管理类
|
|
8
|
+
* @class
|
|
9
|
+
*/
|
|
10
|
+
class Hook {
|
|
11
|
+
/**
|
|
12
|
+
* 创建钩子实例
|
|
13
|
+
*/
|
|
14
|
+
constructor() {
|
|
15
|
+
this._func = {};
|
|
16
|
+
this._action = {};
|
|
17
|
+
this._filter = {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 排序钩子列表
|
|
23
|
+
* @param {Array} list - 钩子列表
|
|
24
|
+
* @returns {Array} 排序后的列表
|
|
25
|
+
*/
|
|
26
|
+
Hook.prototype.sort = function (list) {
|
|
27
|
+
return list.sort((o1, o2) => {
|
|
28
|
+
return o2.sort - o1.sort
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取函数别名
|
|
34
|
+
* @param {Function} func - 钩子函数
|
|
35
|
+
* @param {string} alias - 别名
|
|
36
|
+
* @returns {string} 函数别名
|
|
37
|
+
*/
|
|
38
|
+
Hook.prototype._getName = function (func, alias) {
|
|
39
|
+
if (!alias) {
|
|
40
|
+
var str = func.toString();
|
|
41
|
+
var name = str.between('function ', "(").trim();
|
|
42
|
+
if (name) {
|
|
43
|
+
alias = name;
|
|
44
|
+
} else {
|
|
45
|
+
alias = (new Date()).toString().md5().substring(0, 8);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return alias;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 添加函数钩子
|
|
53
|
+
* @param {string} name - 钩子名称
|
|
54
|
+
* @param {Function} func - 钩子函数
|
|
55
|
+
* @param {number} sort - 执行顺序
|
|
56
|
+
* @param {string} alias - 函数别名
|
|
57
|
+
* @returns {string} 函数别名
|
|
58
|
+
*/
|
|
59
|
+
Hook.prototype.addFunc = function (name, func, sort = 100, alias = "") {
|
|
60
|
+
alias = this._getName(func, alias);
|
|
61
|
+
var obj = {
|
|
62
|
+
func,
|
|
63
|
+
sort,
|
|
64
|
+
alias
|
|
65
|
+
}
|
|
66
|
+
var arr = this._func[name] || [];
|
|
67
|
+
arr.push(obj);
|
|
68
|
+
this._func[name] = this.sort(arr);
|
|
69
|
+
return alias;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 添加动作钩子
|
|
74
|
+
* @param {string} name - 钩子名称
|
|
75
|
+
* @param {Function} func - 钩子函数
|
|
76
|
+
* @param {number} sort - 执行顺序
|
|
77
|
+
* @param {string} alias - 函数别名
|
|
78
|
+
* @returns {string} 函数别名
|
|
79
|
+
*/
|
|
80
|
+
Hook.prototype.addAction = function (name, func, sort = 100, alias = "") {
|
|
81
|
+
alias = this._getName(func, alias);
|
|
82
|
+
var obj = {
|
|
83
|
+
func,
|
|
84
|
+
sort,
|
|
85
|
+
alias
|
|
86
|
+
}
|
|
87
|
+
var arr = this._action[name] || [];
|
|
88
|
+
arr.push(obj);
|
|
89
|
+
this._action[name] = this.sort(arr);
|
|
90
|
+
return alias;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 添加过滤钩子
|
|
95
|
+
* @param {string} name - 钩子名称
|
|
96
|
+
* @param {Function} func - 钩子函数
|
|
97
|
+
* @param {number} sort - 执行顺序
|
|
98
|
+
* @param {string} alias - 函数别名
|
|
99
|
+
* @returns {string} 函数别名
|
|
100
|
+
*/
|
|
101
|
+
Hook.prototype.addFilter = function (name, func, sort = 100, alias = "") {
|
|
102
|
+
alias = this._getName(func, alias);
|
|
103
|
+
var obj = {
|
|
104
|
+
func,
|
|
105
|
+
sort,
|
|
106
|
+
alias
|
|
107
|
+
}
|
|
108
|
+
var arr = this._filter[name] || [];
|
|
109
|
+
arr.push(obj);
|
|
110
|
+
this._filter[name] = this.sort(arr);
|
|
111
|
+
return alias;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 删除函数钩子
|
|
116
|
+
* @param {String} name 钩子名称
|
|
117
|
+
* @param {String} alias 函数别名
|
|
118
|
+
*/
|
|
119
|
+
Hook.prototype.delFunc = function (name, alias) {
|
|
120
|
+
var arr = this._func[name];
|
|
121
|
+
if (arr) {
|
|
122
|
+
var list = [];
|
|
123
|
+
for (var i = 0; i < arr.length; i++) {
|
|
124
|
+
var item = arr[i];
|
|
125
|
+
if (item.alias !== alias) {
|
|
126
|
+
list.push(item);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this._func[name] = list;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 删除动作钩子
|
|
135
|
+
* @param {String} name 钩子名称
|
|
136
|
+
* @param {String} alias 函数别名
|
|
137
|
+
*/
|
|
138
|
+
Hook.prototype.delAction = function (name, alias) {
|
|
139
|
+
var arr = this._action[name];
|
|
140
|
+
if (arr) {
|
|
141
|
+
var list = [];
|
|
142
|
+
for (var i = 0; i < arr.length; i++) {
|
|
143
|
+
var item = arr[i];
|
|
144
|
+
if (item.alias !== alias) {
|
|
145
|
+
list.push(item);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this._action[name] = list;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 删除过滤钩子
|
|
154
|
+
* @param {String} name 钩子名称
|
|
155
|
+
* @param {String} alias 函数别名
|
|
156
|
+
*/
|
|
157
|
+
Hook.prototype.delFilter = function (name, alias = "") {
|
|
158
|
+
var arr = this._filter[name];
|
|
159
|
+
if (arr) {
|
|
160
|
+
var list = [];
|
|
161
|
+
for (var i = 0; i < arr.length; i++) {
|
|
162
|
+
var item = arr[i];
|
|
163
|
+
if (item.alias !== alias) {
|
|
164
|
+
list.push(item);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
this._filter[name] = list;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 执行函数钩子
|
|
173
|
+
* @param {string} name - 钩子名称
|
|
174
|
+
* @param {Object} param - 参数
|
|
175
|
+
* @param {Function} func - 钩子函数
|
|
176
|
+
*/
|
|
177
|
+
Hook.prototype.runFunc = async function (name, model, req, ...args) {
|
|
178
|
+
var ret;
|
|
179
|
+
var list = this._func[name];
|
|
180
|
+
if (list) {
|
|
181
|
+
for (var i = 0; i < list.length; i++) {
|
|
182
|
+
var item = list[i];
|
|
183
|
+
try {
|
|
184
|
+
var ret_new = item.func(model, req, ...args);
|
|
185
|
+
if (types.isPromise(ret_new)) {
|
|
186
|
+
ret_new = await ret_new;
|
|
187
|
+
if (ret_new) {
|
|
188
|
+
ret = ret_new;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (err) {
|
|
192
|
+
$.log.error(`[${this.constructor.name}] 执行函数钩子失败 ${item.alias}`, err);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return ret;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 执行动作钩子
|
|
201
|
+
* @param {string} name - 钩子名称
|
|
202
|
+
* @param {Object} model - 模型对象
|
|
203
|
+
* @param {...any} args - 附加参数
|
|
204
|
+
* @returns {Object} 执行结果
|
|
205
|
+
*/
|
|
206
|
+
Hook.prototype.runAction = function (name, model, ...args) {
|
|
207
|
+
var ret = '';
|
|
208
|
+
var list = this._action[name];
|
|
209
|
+
if (list) {
|
|
210
|
+
for (var i = 0; i < list.length; i++) {
|
|
211
|
+
var o = list[i];
|
|
212
|
+
try {
|
|
213
|
+
ret += o.func(model, ...args);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
$.log.error(`[${this.constructor.name}] 执行动作钩子失败 ${o.alias}`, err);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return ret;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 执行过滤钩子
|
|
224
|
+
* @param {string} name - 钩子名称
|
|
225
|
+
* @param {Object} model - 模型对象
|
|
226
|
+
* @param {Object} ret - 返回值
|
|
227
|
+
* @param {...any} args - 附加参数
|
|
228
|
+
* @returns {Object} 过滤后的参数
|
|
229
|
+
*/
|
|
230
|
+
Hook.prototype.runFilter = function (name, model, ret, ...args) {
|
|
231
|
+
var list = this._filter[name];
|
|
232
|
+
if (list) {
|
|
233
|
+
for (var i = 0; i < list.length; i++) {
|
|
234
|
+
var item = list[i];
|
|
235
|
+
try {
|
|
236
|
+
ret = item.func(model, ret, ...args) || ret;
|
|
237
|
+
} catch (err) {
|
|
238
|
+
$.log.error(`[${this.constructor.name}] 执行过滤钩子失败 ${item.alias}`, err);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return ret;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 删除钩子
|
|
247
|
+
* @param {string} name - 钩子名称
|
|
248
|
+
* @param {string} alias - 函数别名
|
|
249
|
+
*/
|
|
250
|
+
Hook.prototype.del = function (name, alias) {
|
|
251
|
+
var arr = this._func[name];
|
|
252
|
+
if (arr) {
|
|
253
|
+
var list = [];
|
|
254
|
+
for (var i = 0; i < arr.length; i++) {
|
|
255
|
+
var item = arr[i];
|
|
256
|
+
if (item.alias !== alias) {
|
|
257
|
+
list.push(item);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
this._func[name] = list;
|
|
261
|
+
}
|
|
262
|
+
arr = this._action[name];
|
|
263
|
+
if (arr) {
|
|
264
|
+
var list = [];
|
|
265
|
+
for (var i = 0; i < arr.length; i++) {
|
|
266
|
+
var item = arr[i];
|
|
267
|
+
if (item.alias !== alias) {
|
|
268
|
+
list.push(item);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
this._action[name] = list;
|
|
272
|
+
}
|
|
273
|
+
arr = this._filter[name];
|
|
274
|
+
if (arr) {
|
|
275
|
+
var list = [];
|
|
276
|
+
for (var i = 0; i < arr.length; i++) {
|
|
277
|
+
var item = arr[i];
|
|
278
|
+
if (item.alias !== alias) {
|
|
279
|
+
list.push(item);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
this._filter[name] = list;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 清空钩子
|
|
288
|
+
* @param {string} name - 钩子名称
|
|
289
|
+
*/
|
|
290
|
+
Hook.prototype.clear = function (name) {
|
|
291
|
+
if (name) {
|
|
292
|
+
delete this._func[name];
|
|
293
|
+
delete this._action[name];
|
|
294
|
+
delete this._filter[name];
|
|
295
|
+
} else {
|
|
296
|
+
this._func = {};
|
|
297
|
+
this._action = {};
|
|
298
|
+
this._filter = {};
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
module.exports = Hook;
|
package/lib/js.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const {
|
|
2
|
+
Asset
|
|
3
|
+
} = require('./asset.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 脚本文件加载器
|
|
7
|
+
* 脚本与样式同属「引用多个文件」类资源,但不按风格分层
|
|
8
|
+
* 风格是视觉变体,脚本不因颜色明暗而变,按主题分层即可,公共脚本放应用级目录
|
|
9
|
+
* @class
|
|
10
|
+
*/
|
|
11
|
+
class Js extends Asset {
|
|
12
|
+
/**
|
|
13
|
+
* 创建脚本文件加载器
|
|
14
|
+
* @param {Object} tpl - 所属模板实例
|
|
15
|
+
*/
|
|
16
|
+
constructor(tpl) {
|
|
17
|
+
super(tpl, '.js');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 获取脚本目录列表
|
|
23
|
+
* 优先级从高到低:自定义覆盖目录、当前主题、默认主题、应用级
|
|
24
|
+
* 返回顺序为优先级从高到低
|
|
25
|
+
* @param {string} theme - 主题名称,不传时取当前主题
|
|
26
|
+
* @returns {Array<string>} 脚本目录列表
|
|
27
|
+
*/
|
|
28
|
+
Js.prototype.getDirs = function (theme) {
|
|
29
|
+
return this.tpl.getJsDirs(theme);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
Js
|
|
34
|
+
};
|
package/lib/lang.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const {
|
|
2
|
+
Lang: BaseLang
|
|
3
|
+
} = require('mm_expand');
|
|
4
|
+
const {
|
|
5
|
+
TplUtil
|
|
6
|
+
} = require('./util.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 语言包加载器
|
|
10
|
+
* 语言包按「配置字典 + 多个目录文件」合并,高优先级来源覆盖低优先级来源
|
|
11
|
+
* @class
|
|
12
|
+
*/
|
|
13
|
+
class Lang {
|
|
14
|
+
/**
|
|
15
|
+
* 创建语言包加载器
|
|
16
|
+
* @param {Object} tpl - 所属模板实例
|
|
17
|
+
*/
|
|
18
|
+
constructor(tpl) {
|
|
19
|
+
/**
|
|
20
|
+
* 所属模板实例
|
|
21
|
+
*/
|
|
22
|
+
this.tpl = tpl;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 获取语言包目录列表
|
|
28
|
+
* 优先级从低到高:应用级目录、默认主题目录、当前主题目录
|
|
29
|
+
* @returns {Array<string>} 语言包目录列表
|
|
30
|
+
*/
|
|
31
|
+
Lang.prototype.getDirs = function () {
|
|
32
|
+
var tpl = this.tpl;
|
|
33
|
+
var cg = tpl.config;
|
|
34
|
+
if (!cg.lang_dir) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
var dirs = [cg.lang_dir.fullname(tpl.dir)];
|
|
38
|
+
if (!cg.theme_lang_dir) {
|
|
39
|
+
return dirs;
|
|
40
|
+
}
|
|
41
|
+
var themes = [];
|
|
42
|
+
if (cg.default_theme) {
|
|
43
|
+
themes.push(cg.default_theme);
|
|
44
|
+
}
|
|
45
|
+
if (tpl.current_theme && tpl.current_theme !== cg.default_theme) {
|
|
46
|
+
themes.push(tpl.current_theme);
|
|
47
|
+
}
|
|
48
|
+
for (var i = 0; i < themes.length; i++) {
|
|
49
|
+
dirs.push(cg.theme_lang_dir.fullname(tpl.getThemeDir(themes[i])));
|
|
50
|
+
}
|
|
51
|
+
return dirs;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 获取目录下的语言包文件
|
|
56
|
+
* @param {string} dir - 语言包目录
|
|
57
|
+
* @returns {Array<string>} 语言包文件路径列表,已排序
|
|
58
|
+
*/
|
|
59
|
+
Lang.prototype.getFiles = function (dir) {
|
|
60
|
+
if (!dir || !dir.hasDir()) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
var files = $.file.get(dir, '*.json');
|
|
64
|
+
var list = [];
|
|
65
|
+
for (var i = 0; i < files.length; i++) {
|
|
66
|
+
if (files[i].extname() === '.json') {
|
|
67
|
+
list.push(files[i]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 排序保证指纹稳定
|
|
71
|
+
return list.sort();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 获取语言包文件指纹
|
|
76
|
+
* 由文件路径与修改时间组成,文件新增、修改、删除都会使指纹变化
|
|
77
|
+
* @param {Array<string>} dirs - 语言包目录列表
|
|
78
|
+
* @returns {string} 指纹字符串
|
|
79
|
+
*/
|
|
80
|
+
Lang.prototype.getFingerprint = function (dirs) {
|
|
81
|
+
var list = [];
|
|
82
|
+
for (var i = 0; i < dirs.length; i++) {
|
|
83
|
+
var files = this.getFiles(dirs[i]);
|
|
84
|
+
for (var j = 0; j < files.length; j++) {
|
|
85
|
+
list.push(files[j] + ':' + TplUtil.getFileTime(files[j]));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return list.join('|');
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 加载语言包文件
|
|
93
|
+
* 文件名即语言代码,后加载的目录覆盖先加载的目录
|
|
94
|
+
* 结果按文件指纹缓存,文件未变化时直接复用
|
|
95
|
+
* @param {boolean} force - 是否跳过缓存强制重新加载
|
|
96
|
+
* @returns {Object} 语言包字典
|
|
97
|
+
*/
|
|
98
|
+
Lang.prototype.loadFiles = function (force) {
|
|
99
|
+
var dirs = this.getDirs();
|
|
100
|
+
var key = dirs.join('|');
|
|
101
|
+
var fingerprint = this.getFingerprint(dirs);
|
|
102
|
+
var cache = this.tpl.constructor.lang_cache;
|
|
103
|
+
var item = cache[key];
|
|
104
|
+
// 文件未变化时复用缓存,返回副本避免调用方改动污染缓存
|
|
105
|
+
if (!force && item && item.fingerprint === fingerprint) {
|
|
106
|
+
return $.copy(item.dict);
|
|
107
|
+
}
|
|
108
|
+
var dict = {};
|
|
109
|
+
for (var i = 0; i < dirs.length; i++) {
|
|
110
|
+
var files = this.getFiles(dirs[i]);
|
|
111
|
+
for (var j = 0; j < files.length; j++) {
|
|
112
|
+
var f = files[j];
|
|
113
|
+
var lang = f.basename().replace('.json', '');
|
|
114
|
+
var data = f.loadJson();
|
|
115
|
+
if (!lang || !data || typeof (data) !== 'object') {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!dict[lang]) {
|
|
119
|
+
dict[lang] = {};
|
|
120
|
+
}
|
|
121
|
+
// 高优先级目录的相同词条覆盖低优先级目录
|
|
122
|
+
$.push(dict[lang], data, true);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
cache[key] = {
|
|
126
|
+
fingerprint: fingerprint,
|
|
127
|
+
dict: $.copy(dict)
|
|
128
|
+
};
|
|
129
|
+
return dict;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 构建语言包字典
|
|
134
|
+
* 优先级从低到高:语言包文件、配置语言包
|
|
135
|
+
* @param {boolean} force - 是否跳过缓存强制重新加载语言包文件
|
|
136
|
+
* @returns {Object} 语言包字典
|
|
137
|
+
*/
|
|
138
|
+
Lang.prototype.buildDict = function (force) {
|
|
139
|
+
var dict = this.loadFiles(force);
|
|
140
|
+
var langs = this.tpl.config.langs;
|
|
141
|
+
if (langs && Object.keys(langs).length) {
|
|
142
|
+
$.push(dict, langs, true);
|
|
143
|
+
}
|
|
144
|
+
// 未配置任何语言包时,复用全局语言包字典
|
|
145
|
+
// 深拷贝后再用,避免实例上 lang.set() 写入时改到全局 $.lang.config,进而污染其他实例
|
|
146
|
+
if (!Object.keys(dict).length) {
|
|
147
|
+
dict = ($.lang && $.lang.config) ? $.copy($.lang.config) : {};
|
|
148
|
+
}
|
|
149
|
+
return Object.assign({}, dict);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 创建语言实例
|
|
154
|
+
* 语言包字典在实例间共享,仅当前语言各自独立
|
|
155
|
+
* @param {Object} config - 配置参数
|
|
156
|
+
* @param {boolean} force - 是否跳过语言包缓存强制重新加载
|
|
157
|
+
* @returns {BaseLang} 语言实例
|
|
158
|
+
*/
|
|
159
|
+
Lang.prototype.create = function (config, force) {
|
|
160
|
+
var dict = this.buildDict(force);
|
|
161
|
+
dict.default = config.default_lang || dict.default || 'zh_cn';
|
|
162
|
+
var lang = new BaseLang(dict);
|
|
163
|
+
this.registerPlural(lang, config.plural_rules);
|
|
164
|
+
return lang;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 登记复数规则
|
|
169
|
+
* BaseLang 内置规则仅覆盖 zh_cn、en,配置中的语言代码需显式登记才能按语言取复数形式
|
|
170
|
+
* @param {BaseLang} lang - 语言实例
|
|
171
|
+
* @param {Object} plural_rules - 复数规则,键为语言代码
|
|
172
|
+
*/
|
|
173
|
+
Lang.prototype.registerPlural = function (lang, plural_rules) {
|
|
174
|
+
var rules = plural_rules || {};
|
|
175
|
+
for (var code in rules) {
|
|
176
|
+
if (!lang.rules[code] && typeof (rules[code]) === 'function') {
|
|
177
|
+
lang.registerPlural(code, rules[code]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
Lang
|
|
184
|
+
};
|
package/lib/memory.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内存存储
|
|
3
|
+
*
|
|
4
|
+
* 模板引擎的默认用户偏好存储,在内存中维护 user_id -> { style, lang } 映射。
|
|
5
|
+
* 与 SQLite 存储(Sqlite)实现同一契约、方法一一对应,
|
|
6
|
+
* 通过 new Tpl({ user_store: new Sqlite('tpl.db') }) 注入即可替换默认存储。
|
|
7
|
+
*
|
|
8
|
+
* 标识一律按字符串入表,故数字 1001 与字符串 '1001' 视为同一键。
|
|
9
|
+
* 进程重启后数据丢失。
|
|
10
|
+
*
|
|
11
|
+
* 存储契约(store):
|
|
12
|
+
* getUser(user_id) -> Object|null
|
|
13
|
+
* setUser(user_id, prefs) -> Object
|
|
14
|
+
* delUser(user_id) -> Boolean
|
|
15
|
+
* hasUser(user_id) -> Boolean
|
|
16
|
+
* listUsers() -> Array<String>
|
|
17
|
+
* clear() -> Memory
|
|
18
|
+
* @class
|
|
19
|
+
*/
|
|
20
|
+
class Memory {
|
|
21
|
+
/**
|
|
22
|
+
* 构造函数
|
|
23
|
+
*/
|
|
24
|
+
constructor() {
|
|
25
|
+
/**
|
|
26
|
+
* 用户偏好映射,user_id -> { style, lang }
|
|
27
|
+
*/
|
|
28
|
+
this._users = new Map();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 写入用户偏好
|
|
34
|
+
* 同一用户重复写入时按字段合并,只更新本次传入的字段
|
|
35
|
+
* @param {String|Number} user_id - 用户标识
|
|
36
|
+
* @param {Object} prefs - 用户偏好,形如 { style, lang }
|
|
37
|
+
* @returns {Object} 保存后的偏好副本
|
|
38
|
+
*/
|
|
39
|
+
Memory.prototype.setUser = function (user_id, prefs) {
|
|
40
|
+
var key = this._key(user_id);
|
|
41
|
+
var record = this._users.get(key) || {};
|
|
42
|
+
var data = Object.assign({}, record, prefs);
|
|
43
|
+
this._users.set(key, data);
|
|
44
|
+
return Object.assign({}, data);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 读取用户偏好
|
|
49
|
+
* @param {String|Number} user_id - 用户标识
|
|
50
|
+
* @returns {Object|null} 用户偏好副本,未登记时返回 null
|
|
51
|
+
*/
|
|
52
|
+
Memory.prototype.getUser = function (user_id) {
|
|
53
|
+
var record = this._users.get(this._key(user_id));
|
|
54
|
+
return record ? Object.assign({}, record) : null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 删除用户偏好
|
|
59
|
+
* @param {String|Number} user_id - 用户标识
|
|
60
|
+
* @returns {Boolean} 是否删除了已登记的用户
|
|
61
|
+
*/
|
|
62
|
+
Memory.prototype.delUser = function (user_id) {
|
|
63
|
+
return this._users.delete(this._key(user_id));
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 判断用户是否已登记
|
|
68
|
+
* @param {String|Number} user_id - 用户标识
|
|
69
|
+
* @returns {Boolean} 是否已登记
|
|
70
|
+
*/
|
|
71
|
+
Memory.prototype.hasUser = function (user_id) {
|
|
72
|
+
return this._users.has(this._key(user_id));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 列出已登记的用户标识
|
|
77
|
+
* @returns {Array<String>} 用户标识数组
|
|
78
|
+
*/
|
|
79
|
+
Memory.prototype.listUsers = function () {
|
|
80
|
+
return Array.from(this._users.keys());
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 清空全部用户偏好
|
|
85
|
+
* @returns {Memory} 当前实例
|
|
86
|
+
*/
|
|
87
|
+
Memory.prototype.clear = function () {
|
|
88
|
+
this._users.clear();
|
|
89
|
+
return this;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 统一存储键的表示(私有方法)
|
|
94
|
+
* @param {String|Number} user_id - 用户标识
|
|
95
|
+
* @returns {String} 文本标识
|
|
96
|
+
*/
|
|
97
|
+
Memory.prototype._key = function (user_id) {
|
|
98
|
+
return String(user_id);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
Memory
|
|
103
|
+
};
|