xshell 0.0.31 → 0.0.32
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/file.js +9 -9
- package/file.js.map +1 -1
- package/i18n/i18n-scan.js +16 -14
- package/i18n/i18n-scan.js.map +1 -1
- package/i18n/index.js +3 -3
- package/i18n/index.js.map +1 -1
- package/i18n/scanner/checker.js +1 -1
- package/i18n/scanner/checker.js.map +1 -1
- package/i18n/scanner/index.d.ts +1 -1
- package/i18n/scanner/index.js +177 -172
- package/i18n/scanner/index.js.map +1 -1
- package/i18n/scanner/parser.js +5 -5
- package/i18n/scanner/parser.js.map +1 -1
- package/i18n/utils.d.ts +1 -1
- package/i18n/utils.js +5 -4
- package/i18n/utils.js.map +1 -1
- package/index.js +5 -5
- package/index.js.map +1 -1
- package/net.js +5 -5
- package/net.js.map +1 -1
- package/package.json +9 -9
- package/process.js +1 -1
- package/process.js.map +1 -1
- package/prototype.browser.js +2 -2
- package/prototype.browser.js.map +1 -1
- package/prototype.js +7 -6
- package/prototype.js.map +1 -1
- package/repl.js +11 -31
- package/repl.js.map +1 -1
- package/server.js +9 -9
- package/server.js.map +1 -1
- package/utils.d.ts +2 -1
- package/utils.js +23 -6
- package/utils.js.map +1 -1
package/i18n/scanner/index.js
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.scanner = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const i18next_scanner_1 =
|
|
6
|
-
const upath_1 =
|
|
7
|
-
const ejs_1 =
|
|
8
|
-
const vinyl_fs_1 =
|
|
9
|
-
const gulp_sort_1 =
|
|
10
|
-
const ora_1 =
|
|
11
|
-
const cli_truncate_1 =
|
|
12
|
-
const vinyl_1 =
|
|
13
|
-
const through2_1 =
|
|
14
|
-
const cli_table3_1 =
|
|
5
|
+
const i18next_scanner_1 = tslib_1.__importDefault(require("i18next-scanner"));
|
|
6
|
+
const upath_1 = tslib_1.__importDefault(require("upath"));
|
|
7
|
+
const ejs_1 = tslib_1.__importDefault(require("ejs"));
|
|
8
|
+
const vinyl_fs_1 = tslib_1.__importDefault(require("vinyl-fs"));
|
|
9
|
+
const gulp_sort_1 = tslib_1.__importDefault(require("gulp-sort"));
|
|
10
|
+
const ora_1 = tslib_1.__importDefault(require("ora"));
|
|
11
|
+
const cli_truncate_1 = tslib_1.__importDefault(require("cli-truncate"));
|
|
12
|
+
const vinyl_1 = tslib_1.__importDefault(require("vinyl"));
|
|
13
|
+
const through2_1 = tslib_1.__importDefault(require("through2"));
|
|
14
|
+
const cli_table3_1 = tslib_1.__importDefault(require("cli-table3"));
|
|
15
15
|
require("../../prototype.js");
|
|
16
16
|
const utils_js_1 = require("../../utils.js");
|
|
17
17
|
const index_js_1 = require("../index.js");
|
|
18
|
-
const rwdict_js_1 =
|
|
18
|
+
const rwdict_js_1 = tslib_1.__importDefault(require("../rwdict.js"));
|
|
19
19
|
const utils_js_2 = require("../utils.js");
|
|
20
20
|
const parser_js_1 = require("./parser.js");
|
|
21
21
|
/** 默认 i18next 扫描配置 */
|
|
@@ -103,7 +103,7 @@ const VALID_EXTENTIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.ejs']);
|
|
|
103
103
|
- `process.cwd()` rootdir 要扫描根目录
|
|
104
104
|
- config 配置信息
|
|
105
105
|
*/
|
|
106
|
-
function scanner(rootdir = upath_1.default.normalize(process.cwd()), config = {}) {
|
|
106
|
+
async function scanner(rootdir = upath_1.default.normalize(process.cwd()), config = {}) {
|
|
107
107
|
const output = upath_1.default.resolve(rootdir, config.output || DEFAULT_CONFIG.output);
|
|
108
108
|
if (!config.input.length)
|
|
109
109
|
throw new Error('运行 i18n-scan 请指定 --input');
|
|
@@ -122,10 +122,10 @@ function scanner(rootdir = upath_1.default.normalize(process.cwd()), config = {}
|
|
|
122
122
|
};
|
|
123
123
|
let dict = new rwdict_js_1.default();
|
|
124
124
|
for (const fp_dict of config.dict)
|
|
125
|
-
dict.merge((0, utils_js_2.
|
|
125
|
+
dict.merge(await (0, utils_js_2.try_load_dict)(upath_1.default.resolve(output, fp_dict)), { print: false, overwrite: true });
|
|
126
126
|
let c_files = 0;
|
|
127
127
|
let c_scanneds = 0;
|
|
128
|
-
let
|
|
128
|
+
let error_handlers = [];
|
|
129
129
|
// 所有语言的扫描统计信息
|
|
130
130
|
let stats = {};
|
|
131
131
|
for (const language of index_js_1.LANGUAGES)
|
|
@@ -168,167 +168,172 @@ function scanner(rootdir = upath_1.default.normalize(process.cwd()), config = {}
|
|
|
168
168
|
contents: Buffer.from(typeof data === 'string' ? data : JSON.stringify(data, null, 4))
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
function flush(cb) {
|
|
219
|
-
// ------------ stats.json
|
|
220
|
-
this.push(new_vinyl_file('stats.json', Object.fromEntries(Object.entries(stats).map(([l, { translateds, untranslateds }]) => [l, { translateds: Array.from(translateds), untranslateds: Array.from(untranslateds) }]))));
|
|
221
|
-
// ------------ 打印 cli 统计表
|
|
222
|
-
const table = new cli_table3_1.default({
|
|
223
|
-
head: [
|
|
224
|
-
'语言',
|
|
225
|
-
'未翻译'.red,
|
|
226
|
-
'已翻译'.green,
|
|
227
|
-
],
|
|
228
|
-
colAligns: ['right', 'right', 'right', 'right'],
|
|
229
|
-
style: { head: [] },
|
|
230
|
-
chars: {
|
|
231
|
-
top: '',
|
|
232
|
-
'top-mid': '',
|
|
233
|
-
'top-left': '',
|
|
234
|
-
'top-right': '',
|
|
235
|
-
bottom: '',
|
|
236
|
-
'bottom-mid': '',
|
|
237
|
-
'bottom-left': '',
|
|
238
|
-
'bottom-right': '',
|
|
239
|
-
left: '',
|
|
240
|
-
'left-mid': '',
|
|
241
|
-
mid: '',
|
|
242
|
-
'mid-mid': '',
|
|
243
|
-
right: '',
|
|
244
|
-
'right-mid': '',
|
|
245
|
-
middle: ' ',
|
|
246
|
-
},
|
|
247
|
-
});
|
|
248
|
-
Object.entries(stats).forEach(([lang, stat]) => {
|
|
249
|
-
table.push([
|
|
250
|
-
lang,
|
|
251
|
-
String(stat.untranslateds.size).red,
|
|
252
|
-
String(stat.translateds.size).green
|
|
253
|
-
]);
|
|
254
|
-
});
|
|
255
|
-
spinner.stop();
|
|
256
|
-
console.log(`Scanned ${c_files} files. Occured ${errors.length} errors.`);
|
|
257
|
-
console.log(table.toString());
|
|
258
|
-
// ------------ 生成 unmarkeds.md 统计
|
|
259
|
-
/*
|
|
260
|
-
const fp_unmarked = path.resolve(config.output, 'unmarkeds.md')
|
|
261
|
-
|
|
262
|
-
if (fs.existsSync(fp_unmarked))
|
|
263
|
-
rimraf.sync(fp_unmarked)
|
|
264
|
-
|
|
265
|
-
if (unmarkeds.length) {
|
|
266
|
-
console.log(colors.yellow(`\n⚠️ 发现未标记的中文字符 ${unmarkeds.length} 处:\n`))
|
|
267
|
-
unmarkeds.forEach(({ value, filepath, loc: { start } }, index) => {
|
|
268
|
-
if (index >= 5) return
|
|
269
|
-
console.log( ` ${colors.white(`'${value}'`)}\t${colors.blue.underline(`${path.relative(rootdir, filepath)}:${start.line}:${start.column + 1}`)}` )
|
|
270
|
-
})
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
this.push( new_vinyl_file( fp_unmarked,
|
|
274
|
-
unmarkeds.map( ({ value, filepath, loc }) =>
|
|
275
|
-
'- [' + value.trim() + '](' + path.relative( config.output, path.resolve(rootdir, filepath || '') ) + '#L' + loc.start.line + ')'
|
|
276
|
-
).join('\n') + '\n'
|
|
277
|
-
))
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (unmarkeds.length > 5) {
|
|
281
|
-
console.log(' ...')
|
|
282
|
-
console.log(colors.yellow(`\n 完整未标记词条请查看 ${colors.blue.underline(path.relative(rootdir, fp_unmarked))}`))
|
|
283
|
-
}
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
// ------------ scan by file
|
|
173
|
+
vinyl_fs_1.default
|
|
174
|
+
.src(config.input, { cwd: rootdir, sync: false })
|
|
175
|
+
// 每个文件扫描前,统计文件数量
|
|
176
|
+
.pipe((0, utils_js_1.map_stream)((file, cb) => {
|
|
177
|
+
// 支持 `// @i18n-noscan` 忽略扫描
|
|
178
|
+
if (/\/\/\s*@i18n-noscan\s/.test(file.contents.toString()))
|
|
179
|
+
return cb();
|
|
180
|
+
c_files++;
|
|
181
|
+
cb(null, file);
|
|
182
|
+
}))
|
|
183
|
+
// 对文件进行排序,保证词条有一定的顺序
|
|
184
|
+
.pipe((0, gulp_sort_1.default)())
|
|
185
|
+
// 分析代码提取词条
|
|
186
|
+
.pipe(i18next_scanner_1.default.createStream(config, function transform(file, encoding, callback) {
|
|
187
|
+
const { parser } = this;
|
|
188
|
+
const ext = upath_1.default.extname(file.path);
|
|
189
|
+
// 只扫描源码文件
|
|
190
|
+
if (!VALID_EXTENTIONS.has(ext)) {
|
|
191
|
+
callback();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
c_scanneds++;
|
|
195
|
+
const percent = Math.round(100 * c_scanneds / c_files);
|
|
196
|
+
const text = `Scanning (${percent}%): ${file.path.blue}`;
|
|
197
|
+
spinner.text = (0, cli_truncate_1.default)(text, process.stdout.columns - 5, { position: 'middle', });
|
|
198
|
+
let code = file.contents.toString();
|
|
199
|
+
if (ext === '.ejs')
|
|
200
|
+
code = ejs_1.default.compile(code, { filename: file.path, client: true, legacyInclude: true }).toString();
|
|
201
|
+
// --- 添加代码中扫描到的 i18n.t('key') 中的 key 到 parser
|
|
202
|
+
// parser.parseFuncFromString 使用 esprima 来解析代码,esprima 仍然不支持 optional chaining !!
|
|
203
|
+
parser.parseFuncFromString(code.replace(/\?\.\[/g, '[').replace(/\?\.\(/g, '(').replace(/\?\./g, '.'), on_scanned);
|
|
204
|
+
// --- 添加代码中扫描到的 Trans 组件中的 key 到 parser
|
|
205
|
+
if (ext === '.jsx' || ext === '.tsx') {
|
|
206
|
+
// parser.parseTransFromString 使用 acorn 解析代码,不支持 TypeScript,添加 parser.parseTransFromStringByBabel
|
|
207
|
+
(0, parser_js_1.mix_parse_trans_from_string_by_babel)(parser);
|
|
208
|
+
parser.parseTransFromStringByBabel(code, { filepath: file.path }, on_scanned, (error) => {
|
|
209
|
+
error_handlers.push(error);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
setTimeout(callback, 0);
|
|
213
|
+
}))
|
|
214
|
+
// 创建词条文件
|
|
215
|
+
.pipe(through2_1.default.obj(
|
|
216
|
+
/** i18n-scanner 会把扫描结果以每个语言一个文件的形式提供,这里解析扫描结果
|
|
217
|
+
* file: 翻译 resource 文件,其中 file.contents 包含翻译的扫描结果
|
|
284
218
|
*/
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
219
|
+
function write(file, encoding, cb) { cb(); },
|
|
220
|
+
/** 生成 stats.json, unmarkeds.md; 打印 untranslated / unmarkeds */
|
|
221
|
+
function flush(cb) {
|
|
222
|
+
// ------------ stats.json
|
|
223
|
+
this.push(new_vinyl_file('stats.json', Object.fromEntries(Object.entries(stats).map(([l, { translateds, untranslateds }]) => [l, { translateds: Array.from(translateds), untranslateds: Array.from(untranslateds) }]))));
|
|
224
|
+
// ------------ 打印 cli 统计表
|
|
225
|
+
const table = new cli_table3_1.default({
|
|
226
|
+
head: [
|
|
227
|
+
'语言',
|
|
228
|
+
'未翻译'.red,
|
|
229
|
+
'已翻译'.green,
|
|
230
|
+
],
|
|
231
|
+
colAligns: ['right', 'right', 'right', 'right'],
|
|
232
|
+
style: { head: [] },
|
|
233
|
+
chars: {
|
|
234
|
+
top: '',
|
|
235
|
+
'top-mid': '',
|
|
236
|
+
'top-left': '',
|
|
237
|
+
'top-right': '',
|
|
238
|
+
bottom: '',
|
|
239
|
+
'bottom-mid': '',
|
|
240
|
+
'bottom-left': '',
|
|
241
|
+
'bottom-right': '',
|
|
242
|
+
left: '',
|
|
243
|
+
'left-mid': '',
|
|
244
|
+
mid: '',
|
|
245
|
+
'mid-mid': '',
|
|
246
|
+
right: '',
|
|
247
|
+
'right-mid': '',
|
|
248
|
+
middle: ' ',
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
Object.entries(stats).forEach(([lang, stat]) => {
|
|
252
|
+
table.push([
|
|
253
|
+
lang,
|
|
254
|
+
String(stat.untranslateds.size).red,
|
|
255
|
+
String(stat.translateds.size).green
|
|
256
|
+
]);
|
|
257
|
+
});
|
|
258
|
+
spinner.stop();
|
|
259
|
+
console.log(`Scanned ${c_files} files. Occured ${error_handlers.length} errors.`);
|
|
260
|
+
console.log(table.toString());
|
|
261
|
+
// ------------ 生成 unmarkeds.md 统计
|
|
262
|
+
/*
|
|
263
|
+
const fp_unmarked = path.resolve(config.output, 'unmarkeds.md')
|
|
264
|
+
|
|
265
|
+
if (fs.existsSync(fp_unmarked))
|
|
266
|
+
rimraf.sync(fp_unmarked)
|
|
267
|
+
|
|
268
|
+
if (unmarkeds.length) {
|
|
269
|
+
console.log(colors.yellow(`\n⚠️ 发现未标记的中文字符 ${unmarkeds.length} 处:\n`))
|
|
270
|
+
unmarkeds.forEach(({ value, filepath, loc: { start } }, index) => {
|
|
271
|
+
if (index >= 5) return
|
|
272
|
+
console.log( ` ${colors.white(`'${value}'`)}\t${colors.blue.underline(`${path.relative(rootdir, filepath)}:${start.line}:${start.column + 1}`)}` )
|
|
273
|
+
})
|
|
294
274
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
275
|
+
|
|
276
|
+
this.push( new_vinyl_file( fp_unmarked,
|
|
277
|
+
unmarkeds.map( ({ value, filepath, loc }) =>
|
|
278
|
+
'- [' + value.trim() + '](' + path.relative( config.output, path.resolve(rootdir, filepath || '') ) + '#L' + loc.start.line + ')'
|
|
279
|
+
).join('\n') + '\n'
|
|
280
|
+
))
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
if (unmarkeds.length > 5) {
|
|
284
|
+
console.log(' ...')
|
|
285
|
+
console.log(colors.yellow(`\n 完整未标记词条请查看 ${colors.blue.underline(path.relative(rootdir, fp_unmarked))}`))
|
|
298
286
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
'
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
287
|
+
*/
|
|
288
|
+
const en_untranslateds = stats.en.untranslateds;
|
|
289
|
+
if (en_untranslateds.size) {
|
|
290
|
+
console.log('\n缺少英文翻译的词条:'.yellow);
|
|
291
|
+
let i = 0;
|
|
292
|
+
for (const untranslated of en_untranslateds) {
|
|
293
|
+
if (i >= 10)
|
|
294
|
+
break;
|
|
295
|
+
console.log(untranslated);
|
|
296
|
+
i++;
|
|
297
|
+
}
|
|
298
|
+
if (en_untranslateds.size > 10) {
|
|
299
|
+
console.log('...');
|
|
300
|
+
console.log(`--- 共 ${en_untranslateds.size} 个未翻译的英文词条 ---`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else
|
|
304
|
+
console.log('\n所有词条都至少含有英文翻译'.green);
|
|
305
|
+
// ------------ 生成 untranslateds.json (扫描到词条还没有英文翻译)
|
|
306
|
+
const fp_untranslateds = upath_1.default.resolve(config.output, 'untranslateds.json');
|
|
307
|
+
let untranslateds = {};
|
|
308
|
+
for (const key of stats.en.untranslateds) {
|
|
309
|
+
let item = { ...dict.get(key) };
|
|
310
|
+
item.en || (item.en = '');
|
|
311
|
+
item.ja || (item.ja = '');
|
|
312
|
+
item.ko || (item.ko = '');
|
|
313
|
+
untranslateds[key] = item;
|
|
314
|
+
}
|
|
315
|
+
this.push(new_vinyl_file(fp_untranslateds, untranslateds));
|
|
316
|
+
// ------------ 写入 dict.json
|
|
317
|
+
const fp_dict_new = upath_1.default.resolve(output, 'dict.json');
|
|
318
|
+
this.push(new_vinyl_file(fp_dict_new, dict.to_json(true) + '\n'));
|
|
319
|
+
console.log(`\n\n${'请手动补全未翻译的词条: '.yellow}${fp_untranslateds.underline.blue}\n` +
|
|
320
|
+
`${'请检查新生成的词典文件: '.yellow}${fp_dict_new.underline.blue}\n` +
|
|
321
|
+
'\n' +
|
|
322
|
+
'补全 untranslateds.json 后需要重新运行扫描,会根据 untranslateds.json 更新 dict.json\n'.yellow +
|
|
323
|
+
'最后 dict.json 所包含的词条会被打包进 js, 通过 new I18N(<dict.json>) 或 i18n.init(<dict.json>) 加载\n\n'.yellow +
|
|
324
|
+
`${'详细文档请查看: '.yellow}${'https://github.com/ShenHongFei/xshell/tree/master/i18n'.blue.underline}`);
|
|
325
|
+
cb();
|
|
326
|
+
}))
|
|
327
|
+
// 写入词条文件
|
|
328
|
+
.pipe(vinyl_fs_1.default.dest(rootdir))
|
|
329
|
+
.on('end', () => {
|
|
330
|
+
if (error_handlers.length) {
|
|
331
|
+
for (const error_handler of error_handlers)
|
|
332
|
+
error_handler();
|
|
333
|
+
console.log(`以上错误可能是由不规范的词条标记导致,标记规范可见:\n${'https://www.i18next.com/translation-function/essentials'.blue.underline}`);
|
|
334
|
+
}
|
|
335
|
+
resolve(stats.en.untranslateds.size);
|
|
336
|
+
});
|
|
332
337
|
});
|
|
333
338
|
}
|
|
334
339
|
exports.scanner = scanner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Transform } from 'stream'\n\nimport i18n_scanner from 'i18next-scanner'\nimport path from 'upath'\nimport ejs from 'ejs'\nimport vfs from 'vinyl-fs'\nimport sort from 'gulp-sort'\nimport ora from 'ora'\nimport cli_truncate from 'cli-truncate'\nimport Vinyl from 'vinyl'\nimport through2 from 'through2'\nimport CliTable from 'cli-table3'\n\nimport '../../prototype.js'\nimport { map_stream } from '../../utils.js'\n\nimport {\n LANGUAGES,\n type Language,\n} from '../index.js'\nimport Dict from '../rwdict.js'\nimport type { _Dict } from '../dict.js'\nimport { try_require } from '../utils.js'\n\n\nimport { mix_parse_trans_from_string_by_babel } from './parser.js'\n\n\n\n/** 默认 i18next 扫描配置 */\nconst DEFAULT_CONFIG = {\n debug: false,\n \n input: [\n // 'src/**/*.{js,jsx,ts,tsx}',\n '!i18n/**', // Use ! to filter out files or directories\n '!node_modules/**',\n '!**/*.d.ts',\n ],\n \n // 相对于根目录\n output: 'i18n/',\n \n // 若是相对路径,则以 output 为基准进行解析\n dict: ['dict.json', 'untranslateds.json'],\n \n lngs: ['zh', 'en', 'ja', 'ko'],\n ns: ['translation'],\n defaultLng: 'zh',\n defaultNs: 'translation',\n\n func: {\n list: [ 'i18next.t', 'i18n.t', 'i18n.__', 't', '__' ],\n extensions: [ ], // 避免在 transform 中执行原生的 parseFuncFromString\n },\n \n trans: {\n extensions: [ ], // 避免在 transform 中执行原生的 parseTransFromString\n fallbackKey: true,\n \n babylon: {\n sourceType: 'module',\n \n allowAwaitOutsideFunction: true,\n \n // https://babeljs.io/docs/en/babel-parser\n plugins: [\n // Language extensions\n 'jsx',\n 'typescript',\n \n // ECMAScript proposals\n 'classProperties',\n 'classPrivateProperties',\n 'classPrivateMethods',\n 'classStaticBlock',\n 'decimal',\n ['decorators', { decoratorsBeforeExport: true }],\n 'doExpressions',\n 'exportDefaultFrom',\n 'functionBind',\n 'importAssertions',\n 'moduleBlocks',\n 'moduleStringNames',\n 'partialApplication',\n ['pipelineOperator', { proposal: 'smart' }],\n 'privateIn',\n ['recordAndTuple', { syntaxType: 'bar' }],\n 'throwExpressions',\n 'topLevelAwait',\n ],\n } as import('@babel/parser').ParserOptions,\n \n // 实际并没有用到 acorn, 用了 babel\n acorn: {\n ecmaVersion: 'latest',\n sourceType: 'module', // defaults to 'module'\n // Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options\n }\n },\n \n // 禁用 : 和 . 作为 seperator\n keySeparator: false, // char to separate keys\n nsSeparator: false, // char to split namespace from key\n \n // Context Form\n context: true, // whether to add context form key\n contextFallback: true, // whether to add a fallback key as well as the context form key\n contextSeparator: '_', // char to split context from key\n\n // Plural\n // whether to add plural form key\n plural (language: string, ns: string, key: string, options: any /** Config */) {\n return language === 'en'\n }, \n pluralFallback: true, // whether to add a fallback key as well as the plural form key\n pluralSeparator: '_', // char to split plural from key\n \n // interpolation options\n interpolation: {\n prefix: '{{', // prefix for interpolation\n suffix: '}}' // suffix for interpolation\n }\n}\n\nconst VALID_EXTENTIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.ejs'])\n\nexport type Config = Partial<(typeof DEFAULT_CONFIG) & {\n defaultValue?: string\n resource?: { loadPath?: string, savePath?: string, jsonIndent?: number, lineEnding?: '\\n' }\n}>\n\n\n/** 扫描源码中的词条,以及收集未翻译的词条,将结果保存到 dict.json 和 untranslateds.json\n - `process.cwd()` rootdir 要扫描根目录\n - config 配置信息\n*/\nexport function scanner (rootdir: string = path.normalize(process.cwd()), config: Config = { }) {\n const output = path.resolve(rootdir, config.output || DEFAULT_CONFIG.output)\n \n if (!config.input.length)\n throw new Error('运行 i18n-scan 请指定 --input')\n \n const input = [...config.input, ...DEFAULT_CONFIG.input]\n \n config = {\n ...DEFAULT_CONFIG,\n ...config,\n input,\n output,\n resource: {\n loadPath: '',\n savePath: path.resolve(output, 'translation/{{lng}}.js'),\n jsonIndent: 4,\n lineEnding: '\\n'\n }\n }\n \n let dict = new Dict()\n \n for (const fp_dict of config.dict)\n dict.merge(\n try_require(\n path.resolve(output, fp_dict)\n ), { print: false, overwrite: true }\n )\n \n \n let c_files = 0\n let c_scanneds = 0\n let errors = []\n \n // 所有语言的扫描统计信息\n let stats: Record<Language, { translateds: Set<string>, untranslateds: Set<string> }> = { } as any\n \n for (const language of LANGUAGES)\n stats[language] = {\n translateds: new Set<string>(),\n untranslateds: new Set<string>()\n }\n \n \n let spinner = ora({ interval: 66 }).start('Scanning...')\n \n \n \n function on_scanned (text: string, { language, key, defaultValue, count, context }: { language?: Language, key?: string, defaultValue?: string, count?: number, context?: string }) {\n // console.log(text, { language, key, defaultValue, count, context })\n \n text = text || defaultValue\n \n if (!key)\n key = context ? `${text}_${context}` : text\n \n if (!language) {\n for (const language of LANGUAGES)\n on_scanned(text, { language, key, count, context })\n return\n }\n \n // console.log(text, { language, key, defaultValue, count, context })\n // debugger\n \n const stat = stats[language]\n \n // 获取已有翻译\n const translation = \n dict.get(key, language) || \n language === 'zh' && text || \n ''\n \n if (language === 'zh' && !context)\n return\n \n if (translation)\n stat.translateds.add(key)\n else\n stat.untranslateds.add(key)\n \n if (language === 'en' && count !== undefined)\n on_scanned(text, { language, key: `${key}_plural`, context })\n }\n \n \n function new_vinyl_file (_path: string, data: string | object) {\n return new Vinyl({\n cwd: rootdir,\n base: rootdir,\n path: path.resolve(config.output, _path),\n contents: Buffer.from(typeof data === 'string' ? data : JSON.stringify(data, null, 4))\n })\n }\n \n \n // ------------ scan by file\n vfs\n .src(config.input, { cwd: rootdir, sync: false })\n \n // 每个文件扫描前,统计文件数量\n .pipe(\n map_stream((file: Vinyl, cb: Function) => {\n // 支持 `// @i18n-noscan` 忽略扫描\n if (/\\/\\/\\s*@i18n-noscan\\s/.test(file.contents.toString())) return cb()\n c_files++\n cb(null, file)\n })\n )\n \n // 对文件进行排序,保证词条有一定的顺序\n .pipe( sort() )\n \n // 分析代码提取词条\n .pipe(\n i18n_scanner.createStream( config, function transform (this: { parser: any }, file: Vinyl, encoding: string, callback: Function): void {\n const { parser } = this\n const ext = path.extname(file.path)\n \n // 只扫描源码文件\n if (!VALID_EXTENTIONS.has(ext)) {\n callback()\n return\n }\n \n c_scanneds++\n const percent = Math.round(\n 100 * c_scanneds / c_files\n )\n const text = `Scanning (${percent}%): ${file.path.blue}`\n spinner.text = cli_truncate(text, process.stdout.columns - 5, { position: 'middle', })\n \n let code = file.contents.toString()\n \n if (ext === '.ejs')\n code = ejs.compile( code, { filename: file.path, client: true, legacyInclude: true } as any ).toString()\n \n \n // --- 添加代码中扫描到的 i18n.t('key') 中的 key 到 parser\n // parser.parseFuncFromString 使用 esprima 来解析代码,esprima 仍然不支持 optional chaining !!\n parser.parseFuncFromString(code.replace(/\\?\\.\\[/g, '[').replace(/\\?\\.\\(/g, '(').replace(/\\?\\./g, '.'), on_scanned)\n \n // --- 添加代码中扫描到的 Trans 组件中的 key 到 parser\n if (ext === '.jsx' || ext === '.tsx') {\n // parser.parseTransFromString 使用 acorn 解析代码,不支持 TypeScript,添加 parser.parseTransFromStringByBabel\n mix_parse_trans_from_string_by_babel(parser)\n parser.parseTransFromStringByBabel(\n code,\n { filepath: file.path },\n on_scanned,\n (error: Error) => { errors.push(error) }\n )\n }\n \n setTimeout(callback, 0)\n })\n )\n \n // 创建词条文件\n .pipe(\n through2.obj(\n /** i18n-scanner 会把扫描结果以每个语言一个文件的形式提供,这里解析扫描结果\n * file: 翻译 resource 文件,其中 file.contents 包含翻译的扫描结果\n */\n function write (this: Transform, file: Vinyl, encoding: string, cb: Function) { cb() },\n \n /** 生成 stats.json, unmarkeds.md; 打印 untranslated / unmarkeds */\n function flush (this: Transform, cb: Function) {\n // ------------ stats.json\n this.push(new_vinyl_file('stats.json', \n Object.fromEntries(\n Object.entries(stats).map( ([l, { translateds, untranslateds }]) => \n [l, { translateds: Array.from(translateds), untranslateds: Array.from(untranslateds) }])\n )\n ))\n \n \n // ------------ 打印 cli 统计表\n const table = new CliTable({\n head: [\n '语言',\n '未翻译'.red,\n '已翻译'.green,\n ],\n colAligns: ['right', 'right', 'right', 'right'],\n style: { head: [] },\n chars: {\n top: '',\n 'top-mid': '',\n 'top-left': '',\n 'top-right': '',\n bottom: '',\n 'bottom-mid': '',\n 'bottom-left': '',\n 'bottom-right': '',\n left: '',\n 'left-mid': '',\n mid: '',\n 'mid-mid': '',\n right: '',\n 'right-mid': '',\n middle: ' ',\n },\n })\n \n Object.entries(stats).forEach( ([lang, stat]) => {\n table.push([\n lang, \n String(stat.untranslateds.size).red, \n String(stat.translateds.size).green\n ] as any)\n })\n \n \n \n spinner.stop()\n console.log(`Scanned ${c_files} files. Occured ${errors.length} errors.`)\n console.log(table.toString())\n \n \n // ------------ 生成 unmarkeds.md 统计\n /*\n const fp_unmarked = path.resolve(config.output, 'unmarkeds.md')\n \n if (fs.existsSync(fp_unmarked))\n rimraf.sync(fp_unmarked)\n \n if (unmarkeds.length) {\n console.log(colors.yellow(`\\n⚠️ 发现未标记的中文字符 ${unmarkeds.length} 处:\\n`))\n unmarkeds.forEach(({ value, filepath, loc: { start } }, index) => {\n if (index >= 5) return\n console.log( ` ${colors.white(`'${value}'`)}\\t${colors.blue.underline(`${path.relative(rootdir, filepath)}:${start.line}:${start.column + 1}`)}` )\n })\n }\n \n this.push( new_vinyl_file( fp_unmarked, \n unmarkeds.map( ({ value, filepath, loc }) =>\n '- [' + value.trim() + '](' + path.relative( config.output, path.resolve(rootdir, filepath || '') ) + '#L' + loc.start.line + ')'\n ).join('\\n') + '\\n'\n ))\n \n \n if (unmarkeds.length > 5) {\n console.log(' ...')\n console.log(colors.yellow(`\\n 完整未标记词条请查看 ${colors.blue.underline(path.relative(rootdir, fp_unmarked))}`))\n }\n */\n \n const en_untranslateds = stats.en.untranslateds\n if (en_untranslateds.size) {\n console.log('\\n缺少英文翻译的词条:'.yellow)\n let i = 0\n for (const untranslated of en_untranslateds) {\n if (i >= 10)\n break\n console.log(untranslated)\n i++\n }\n if (en_untranslateds.size > 10) {\n console.log('...')\n console.log(`--- 共 ${en_untranslateds.size} 个未翻译的英文词条 ---`)\n }\n } else\n console.log('\\n所有词条都至少含有英文翻译'.green)\n \n \n // ------------ 生成 untranslateds.json (扫描到词条还没有英文翻译)\n const fp_untranslateds = path.resolve(config.output, 'untranslateds.json')\n \n let untranslateds: _Dict = { }\n \n for (const key of stats.en.untranslateds) {\n let item = { ...dict.get(key) }\n item.en ||= ''\n item.ja ||= ''\n item.ko ||= ''\n untranslateds[key] = item\n }\n \n this.push(\n new_vinyl_file(fp_untranslateds, untranslateds)\n )\n \n \n // ------------ 写入 dict.json\n const fp_dict_new = path.resolve(output, 'dict.json')\n this.push(\n new_vinyl_file(\n fp_dict_new,\n dict.to_json(true) + '\\n'\n )\n )\n \n console.log(\n `\\n\\n${'请手动补全未翻译的词条: '.yellow}${fp_untranslateds.underline.blue}\\n` +\n `${'请检查新生成的词典文件: '.yellow}${fp_dict_new.underline.blue}\\n` +\n '\\n' +\n '补全 untranslateds.json 后需要重新运行扫描,会根据 untranslateds.json 更新 dict.json\\n'.yellow +\n '最后 dict.json 所包含的词条会被打包进 js, 通过 new I18N(<dict.json>) 或 i18n.init(<dict.json>) 加载\\n\\n'.yellow +\n `${'详细文档请查看: '.yellow}${'https://github.com/ShenHongFei/xshell/tree/master/i18n'.blue.underline}`\n )\n \n cb()\n }\n )\n )\n \n // 写入词条文件\n .pipe(\n vfs.dest(rootdir)\n )\n \n .on('end', () => {\n for (const error_handler of errors)\n error_handler()\n if (!errors.length)\n return\n console.log(`以上错误可能是由不规范的词条标记导致,标记规范可见:\\n${'https://www.i18next.com/translation-function/essentials'.blue.underline}`)\n })\n}\n\nexport default scanner\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;AAEA,8EAA0C;AAC1C,0DAAwB;AACxB,sDAAqB;AACrB,gEAA0B;AAC1B,kEAA4B;AAC5B,sDAAqB;AACrB,wEAAuC;AACvC,0DAAyB;AACzB,gEAA+B;AAC/B,oEAAiC;AAEjC,8BAA2B;AAC3B,6CAA2C;AAE3C,0CAGoB;AACpB,qEAA+B;AAE/B,0CAA2C;AAG3C,2CAAkE;AAIlE,sBAAsB;AACtB,MAAM,cAAc,GAAG;IACnB,KAAK,EAAE,KAAK;IAEZ,KAAK,EAAE;QACH,8BAA8B;QAC9B,UAAU;QACV,kBAAkB;QAClB,YAAY;KACf;IAED,SAAS;IACT,MAAM,EAAE,OAAO;IAEf,2BAA2B;IAC3B,IAAI,EAAE,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAEzC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;IAC9B,EAAE,EAAE,CAAC,aAAa,CAAC;IACnB,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,aAAa;IAExB,IAAI,EAAE;QACF,IAAI,EAAE,CAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAE;QACrD,UAAU,EAAE,EAAG,EAAE,2CAA2C;KAC/D;IAED,KAAK,EAAE;QACH,UAAU,EAAE,EAAG;QACf,WAAW,EAAE,IAAI;QAEjB,OAAO,EAAE;YACL,UAAU,EAAE,QAAQ;YAEpB,yBAAyB,EAAE,IAAI;YAE/B,0CAA0C;YAC1C,OAAO,EAAE;gBACL,sBAAsB;gBACtB,KAAK;gBACL,YAAY;gBAEZ,uBAAuB;gBACvB,iBAAiB;gBACjB,wBAAwB;gBACxB,qBAAqB;gBACrB,kBAAkB;gBAClB,SAAS;gBACT,CAAC,YAAY,EAAE,EAAE,sBAAsB,EAAE,IAAI,EAAE,CAAC;gBAChD,eAAe;gBACf,mBAAmB;gBACnB,cAAc;gBACd,kBAAkB;gBAClB,cAAc;gBACd,mBAAmB;gBACnB,oBAAoB;gBACpB,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;gBAC3C,WAAW;gBACX,CAAC,gBAAgB,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;gBACzC,kBAAkB;gBAClB,eAAe;aAClB;SACqC;QAE1C,0BAA0B;QAC1B,KAAK,EAAE;YACH,WAAW,EAAE,QAAQ;YACrB,UAAU,EAAE,QAAQ,EAAE,uBAAuB;YAC7C,gGAAgG;SACnG;KACJ;IAED,wBAAwB;IACxB,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,KAAK;IAElB,eAAe;IACf,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,IAAI;IACrB,gBAAgB,EAAE,GAAG;IAErB,SAAS;IACT,iCAAiC;IACjC,MAAM,CAAE,QAAgB,EAAE,EAAU,EAAE,GAAW,EAAE,OAAY,CAAC,aAAa;QACzE,OAAO,QAAQ,KAAK,IAAI,CAAA;IAC5B,CAAC;IACD,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,GAAG;IAEpB,wBAAwB;IACxB,aAAa,EAAE;QACX,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,IAAI,CAAC,2BAA2B;KAC3C;CACJ,CAAA;AAED,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAQxE;;;EAGE;AACK,KAAK,UAAU,OAAO,CAAE,UAAkB,eAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,SAAiB,EAAG;IAChG,MAAM,MAAM,GAAG,eAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,CAAA;IAE5E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM;QACpB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;IAE/C,MAAM,KAAK,GAAI,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;IAEzD,MAAM,GAAG;QACL,GAAG,cAAc;QACjB,GAAG,MAAM;QACT,KAAK;QACL,MAAM;QACN,QAAQ,EAAE;YACN,QAAQ,EAAE,EAAE;YACZ,QAAQ,EAAE,eAAI,CAAC,OAAO,CAAC,MAAM,EAAE,wBAAwB,CAAC;YACxD,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,IAAI;SACnB;KACJ,CAAA;IAED,IAAI,IAAI,GAAG,IAAI,mBAAI,EAAE,CAAA;IAErB,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI;QAC7B,IAAI,CAAC,KAAK,CACN,MAAM,IAAA,wBAAa,EACf,eAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAChC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CACvC,CAAA;IAGL,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,cAAc,GAAG,EAAE,CAAA;IAEvB,cAAc;IACd,IAAI,KAAK,GAA+E,EAAU,CAAA;IAElG,KAAK,MAAM,QAAQ,IAAI,oBAAS;QAC5B,KAAK,CAAC,QAAQ,CAAC,GAAG;YACd,WAAW,EAAE,IAAI,GAAG,EAAU;YAC9B,aAAa,EAAE,IAAI,GAAG,EAAU;SACnC,CAAA;IAGL,IAAI,OAAO,GAAG,IAAA,aAAG,EAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;IAIxD,SAAS,UAAU,CAAE,IAAY,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAkG;QAC9K,qEAAqE;QAErE,IAAI,GAAG,IAAI,IAAI,YAAY,CAAA;QAE3B,IAAI,CAAC,GAAG;YACJ,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;QAE/C,IAAI,CAAC,QAAQ,EAAE;YACX,KAAK,MAAM,QAAQ,IAAI,oBAAS;gBAC5B,UAAU,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;YACvD,OAAM;SACT;QAED,qEAAqE;QACrE,WAAW;QAEX,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAA;QAE5B,SAAS;QACT,MAAM,WAAW,GACb,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC;YACvB,QAAQ,KAAK,IAAI,IAAI,IAAI;YACzB,EAAE,CAAA;QAEN,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,OAAO;YAC7B,OAAM;QAEV,IAAI,WAAW;YACX,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;;YAEzB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAE/B,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;YACxC,UAAU,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,CAAA;IACrE,CAAC;IAGD,SAAS,cAAc,CAAE,KAAa,EAAE,IAAqB;QACzD,OAAO,IAAI,eAAK,CAAC;YACb,GAAG,EAAE,OAAO;YACZ,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,eAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;YACxC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;SACzF,CAAC,CAAA;IACN,CAAC;IAED,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,4BAA4B;QAC5B,kBAAG;aACE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YAEjD,iBAAiB;aAChB,IAAI,CACD,IAAA,qBAAU,EAAC,CAAC,IAAW,EAAE,EAAY,EAAE,EAAE;YACrC,4BAA4B;YAC5B,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACtD,OAAO,EAAE,EAAE,CAAA;YACf,OAAO,EAAE,CAAA;YACT,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAClB,CAAC,CAAC,CACL;YAED,qBAAqB;aACpB,IAAI,CACD,IAAA,mBAAI,GAAE,CACT;YAED,WAAW;aACV,IAAI,CACD,yBAAY,CAAC,YAAY,CAAE,MAAM,EAAE,SAAS,SAAS,CAAyB,IAAW,EAAE,QAAgB,EAAE,QAAkB;YAC3H,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;YACvB,MAAM,GAAG,GAAG,eAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAEnC,UAAU;YACV,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;gBAC5B,QAAQ,EAAE,CAAA;gBACV,OAAM;aACT;YAED,UAAU,EAAE,CAAA;YACZ,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CACtB,GAAG,GAAG,UAAU,GAAG,OAAO,CAC7B,CAAA;YACD,MAAM,IAAI,GAAG,aAAa,OAAO,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;YACxD,OAAO,CAAC,IAAI,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,CAAC,CAAA;YAEtF,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAA;YAEnC,IAAI,GAAG,KAAK,MAAM;gBACd,IAAI,GAAG,aAAG,CAAC,OAAO,CAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAS,CAAE,CAAC,QAAQ,EAAE,CAAA;YAG5G,8CAA8C;YAC9C,iFAAiF;YACjF,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,CAAA;YAElH,wCAAwC;YACxC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,EAAE;gBAClC,iGAAiG;gBACjG,IAAA,gDAAoC,EAAC,MAAM,CAAC,CAAA;gBAC5C,MAAM,CAAC,2BAA2B,CAC9B,IAAI,EACJ,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,EACvB,UAAU,EACV,CAAC,KAAY,EAAE,EAAE;oBACb,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAC9B,CAAC,CACJ,CAAA;aACJ;YAED,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;QAC3B,CAAC,CAAC,CACL;YAED,SAAS;aACR,IAAI,CACD,kBAAQ,CAAC,GAAG;QACR;;UAEE;QACF,SAAS,KAAK,CAAmB,IAAW,EAAE,QAAgB,EAAE,EAAY,IAAI,EAAE,EAAE,CAAA,CAAC,CAAC;QAEtF,+DAA+D;QAC/D,SAAS,KAAK,CAAmB,EAAY;YACzC,0BAA0B;YAC1B,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,EACjC,MAAM,CAAC,WAAW,CACd,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAE,CAAC,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,CAC/D,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAC/F,CACJ,CAAC,CAAA;YAGF,0BAA0B;YAC1B,MAAM,KAAK,GAAG,IAAI,oBAAQ,CAAC;gBACvB,IAAI,EAAE;oBACF,IAAI;oBACJ,KAAK,CAAC,GAAG;oBACT,KAAK,CAAC,KAAK;iBACd;gBACD,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;gBAC/C,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;gBACnB,KAAK,EAAE;oBACH,GAAG,EAAE,EAAE;oBACP,SAAS,EAAE,EAAE;oBACb,UAAU,EAAE,EAAE;oBACd,WAAW,EAAE,EAAE;oBACf,MAAM,EAAE,EAAE;oBACV,YAAY,EAAE,EAAE;oBAChB,aAAa,EAAE,EAAE;oBACjB,cAAc,EAAE,EAAE;oBAClB,IAAI,EAAE,EAAE;oBACR,UAAU,EAAE,EAAE;oBACd,GAAG,EAAE,EAAE;oBACP,SAAS,EAAE,EAAE;oBACb,KAAK,EAAE,EAAE;oBACT,WAAW,EAAE,EAAE;oBACf,MAAM,EAAE,GAAG;iBACd;aACJ,CAAC,CAAA;YAEF,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;gBAC5C,KAAK,CAAC,IAAI,CAAC;oBACP,IAAI;oBACJ,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG;oBACnC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK;iBAC/B,CAAC,CAAA;YACb,CAAC,CAAC,CAAA;YAIF,OAAO,CAAC,IAAI,EAAE,CAAA;YACd,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,mBAAmB,cAAc,CAAC,MAAM,UAAU,CAAC,CAAA;YACjF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;YAG7B,kCAAkC;YAClC;;;;;;;;;;;;;;;;;;;;;;;;;cAyBE;YAEF,MAAM,gBAAgB,GAAG,KAAK,CAAC,EAAE,CAAC,aAAa,CAAA;YAC/C,IAAI,gBAAgB,CAAC,IAAI,EAAE;gBACvB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;gBAClC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACT,KAAK,MAAM,YAAY,IAAI,gBAAgB,EAAE;oBACzC,IAAI,CAAC,IAAI,EAAE;wBACP,MAAK;oBACT,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;oBACzB,CAAC,EAAE,CAAA;iBACN;gBACD,IAAI,gBAAgB,CAAC,IAAI,GAAG,EAAE,EAAE;oBAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;oBAClB,OAAO,CAAC,GAAG,CAAC,SAAS,gBAAgB,CAAC,IAAI,gBAAgB,CAAC,CAAA;iBAC9D;aACJ;;gBACG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAA;YAGxC,oDAAoD;YACpD,MAAM,gBAAgB,GAAG,eAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;YAE1E,IAAI,aAAa,GAAU,EAAG,CAAA;YAE9B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,aAAa,EAAE;gBACtC,IAAI,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAA;gBAC/B,IAAI,CAAC,EAAE,KAAP,IAAI,CAAC,EAAE,GAAK,EAAE,EAAA;gBACd,IAAI,CAAC,EAAE,KAAP,IAAI,CAAC,EAAE,GAAK,EAAE,EAAA;gBACd,IAAI,CAAC,EAAE,KAAP,IAAI,CAAC,EAAE,GAAK,EAAE,EAAA;gBACd,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;aAC5B;YAED,IAAI,CAAC,IAAI,CACL,cAAc,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAClD,CAAA;YAGD,4BAA4B;YAC5B,MAAM,WAAW,GAAG,eAAI,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;YACrD,IAAI,CAAC,IAAI,CACL,cAAc,CACV,WAAW,EACX,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAC5B,CACJ,CAAA;YAED,OAAO,CAAC,GAAG,CACP,OAAO,eAAe,CAAC,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,IAAI,IAAI;gBACnE,GAAG,eAAe,CAAC,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,IAAI;gBAC1D,IAAI;gBACJ,uEAAuE,CAAC,MAAM;gBAC9E,uFAAuF,CAAC,MAAM;gBAC9F,GAAG,WAAW,CAAC,MAAM,GAAG,wDAAwD,CAAC,IAAI,CAAC,SAAS,EAAE,CACpG,CAAA;YAED,EAAE,EAAE,CAAA;QACR,CAAC,CACJ,CACJ;YAED,SAAS;aACR,IAAI,CACD,kBAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CACpB;aAEA,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACZ,IAAI,cAAc,CAAC,MAAM,EAAE;gBACvB,KAAK,MAAM,aAAa,IAAI,cAAc;oBACtC,aAAa,EAAE,CAAA;gBAEnB,OAAO,CAAC,GAAG,CAAC,+BAA+B,yDAAyD,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;aACzH;YAED,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;QACxC,CAAC,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;AACN,CAAC;AAzUD,0BAyUC;AAED,kBAAe,OAAO,CAAA","sourcesContent":["import type { Transform } from 'stream'\n\nimport i18n_scanner from 'i18next-scanner'\nimport path from 'upath'\nimport ejs from 'ejs'\nimport vfs from 'vinyl-fs'\nimport sort from 'gulp-sort'\nimport ora from 'ora'\nimport cli_truncate from 'cli-truncate'\nimport Vinyl from 'vinyl'\nimport through2 from 'through2'\nimport CliTable from 'cli-table3'\n\nimport '../../prototype.js'\nimport { map_stream } from '../../utils.js'\n\nimport {\n LANGUAGES,\n type Language,\n} from '../index.js'\nimport Dict from '../rwdict.js'\nimport type { _Dict } from '../dict.js'\nimport { try_load_dict } from '../utils.js'\n\n\nimport { mix_parse_trans_from_string_by_babel } from './parser.js'\n\n\n\n/** 默认 i18next 扫描配置 */\nconst DEFAULT_CONFIG = {\n debug: false,\n \n input: [\n // 'src/**/*.{js,jsx,ts,tsx}',\n '!i18n/**', // Use ! to filter out files or directories\n '!node_modules/**',\n '!**/*.d.ts',\n ],\n \n // 相对于根目录\n output: 'i18n/',\n \n // 若是相对路径,则以 output 为基准进行解析\n dict: ['dict.json', 'untranslateds.json'],\n \n lngs: ['zh', 'en', 'ja', 'ko'],\n ns: ['translation'],\n defaultLng: 'zh',\n defaultNs: 'translation',\n\n func: {\n list: [ 'i18next.t', 'i18n.t', 'i18n.__', 't', '__' ],\n extensions: [ ], // 避免在 transform 中执行原生的 parseFuncFromString\n },\n \n trans: {\n extensions: [ ], // 避免在 transform 中执行原生的 parseTransFromString\n fallbackKey: true,\n \n babylon: {\n sourceType: 'module',\n \n allowAwaitOutsideFunction: true,\n \n // https://babeljs.io/docs/en/babel-parser\n plugins: [\n // Language extensions\n 'jsx',\n 'typescript',\n \n // ECMAScript proposals\n 'classProperties',\n 'classPrivateProperties',\n 'classPrivateMethods',\n 'classStaticBlock',\n 'decimal',\n ['decorators', { decoratorsBeforeExport: true }],\n 'doExpressions',\n 'exportDefaultFrom',\n 'functionBind',\n 'importAssertions',\n 'moduleBlocks',\n 'moduleStringNames',\n 'partialApplication',\n ['pipelineOperator', { proposal: 'smart' }],\n 'privateIn',\n ['recordAndTuple', { syntaxType: 'bar' }],\n 'throwExpressions',\n 'topLevelAwait',\n ],\n } as import('@babel/parser').ParserOptions,\n \n // 实际并没有用到 acorn, 用了 babel\n acorn: {\n ecmaVersion: 'latest',\n sourceType: 'module', // defaults to 'module'\n // Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options\n }\n },\n \n // 禁用 : 和 . 作为 seperator\n keySeparator: false, // char to separate keys\n nsSeparator: false, // char to split namespace from key\n \n // Context Form\n context: true, // whether to add context form key\n contextFallback: true, // whether to add a fallback key as well as the context form key\n contextSeparator: '_', // char to split context from key\n\n // Plural\n // whether to add plural form key\n plural (language: string, ns: string, key: string, options: any /** Config */) {\n return language === 'en'\n }, \n pluralFallback: true, // whether to add a fallback key as well as the plural form key\n pluralSeparator: '_', // char to split plural from key\n \n // interpolation options\n interpolation: {\n prefix: '{{', // prefix for interpolation\n suffix: '}}' // suffix for interpolation\n }\n}\n\nconst VALID_EXTENTIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.ejs'])\n\nexport type Config = Partial<(typeof DEFAULT_CONFIG) & {\n defaultValue?: string\n resource?: { loadPath?: string, savePath?: string, jsonIndent?: number, lineEnding?: '\\n' }\n}>\n\n\n/** 扫描源码中的词条,以及收集未翻译的词条,将结果保存到 dict.json 和 untranslateds.json\n - `process.cwd()` rootdir 要扫描根目录\n - config 配置信息\n*/\nexport async function scanner (rootdir: string = path.normalize(process.cwd()), config: Config = { }) {\n const output = path.resolve(rootdir, config.output || DEFAULT_CONFIG.output)\n \n if (!config.input.length)\n throw new Error('运行 i18n-scan 请指定 --input')\n \n const input = [...config.input, ...DEFAULT_CONFIG.input]\n \n config = {\n ...DEFAULT_CONFIG,\n ...config,\n input,\n output,\n resource: {\n loadPath: '',\n savePath: path.resolve(output, 'translation/{{lng}}.js'),\n jsonIndent: 4,\n lineEnding: '\\n'\n }\n }\n \n let dict = new Dict()\n \n for (const fp_dict of config.dict)\n dict.merge(\n await try_load_dict(\n path.resolve(output, fp_dict)\n ), { print: false, overwrite: true }\n )\n \n \n let c_files = 0\n let c_scanneds = 0\n let error_handlers = []\n \n // 所有语言的扫描统计信息\n let stats: Record<Language, { translateds: Set<string>, untranslateds: Set<string> }> = { } as any\n \n for (const language of LANGUAGES)\n stats[language] = {\n translateds: new Set<string>(),\n untranslateds: new Set<string>()\n }\n \n \n let spinner = ora({ interval: 66 }).start('Scanning...')\n \n \n \n function on_scanned (text: string, { language, key, defaultValue, count, context }: { language?: Language, key?: string, defaultValue?: string, count?: number, context?: string }) {\n // console.log(text, { language, key, defaultValue, count, context })\n \n text = text || defaultValue\n \n if (!key)\n key = context ? `${text}_${context}` : text\n \n if (!language) {\n for (const language of LANGUAGES)\n on_scanned(text, { language, key, count, context })\n return\n }\n \n // console.log(text, { language, key, defaultValue, count, context })\n // debugger\n \n const stat = stats[language]\n \n // 获取已有翻译\n const translation = \n dict.get(key, language) || \n language === 'zh' && text || \n ''\n \n if (language === 'zh' && !context)\n return\n \n if (translation)\n stat.translateds.add(key)\n else\n stat.untranslateds.add(key)\n \n if (language === 'en' && count !== undefined)\n on_scanned(text, { language, key: `${key}_plural`, context })\n }\n \n \n function new_vinyl_file (_path: string, data: string | object) {\n return new Vinyl({\n cwd: rootdir,\n base: rootdir,\n path: path.resolve(config.output, _path),\n contents: Buffer.from(typeof data === 'string' ? data : JSON.stringify(data, null, 4))\n })\n }\n \n return new Promise<number>((resolve, reject) => {\n // ------------ scan by file\n vfs\n .src(config.input, { cwd: rootdir, sync: false })\n \n // 每个文件扫描前,统计文件数量\n .pipe(\n map_stream((file: Vinyl, cb: Function) => {\n // 支持 `// @i18n-noscan` 忽略扫描\n if (/\\/\\/\\s*@i18n-noscan\\s/.test(file.contents.toString()))\n return cb()\n c_files++\n cb(null, file)\n })\n )\n \n // 对文件进行排序,保证词条有一定的顺序\n .pipe(\n sort()\n )\n \n // 分析代码提取词条\n .pipe(\n i18n_scanner.createStream( config, function transform (this: { parser: any }, file: Vinyl, encoding: string, callback: Function): void {\n const { parser } = this\n const ext = path.extname(file.path)\n \n // 只扫描源码文件\n if (!VALID_EXTENTIONS.has(ext)) {\n callback()\n return\n }\n \n c_scanneds++\n const percent = Math.round(\n 100 * c_scanneds / c_files\n )\n const text = `Scanning (${percent}%): ${file.path.blue}`\n spinner.text = cli_truncate(text, process.stdout.columns - 5, { position: 'middle', })\n \n let code = file.contents.toString()\n \n if (ext === '.ejs')\n code = ejs.compile( code, { filename: file.path, client: true, legacyInclude: true } as any ).toString()\n \n \n // --- 添加代码中扫描到的 i18n.t('key') 中的 key 到 parser\n // parser.parseFuncFromString 使用 esprima 来解析代码,esprima 仍然不支持 optional chaining !!\n parser.parseFuncFromString(code.replace(/\\?\\.\\[/g, '[').replace(/\\?\\.\\(/g, '(').replace(/\\?\\./g, '.'), on_scanned)\n \n // --- 添加代码中扫描到的 Trans 组件中的 key 到 parser\n if (ext === '.jsx' || ext === '.tsx') {\n // parser.parseTransFromString 使用 acorn 解析代码,不支持 TypeScript,添加 parser.parseTransFromStringByBabel\n mix_parse_trans_from_string_by_babel(parser)\n parser.parseTransFromStringByBabel(\n code,\n { filepath: file.path },\n on_scanned,\n (error: Error) => {\n error_handlers.push(error)\n }\n )\n }\n \n setTimeout(callback, 0)\n })\n )\n \n // 创建词条文件\n .pipe(\n through2.obj(\n /** i18n-scanner 会把扫描结果以每个语言一个文件的形式提供,这里解析扫描结果\n * file: 翻译 resource 文件,其中 file.contents 包含翻译的扫描结果\n */\n function write (this: Transform, file: Vinyl, encoding: string, cb: Function) { cb() },\n \n /** 生成 stats.json, unmarkeds.md; 打印 untranslated / unmarkeds */\n function flush (this: Transform, cb: Function) {\n // ------------ stats.json\n this.push(new_vinyl_file('stats.json', \n Object.fromEntries(\n Object.entries(stats).map( ([l, { translateds, untranslateds }]) => \n [l, { translateds: Array.from(translateds), untranslateds: Array.from(untranslateds) }])\n )\n ))\n \n \n // ------------ 打印 cli 统计表\n const table = new CliTable({\n head: [\n '语言',\n '未翻译'.red,\n '已翻译'.green,\n ],\n colAligns: ['right', 'right', 'right', 'right'],\n style: { head: [] },\n chars: {\n top: '',\n 'top-mid': '',\n 'top-left': '',\n 'top-right': '',\n bottom: '',\n 'bottom-mid': '',\n 'bottom-left': '',\n 'bottom-right': '',\n left: '',\n 'left-mid': '',\n mid: '',\n 'mid-mid': '',\n right: '',\n 'right-mid': '',\n middle: ' ',\n },\n })\n \n Object.entries(stats).forEach( ([lang, stat]) => {\n table.push([\n lang, \n String(stat.untranslateds.size).red, \n String(stat.translateds.size).green\n ] as any)\n })\n \n \n \n spinner.stop()\n console.log(`Scanned ${c_files} files. Occured ${error_handlers.length} errors.`)\n console.log(table.toString())\n \n \n // ------------ 生成 unmarkeds.md 统计\n /*\n const fp_unmarked = path.resolve(config.output, 'unmarkeds.md')\n \n if (fs.existsSync(fp_unmarked))\n rimraf.sync(fp_unmarked)\n \n if (unmarkeds.length) {\n console.log(colors.yellow(`\\n⚠️ 发现未标记的中文字符 ${unmarkeds.length} 处:\\n`))\n unmarkeds.forEach(({ value, filepath, loc: { start } }, index) => {\n if (index >= 5) return\n console.log( ` ${colors.white(`'${value}'`)}\\t${colors.blue.underline(`${path.relative(rootdir, filepath)}:${start.line}:${start.column + 1}`)}` )\n })\n }\n \n this.push( new_vinyl_file( fp_unmarked, \n unmarkeds.map( ({ value, filepath, loc }) =>\n '- [' + value.trim() + '](' + path.relative( config.output, path.resolve(rootdir, filepath || '') ) + '#L' + loc.start.line + ')'\n ).join('\\n') + '\\n'\n ))\n \n \n if (unmarkeds.length > 5) {\n console.log(' ...')\n console.log(colors.yellow(`\\n 完整未标记词条请查看 ${colors.blue.underline(path.relative(rootdir, fp_unmarked))}`))\n }\n */\n \n const en_untranslateds = stats.en.untranslateds\n if (en_untranslateds.size) {\n console.log('\\n缺少英文翻译的词条:'.yellow)\n let i = 0\n for (const untranslated of en_untranslateds) {\n if (i >= 10)\n break\n console.log(untranslated)\n i++\n }\n if (en_untranslateds.size > 10) {\n console.log('...')\n console.log(`--- 共 ${en_untranslateds.size} 个未翻译的英文词条 ---`)\n }\n } else\n console.log('\\n所有词条都至少含有英文翻译'.green)\n \n \n // ------------ 生成 untranslateds.json (扫描到词条还没有英文翻译)\n const fp_untranslateds = path.resolve(config.output, 'untranslateds.json')\n \n let untranslateds: _Dict = { }\n \n for (const key of stats.en.untranslateds) {\n let item = { ...dict.get(key) }\n item.en ||= ''\n item.ja ||= ''\n item.ko ||= ''\n untranslateds[key] = item\n }\n \n this.push(\n new_vinyl_file(fp_untranslateds, untranslateds)\n )\n \n \n // ------------ 写入 dict.json\n const fp_dict_new = path.resolve(output, 'dict.json')\n this.push(\n new_vinyl_file(\n fp_dict_new,\n dict.to_json(true) + '\\n'\n )\n )\n \n console.log(\n `\\n\\n${'请手动补全未翻译的词条: '.yellow}${fp_untranslateds.underline.blue}\\n` +\n `${'请检查新生成的词典文件: '.yellow}${fp_dict_new.underline.blue}\\n` +\n '\\n' +\n '补全 untranslateds.json 后需要重新运行扫描,会根据 untranslateds.json 更新 dict.json\\n'.yellow +\n '最后 dict.json 所包含的词条会被打包进 js, 通过 new I18N(<dict.json>) 或 i18n.init(<dict.json>) 加载\\n\\n'.yellow +\n `${'详细文档请查看: '.yellow}${'https://github.com/ShenHongFei/xshell/tree/master/i18n'.blue.underline}`\n )\n \n cb()\n }\n )\n )\n \n // 写入词条文件\n .pipe(\n vfs.dest(rootdir)\n )\n \n .on('end', () => {\n if (error_handlers.length) {\n for (const error_handler of error_handlers)\n error_handler()\n \n console.log(`以上错误可能是由不规范的词条标记导致,标记规范可见:\\n${'https://www.i18next.com/translation-function/essentials'.blue.underline}`)\n }\n \n resolve(stats.en.untranslateds.size)\n })\n })\n}\n\nexport default scanner\n"]}
|
package/i18n/scanner/parser.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mix_parse_trans_from_string_by_babel = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const castArray_js_1 =
|
|
6
|
-
const trim_js_1 =
|
|
7
|
-
const get_js_1 =
|
|
8
|
-
const traverse_1 =
|
|
5
|
+
const castArray_js_1 = tslib_1.__importDefault(require("lodash/castArray.js"));
|
|
6
|
+
const trim_js_1 = tslib_1.__importDefault(require("lodash/trim.js"));
|
|
7
|
+
const get_js_1 = tslib_1.__importDefault(require("lodash/get.js"));
|
|
8
|
+
const traverse_1 = tslib_1.__importDefault(require("@babel/traverse"));
|
|
9
9
|
const parser_1 = require("@babel/parser");
|
|
10
|
-
const t =
|
|
10
|
+
const t = tslib_1.__importStar(require("@babel/types"));
|
|
11
11
|
require("../../prototype.js");
|
|
12
12
|
// import { Checker } from './checker'
|
|
13
13
|
/** file:///D:/0/i18next-scanner/src/parser.js */
|