xshell 0.0.29 → 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.
@@ -2,22 +2,22 @@
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 = (0, tslib_1.__importDefault)(require("i18next-scanner"));
6
- const upath_1 = (0, tslib_1.__importDefault)(require("upath"));
7
- const ejs_1 = (0, tslib_1.__importDefault)(require("ejs"));
8
- const vinyl_fs_1 = (0, tslib_1.__importDefault)(require("vinyl-fs"));
9
- const map_stream_1 = (0, tslib_1.__importDefault)(require("map-stream"));
10
- const gulp_sort_1 = (0, tslib_1.__importDefault)(require("gulp-sort"));
11
- const ora_1 = (0, tslib_1.__importDefault)(require("ora"));
12
- const cli_truncate_1 = (0, tslib_1.__importDefault)(require("cli-truncate"));
13
- const vinyl_1 = (0, tslib_1.__importDefault)(require("vinyl"));
14
- const through2_1 = (0, tslib_1.__importDefault)(require("through2"));
15
- const cli_table3_1 = (0, tslib_1.__importDefault)(require("cli-table3"));
16
- const index_1 = require("../index");
17
- const rwdict_1 = (0, tslib_1.__importDefault)(require("../rwdict"));
18
- const utils_1 = require("../utils");
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"));
19
15
  require("../../prototype.js");
20
- const parser_1 = require("./parser");
16
+ const utils_js_1 = require("../../utils.js");
17
+ const index_js_1 = require("../index.js");
18
+ const rwdict_js_1 = tslib_1.__importDefault(require("../rwdict.js"));
19
+ const utils_js_2 = require("../utils.js");
20
+ const parser_js_1 = require("./parser.js");
21
21
  /** 默认 i18next 扫描配置 */
22
22
  const DEFAULT_CONFIG = {
23
23
  debug: false,
@@ -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');
@@ -120,15 +120,15 @@ function scanner(rootdir = upath_1.default.normalize(process.cwd()), config = {}
120
120
  lineEnding: '\n'
121
121
  }
122
122
  };
123
- let dict = new rwdict_1.default();
123
+ let dict = new rwdict_js_1.default();
124
124
  for (const fp_dict of config.dict)
125
- dict.merge((0, utils_1.try_require)(upath_1.default.resolve(output, fp_dict)), { print: false, overwrite: true });
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 errors = [];
128
+ let error_handlers = [];
129
129
  // 所有语言的扫描统计信息
130
130
  let stats = {};
131
- for (const language of index_1.LANGUAGES)
131
+ for (const language of index_js_1.LANGUAGES)
132
132
  stats[language] = {
133
133
  translateds: new Set(),
134
134
  untranslateds: new Set()
@@ -140,7 +140,7 @@ function scanner(rootdir = upath_1.default.normalize(process.cwd()), config = {}
140
140
  if (!key)
141
141
  key = context ? `${text}_${context}` : text;
142
142
  if (!language) {
143
- for (const language of index_1.LANGUAGES)
143
+ for (const language of index_js_1.LANGUAGES)
144
144
  on_scanned(text, { language, key, count, context });
145
145
  return;
146
146
  }
@@ -168,163 +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
- // ------------ scan by file
172
- vinyl_fs_1.default
173
- .src(config.input, { cwd: rootdir, sync: false })
174
- // 每个文件扫描前,统计文件数量
175
- .pipe((0, map_stream_1.default)((file, cb, count) => {
176
- // 支持 `// @i18n-noscan` 忽略扫描
177
- if (/\/\/\s*@i18n-noscan\s/.test(file.contents.toString()))
178
- return cb();
179
- c_files++;
180
- cb(null, file);
181
- }))
182
- // 对文件进行排序,保证词条有一定的顺序
183
- .pipe((0, gulp_sort_1.default)())
184
- // 分析代码提取词条
185
- .pipe(i18next_scanner_1.default.createStream(config, function transform(file, encoding, callback) {
186
- const { parser } = this;
187
- const ext = upath_1.default.extname(file.path);
188
- // 只扫描源码文件
189
- if (!VALID_EXTENTIONS.has(ext)) {
190
- callback();
191
- return;
192
- }
193
- c_scanneds++;
194
- const percent = Math.round((100 * c_scanneds) / c_files);
195
- const text = `Scanning (${percent}%): ${file.path.green}`;
196
- spinner.text = (0, cli_truncate_1.default)(text, process.stdout.columns - 5, { position: 'middle', });
197
- let code = file.contents.toString();
198
- if (ext === '.ejs')
199
- code = ejs_1.default.compile(code, { filename: file.path, client: true, legacyInclude: true }).toString();
200
- // --- 添加代码中扫描到的 i18n.t('key') 中的 key parser
201
- // parser.parseFuncFromString 使用 esprima 来解析代码,esprima 仍然不支持 optional chaining !!
202
- parser.parseFuncFromString(code.replace(/\?\.\[/g, '[').replace(/\?\.\(/g, '(').replace(/\?\./g, '.'), on_scanned);
203
- // --- 添加代码中扫描到的 Trans 组件中的 key 到 parser
204
- if (ext === '.jsx' || ext === '.tsx') {
205
- // parser.parseTransFromString 使用 acorn 解析代码,不支持 TypeScript,添加 parser.parseTransFromStringByBabel
206
- (0, parser_1.mix_parse_trans_from_string_by_babel)(parser);
207
- parser.parseTransFromStringByBabel(code, { filepath: file.path }, on_scanned, (error) => { errors.push(error); });
208
- }
209
- setTimeout(callback, 0);
210
- }))
211
- // 创建词条文件
212
- .pipe(through2_1.default.obj(
213
- /** i18n-scanner 会把扫描结果以每个语言一个文件的形式提供,这里解析扫描结果
214
- * file: 翻译 resource 文件,其中 file.contents 包含翻译的扫描结果
215
- */
216
- function write(file, encoding, cb) { cb(); },
217
- /** 生成 stats.json, unmarkeds.md; 打印 untranslated / unmarkeds */
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
- const en_untranslateds = stats.en.untranslateds;
286
- if (en_untranslateds.size) {
287
- console.log('\n未翻译的英文词条:'.yellow);
288
- Array.from(en_untranslateds).slice(0, 10).forEach(untranslated => {
289
- console.log(untranslated);
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
+ },
290
250
  });
291
- if (en_untranslateds.size > 10) {
292
- console.log('...');
293
- console.log(`--- 共 ${en_untranslateds.size} 个未翻译的英文词条 ---`);
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
- else
297
- console.log('\n所有词条都至少含有英文翻译.'.green);
298
- // ------------ 生成 untranslateds.json (扫描到词条还没有英文翻译)
299
- const fp_untranslateds = upath_1.default.resolve(config.output, 'untranslateds.json');
300
- let untranslateds = {};
301
- for (const key of stats.en.untranslateds) {
302
- let item = { ...dict.get(key) };
303
- item.en || (item.en = '');
304
- item.ja || (item.ja = '');
305
- item.ko || (item.ko = '');
306
- untranslateds[key] = item;
307
- }
308
- this.push(new_vinyl_file(fp_untranslateds, untranslateds));
309
- // ------------ 写入 dict.json
310
- const fp_dict_new = upath_1.default.resolve(output, 'dict.json');
311
- this.push(new_vinyl_file(fp_dict_new, dict.to_json(true) + '\n'));
312
- console.log(`\n\n${'请手动补全未翻译的词条: '.yellow}${fp_untranslateds.underline.blue}\n` +
313
- `${'请检查新生成的词典文件: '.yellow}${fp_dict_new.underline.blue}\n` +
314
- '\n' +
315
- '补全 untranslateds.json 后需要重新运行扫描,会根据 untranslateds.json 更新 dict.json\n'.yellow +
316
- '最后 dict.json 所包含的词条会被打包进 js, 通过 new I18N(<dict.json>) 或 i18n.init(<dict.json>) 加载\n\n'.yellow +
317
- `${'详细文档请查看: '.yellow}${'https://github.com/ShenHongFei/xshell/tree/master/i18n'.blue.underline}`);
318
- cb();
319
- }))
320
- // 写入词条文件
321
- .pipe(vinyl_fs_1.default.dest(rootdir))
322
- .on('end', () => {
323
- for (const error_handler of errors)
324
- error_handler();
325
- if (!errors.length)
326
- return;
327
- console.log(`以上错误可能是由不规范的词条标记导致,标记规范可见:\n${'https://www.i18next.com/translation-function/essentials'.blue.underline}`);
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))}`))
286
+ }
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
+ });
328
337
  });
329
338
  }
330
339
  exports.scanner = scanner;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import i18n_scanner from 'i18next-scanner'\nimport path from 'upath'\nimport ejs from 'ejs'\nimport vfs from 'vinyl-fs'\nimport map from 'map-stream'\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'\nimport type { Transform } from 'stream'\n\nimport { LANGUAGES } from '../index'\nimport type { Language } from '../index'\nimport Dict from '../rwdict'\nimport type { _Dict } from '../dict'\nimport { try_require } from '../utils'\n\nimport '../../prototype.js'\n\nimport { mix_parse_trans_from_string_by_babel } from './parser'\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( (file: Vinyl, cb: Function, count: number) => {\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( (100 * c_scanneds) / c_files )\n const text = `Scanning (${percent}%): ${file.path.green}`\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 Array.from(en_untranslateds).slice(0, 10).forEach( untranslated => {\n console.log(untranslated)\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( new_vinyl_file( fp_dict_new, dict.to_json(true) + '\\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":"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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_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"]}