i18ntk 4.0.0 → 4.2.0
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/CHANGELOG.md +116 -29
- package/README.md +83 -18
- package/SECURITY.md +13 -5
- package/main/i18ntk-analyze.js +10 -20
- package/main/i18ntk-backup.js +227 -111
- package/main/i18ntk-init.js +153 -157
- package/main/i18ntk-scanner.js +9 -7
- package/main/i18ntk-setup.js +36 -13
- package/main/i18ntk-sizing.js +18 -50
- package/main/i18ntk-translate.js +169 -21
- package/main/i18ntk-usage.js +298 -154
- package/main/i18ntk-validate.js +49 -37
- package/main/manage/commands/AnalyzeCommand.js +7 -17
- package/main/manage/commands/CommandRouter.js +6 -6
- package/main/manage/commands/TranslateCommand.js +65 -56
- package/main/manage/commands/ValidateCommand.js +34 -26
- package/main/manage/index.js +11 -42
- package/main/manage/managers/InteractiveMenu.js +11 -40
- package/main/manage/services/InitService.js +114 -118
- package/main/manage/services/UsageService.js +244 -85
- package/package.json +55 -4
- package/runtime/enhanced.d.ts +5 -5
- package/runtime/enhanced.js +49 -25
- package/runtime/i18ntk.d.ts +30 -7
- package/runtime/index.d.ts +48 -19
- package/runtime/index.js +188 -97
- package/settings/settings-cli.js +115 -38
- package/settings/settings-manager.js +24 -6
- package/ui-locales/de.json +192 -11
- package/ui-locales/en.json +182 -8
- package/ui-locales/es.json +193 -12
- package/ui-locales/fr.json +189 -8
- package/ui-locales/ja.json +190 -8
- package/ui-locales/ru.json +191 -9
- package/ui-locales/zh.json +194 -9
- package/utils/cli-helper.js +8 -12
- package/utils/config-helper.js +1 -1
- package/utils/config-manager.js +8 -6
- package/utils/localized-confirm.js +55 -0
- package/utils/menu-layout.js +41 -0
- package/utils/report-writer.js +110 -0
- package/utils/security.js +15 -22
- package/utils/translate/api.js +31 -3
- package/utils/translate/placeholder.js +42 -1
- package/utils/translate/protection.js +17 -12
- package/utils/translate/report.js +3 -2
- package/utils/translate/safe-network.js +24 -4
- package/utils/usage-insights.js +435 -0
- package/utils/usage-source.js +50 -0
- package/utils/watch-locales.js +13 -9
package/main/i18ntk-translate.js
CHANGED
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
* --protection-file <path> JSON file with protected terms, keys, values, and patterns
|
|
25
25
|
* --create-protection-file Create the protection JSON file if it does not exist
|
|
26
26
|
* --no-protection Disable protected term/key/value handling for this run
|
|
27
|
-
* --concurrency <n> Max concurrent API requests (default:
|
|
27
|
+
* --concurrency <n> Max concurrent API requests (default: 12, Google max: 100)
|
|
28
28
|
* --batch-size <n> Number of text segments per batch (default: 50)
|
|
29
29
|
* --dry-run Preview mode without API calls
|
|
30
|
+
* --only-missing Translate only missing, marker, source-copy, or likely English target values
|
|
31
|
+
* --translate-all Re-translate every source string even when a target value already exists
|
|
30
32
|
* --report-file <path> Write report to file
|
|
31
33
|
* --report-stdout Print report to stdout
|
|
32
34
|
* --bom Output UTF-8 with BOM
|
|
@@ -61,9 +63,10 @@ const {
|
|
|
61
63
|
restoreText,
|
|
62
64
|
shouldPreserveWholeValue,
|
|
63
65
|
} = require('../utils/translate/protection');
|
|
64
|
-
const { translateBatch } = require('../utils/translate/api');
|
|
65
|
-
const { collectLeaves, setLeaf, deepClone } = require('../utils/translate/traverse');
|
|
66
|
+
const { translateBatch, DEFAULT_CONCURRENCY, clampProviderConcurrency } = require('../utils/translate/api');
|
|
67
|
+
const { collectLeaves, getLeaf, setLeaf, deepClone } = require('../utils/translate/traverse');
|
|
66
68
|
const { generateReport, writeReport, formatSummaryLine } = require('../utils/translate/report');
|
|
69
|
+
const { analyzeEnglishContent } = require('../utils/validation-risk');
|
|
67
70
|
const {
|
|
68
71
|
confirmGlobalChoice,
|
|
69
72
|
confirmPerKey,
|
|
@@ -101,10 +104,12 @@ function printHelp() {
|
|
|
101
104
|
' --protection-file <path> Protected terms/keys JSON file (default: i18ntk-auto-translate.json)',
|
|
102
105
|
' --create-protection-file Create the protection JSON file if missing',
|
|
103
106
|
' --no-protection Disable protected term/key/value handling',
|
|
104
|
-
' --concurrency <n> Max concurrent API requests (default:
|
|
107
|
+
' --concurrency <n> Max concurrent API requests (default: 12, Google max: 100)',
|
|
105
108
|
' --batch-size <n> Number of text segments per batch (default: 50)',
|
|
106
109
|
' --progress-interval <n> Progress update interval (default: 10)',
|
|
107
110
|
' --dry-run Preview: show what would be skipped',
|
|
111
|
+
' --only-missing Translate only missing, marker, source-copy, or likely English target values (default)',
|
|
112
|
+
' --translate-all Re-translate every source string even when target values already exist',
|
|
108
113
|
' --report-file <path> Write post-translation report to file',
|
|
109
114
|
' --report-stdout Print post-translation report to stdout',
|
|
110
115
|
' --bom Write output files with UTF-8 BOM',
|
|
@@ -141,10 +146,11 @@ function parseArgs(argv) {
|
|
|
141
146
|
protectionFile: DEFAULT_PROTECTION_FILE,
|
|
142
147
|
protectionEnabled: true,
|
|
143
148
|
createProtectionFile: false,
|
|
144
|
-
concurrency:
|
|
149
|
+
concurrency: DEFAULT_CONCURRENCY,
|
|
145
150
|
batchSize: 50,
|
|
146
151
|
progressInterval: 10,
|
|
147
152
|
dryRun: false,
|
|
153
|
+
onlyMissingOrEnglish: true,
|
|
148
154
|
reportFile: null,
|
|
149
155
|
reportStdout: false,
|
|
150
156
|
bom: false,
|
|
@@ -168,6 +174,8 @@ function parseArgs(argv) {
|
|
|
168
174
|
else if (arg === '--no-protection') { args.protectionEnabled = false; }
|
|
169
175
|
else if (arg === '--create-protection-file') { args.createProtectionFile = true; }
|
|
170
176
|
else if (arg === '--dry-run') { args.dryRun = true; }
|
|
177
|
+
else if (arg === '--translate-all' || arg === '--force-translate') { args.onlyMissingOrEnglish = false; }
|
|
178
|
+
else if (arg === '--only-missing' || arg === '--only-missing-or-english') { args.onlyMissingOrEnglish = true; }
|
|
171
179
|
else if (arg === '--report-stdout') { args.reportStdout = true; }
|
|
172
180
|
else if (arg === '--bom') { args.bom = true; }
|
|
173
181
|
else if (arg === '--source-dir' && i + 1 < argv.length) { args.sourceDir = argv[++i]; }
|
|
@@ -176,7 +184,7 @@ function parseArgs(argv) {
|
|
|
176
184
|
else if (arg === '--provider' && i + 1 < argv.length) { args.provider = argv[++i]; }
|
|
177
185
|
else if (arg === '--custom-regex' && i + 1 < argv.length) { args.customRegex.push(argv[++i]); }
|
|
178
186
|
else if (arg === '--protection-file' && i + 1 < argv.length) { args.protectionFile = argv[++i]; }
|
|
179
|
-
else if (arg === '--concurrency' && i + 1 < argv.length) { args.concurrency = parseInt(argv[++i], 10) ||
|
|
187
|
+
else if (arg === '--concurrency' && i + 1 < argv.length) { args.concurrency = parseInt(argv[++i], 10) || DEFAULT_CONCURRENCY; }
|
|
180
188
|
else if (arg === '--batch-size' && i + 1 < argv.length) { args.batchSize = parseInt(argv[++i], 10) || 50; }
|
|
181
189
|
else if (arg === '--progress-interval' && i + 1 < argv.length) { args.progressInterval = parseInt(argv[++i], 10) || 10; }
|
|
182
190
|
else if (arg === '--report-file' && i + 1 < argv.length) { args.reportFile = argv[++i]; }
|
|
@@ -202,13 +210,23 @@ function parseArgs(argv) {
|
|
|
202
210
|
process.exit(1);
|
|
203
211
|
}
|
|
204
212
|
|
|
205
|
-
args.concurrency =
|
|
213
|
+
args.concurrency = clampProviderConcurrency(args.concurrency, args.provider, DEFAULT_CONCURRENCY);
|
|
206
214
|
args.batchSize = clampInt(args.batchSize, 1, 10000, 50);
|
|
207
215
|
args.progressInterval = clampInt(args.progressInterval, 1, 10000, 10);
|
|
208
216
|
|
|
209
217
|
return args;
|
|
210
218
|
}
|
|
211
219
|
|
|
220
|
+
const UNTRANSLATED_MARKERS = new Set([
|
|
221
|
+
'',
|
|
222
|
+
'__not_translated__',
|
|
223
|
+
'not_translated',
|
|
224
|
+
'[translate]',
|
|
225
|
+
'[not translated]',
|
|
226
|
+
'todo',
|
|
227
|
+
'tbd',
|
|
228
|
+
]);
|
|
229
|
+
|
|
212
230
|
function clampInt(value, min, max, fallback) {
|
|
213
231
|
const num = parseInt(value, 10);
|
|
214
232
|
if (!Number.isInteger(num)) return fallback;
|
|
@@ -289,6 +307,95 @@ function classifyLeaves(leaves, customRegex) {
|
|
|
289
307
|
return { withPlaceholders, withoutPlaceholders };
|
|
290
308
|
}
|
|
291
309
|
|
|
310
|
+
function readExistingTargetData(targetPath) {
|
|
311
|
+
if (!SecurityUtils.safeExistsSync(targetPath, path.dirname(targetPath))) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
const raw = SecurityUtils.safeReadFileSync(targetPath, path.dirname(targetPath), 'utf-8').replace(/^\uFEFF/, '');
|
|
317
|
+
return JSON.parse(raw);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.warn(`Warning: Could not read existing target file "${targetPath}": ${error.message}`);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function isUntranslatedMarker(value) {
|
|
325
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
326
|
+
return UNTRANSLATED_MARKERS.has(normalized);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function isLikelyEnglish(value, args) {
|
|
330
|
+
const text = String(value || '').trim();
|
|
331
|
+
if (!text) return false;
|
|
332
|
+
const analysis = analyzeEnglishContent(text, {
|
|
333
|
+
allowedEnglishTerms: args.allowedEnglishTerms,
|
|
334
|
+
});
|
|
335
|
+
const threshold = Number.isFinite(Number(args.englishThresholdPercent))
|
|
336
|
+
? Number(args.englishThresholdPercent)
|
|
337
|
+
: 10;
|
|
338
|
+
return analysis.englishPercentage > threshold && analysis.englishWordCount >= 2;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function isBrokenTranslationValue(value) {
|
|
342
|
+
if (typeof value !== 'string') return false;
|
|
343
|
+
const text = value.trim();
|
|
344
|
+
if (!text) return false;
|
|
345
|
+
if (text.includes('\uFFFD')) return true;
|
|
346
|
+
|
|
347
|
+
const compact = text.replace(/[\s.,!;:()[\]{}'"`~_\-]/g, '');
|
|
348
|
+
if (compact.length >= 1 && /^\?+$/.test(compact)) return true;
|
|
349
|
+
if (/\?{3,}/.test(text)) return true;
|
|
350
|
+
|
|
351
|
+
const questionCount = (text.match(/\?/g) || []).length;
|
|
352
|
+
const visibleLength = Math.max(text.replace(/\s/g, '').length, 1);
|
|
353
|
+
if (questionCount >= 3 && questionCount / visibleLength >= 0.5) return true;
|
|
354
|
+
|
|
355
|
+
if (/[\u0080-\u009F]/.test(text) && /[ÃÂÐÑ]/.test(text)) return true;
|
|
356
|
+
|
|
357
|
+
const mojibakePatterns = [
|
|
358
|
+
/[ÃÂ][\u0080-\u00BF]/,
|
|
359
|
+
/Ð[\u0080-\u00BF]/,
|
|
360
|
+
/Ñ[\u0080-\u00BF]/,
|
|
361
|
+
/ã[‚ƒ€]/,
|
|
362
|
+
];
|
|
363
|
+
return mojibakePatterns.some((pattern) => pattern.test(text));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function shouldTranslateTargetValue(sourceValue, targetValue, args) {
|
|
367
|
+
if (targetValue === undefined || targetValue === null) return true;
|
|
368
|
+
if (typeof targetValue !== 'string') return true;
|
|
369
|
+
if (isUntranslatedMarker(targetValue)) return true;
|
|
370
|
+
if (isBrokenTranslationValue(targetValue)) return true;
|
|
371
|
+
if (targetValue.trim() === String(sourceValue ?? '').trim()) return true;
|
|
372
|
+
if (args.onlyMissingOrEnglish !== false && isLikelyEnglish(targetValue, args)) return true;
|
|
373
|
+
return args.onlyMissingOrEnglish === false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function planTargetAwareLeaves(sourceLeaves, targetData, args) {
|
|
377
|
+
if (!targetData || args.onlyMissingOrEnglish === false) {
|
|
378
|
+
return {
|
|
379
|
+
translatableLeaves: sourceLeaves,
|
|
380
|
+
existingLeaves: [],
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const translatableLeaves = [];
|
|
385
|
+
const existingLeaves = [];
|
|
386
|
+
|
|
387
|
+
for (const leaf of sourceLeaves) {
|
|
388
|
+
const targetValue = getLeaf(targetData, leaf.keyPath);
|
|
389
|
+
if (shouldTranslateTargetValue(leaf.value, targetValue, args)) {
|
|
390
|
+
translatableLeaves.push(leaf);
|
|
391
|
+
} else {
|
|
392
|
+
existingLeaves.push({ ...leaf, value: targetValue, skipReason: 'existing' });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return { translatableLeaves, existingLeaves };
|
|
397
|
+
}
|
|
398
|
+
|
|
292
399
|
async function resolvePlaceholderStrategy(args) {
|
|
293
400
|
const interactive = isInteractive({ noPrompt: args.noConfirm });
|
|
294
401
|
|
|
@@ -403,7 +510,11 @@ function prepareDirectBatch(toTranslate, customRegex, protection) {
|
|
|
403
510
|
|
|
404
511
|
async function runTranslation(maskedBatch, targetLang, options) {
|
|
405
512
|
const batchItems = maskedBatch.map((item) => ({ value: item.masked, keyPath: item.keyPath }));
|
|
406
|
-
const results = await translateBatchInChunks(batchItems, targetLang,
|
|
513
|
+
const results = await translateBatchInChunks(batchItems, targetLang, {
|
|
514
|
+
...options,
|
|
515
|
+
stageLabel: 'Translating strings',
|
|
516
|
+
progressUnit: 'strings',
|
|
517
|
+
});
|
|
407
518
|
return results;
|
|
408
519
|
}
|
|
409
520
|
|
|
@@ -428,6 +539,9 @@ async function translateBatchInChunks(batch, targetLang, options) {
|
|
|
428
539
|
total: batch.length,
|
|
429
540
|
chunkCompleted: info.completed,
|
|
430
541
|
chunkTotal: info.total,
|
|
542
|
+
keyPath: info.keyPath,
|
|
543
|
+
stage: options.stageLabel || 'Translating',
|
|
544
|
+
unit: options.progressUnit || 'items',
|
|
431
545
|
});
|
|
432
546
|
}
|
|
433
547
|
},
|
|
@@ -522,7 +636,11 @@ async function translatePreservedItems(items, targetLang, options, customRegex,
|
|
|
522
636
|
return plan;
|
|
523
637
|
});
|
|
524
638
|
|
|
525
|
-
const translatedSegments = await translateBatchInChunks(segmentJobs, targetLang,
|
|
639
|
+
const translatedSegments = await translateBatchInChunks(segmentJobs, targetLang, {
|
|
640
|
+
...options,
|
|
641
|
+
stageLabel: 'Translating placeholder-safe text segments',
|
|
642
|
+
progressUnit: 'segments',
|
|
643
|
+
});
|
|
526
644
|
|
|
527
645
|
return plans.map((plan) => {
|
|
528
646
|
const value = plan.segments.map((segment) => {
|
|
@@ -580,8 +698,8 @@ async function translateItems(toTranslate, targetLang, options, customRegex, pro
|
|
|
580
698
|
return finalResults;
|
|
581
699
|
}
|
|
582
700
|
|
|
583
|
-
function applyResults(sourceData, translatedResults, toTranslate, toSkip) {
|
|
584
|
-
const output = deepClone(sourceData);
|
|
701
|
+
function applyResults(sourceData, translatedResults, toTranslate, toSkip, targetData = null) {
|
|
702
|
+
const output = deepClone(targetData || sourceData);
|
|
585
703
|
|
|
586
704
|
for (let i = 0; i < toTranslate.length; i++) {
|
|
587
705
|
setLeaf(output, toTranslate[i].keyPath, translatedResults[i]);
|
|
@@ -594,6 +712,12 @@ function applyResults(sourceData, translatedResults, toTranslate, toSkip) {
|
|
|
594
712
|
return output;
|
|
595
713
|
}
|
|
596
714
|
|
|
715
|
+
function formatProgressKey(keyPath) {
|
|
716
|
+
if (!keyPath) return '';
|
|
717
|
+
const value = String(keyPath);
|
|
718
|
+
return value.length > 72 ? `${value.slice(0, 69)}...` : value;
|
|
719
|
+
}
|
|
720
|
+
|
|
597
721
|
function writeOutput(outputData, outputPath, bom) {
|
|
598
722
|
const resolvedOutputPath = path.resolve(process.cwd(), outputPath);
|
|
599
723
|
const dir = path.dirname(resolvedOutputPath);
|
|
@@ -628,14 +752,17 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
628
752
|
return { total: 0, translated: 0, skipped: 0, skippedKeys: [] };
|
|
629
753
|
}
|
|
630
754
|
|
|
755
|
+
const targetData = readExistingTargetData(targetPath);
|
|
756
|
+
const { translatableLeaves: candidateLeaves, existingLeaves } = planTargetAwareLeaves(leaves, targetData, args);
|
|
757
|
+
|
|
631
758
|
const protection = args.protection || loadProtectionConfig(args.protectionFile, {
|
|
632
759
|
enabled: args.protectionEnabled,
|
|
633
760
|
create: args.createProtectionFile,
|
|
634
761
|
});
|
|
635
|
-
const protectedLeaves =
|
|
762
|
+
const protectedLeaves = candidateLeaves
|
|
636
763
|
.filter((leaf) => shouldPreserveWholeValue(leaf.keyPath, leaf.value, protection))
|
|
637
764
|
.map((leaf) => ({ ...leaf, skipReason: 'protected' }));
|
|
638
|
-
const translatableLeaves =
|
|
765
|
+
const translatableLeaves = candidateLeaves.filter((leaf) => !shouldPreserveWholeValue(leaf.keyPath, leaf.value, protection));
|
|
639
766
|
const { withPlaceholders, withoutPlaceholders } = classifyLeaves(translatableLeaves, args.customRegex);
|
|
640
767
|
const { strategy, interactiveMode } = await resolvePlaceholderStrategy(args);
|
|
641
768
|
|
|
@@ -649,13 +776,17 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
649
776
|
skippedKeys,
|
|
650
777
|
placeholderProtected: 0,
|
|
651
778
|
protectedSkipped: protectedLeaves.length,
|
|
779
|
+
skippedExisting: existingLeaves.length,
|
|
652
780
|
dryRun: true,
|
|
653
781
|
};
|
|
654
782
|
}
|
|
655
783
|
|
|
656
784
|
if (args.dryRun) {
|
|
657
785
|
const protectedCount = strategy === 'send' ? 0 : withPlaceholders.length;
|
|
658
|
-
console.log(`[${fileName}] Dry-run: ${leaves.length} strings would be translated.`);
|
|
786
|
+
console.log(`[${fileName}] Dry-run: ${candidateLeaves.length} of ${leaves.length} strings would be translated.`);
|
|
787
|
+
if (existingLeaves.length > 0) {
|
|
788
|
+
console.log(`[${fileName}] Dry-run: ${existingLeaves.length} existing translated strings would be kept.`);
|
|
789
|
+
}
|
|
659
790
|
if (protectedLeaves.length > 0) {
|
|
660
791
|
console.log(`[${fileName}] Dry-run: ${protectedLeaves.length} protected keys/values would be copied unchanged.`);
|
|
661
792
|
}
|
|
@@ -667,11 +798,12 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
667
798
|
}
|
|
668
799
|
return {
|
|
669
800
|
total: leaves.length,
|
|
670
|
-
translated:
|
|
801
|
+
translated: candidateLeaves.length - protectedLeaves.length,
|
|
671
802
|
skipped: protectedLeaves.length,
|
|
672
803
|
skippedKeys: protectedLeaves,
|
|
673
804
|
placeholderProtected: protectedCount,
|
|
674
805
|
termProtected: hasProtectionRules(protection),
|
|
806
|
+
skippedExisting: existingLeaves.length,
|
|
675
807
|
dryRun: true,
|
|
676
808
|
};
|
|
677
809
|
}
|
|
@@ -679,8 +811,9 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
679
811
|
const decisions = await resolvePerKeyDecisions(withPlaceholders, interactiveMode);
|
|
680
812
|
const { toTranslate, toSkip } = buildTranslateList(withPlaceholders, withoutPlaceholders, strategy, decisions);
|
|
681
813
|
toSkip.push(...protectedLeaves);
|
|
814
|
+
toSkip.push(...existingLeaves);
|
|
682
815
|
const placeholderProtected = toTranslate.filter((leaf) => leaf.placeholderMode === 'preserve').length;
|
|
683
|
-
const placeholderSkipped = toSkip.filter((leaf) => leaf.skipReason !== 'protected').length;
|
|
816
|
+
const placeholderSkipped = toSkip.filter((leaf) => leaf.skipReason !== 'protected' && leaf.skipReason !== 'existing').length;
|
|
684
817
|
|
|
685
818
|
if (placeholderSkipped > 0) {
|
|
686
819
|
console.log(`[${fileName}] Skipping ${placeholderSkipped} keys with placeholders.`);
|
|
@@ -691,6 +824,9 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
691
824
|
if (protectedLeaves.length > 0) {
|
|
692
825
|
console.log(`[${fileName}] Copying ${protectedLeaves.length} protected keys/values unchanged.`);
|
|
693
826
|
}
|
|
827
|
+
if (existingLeaves.length > 0) {
|
|
828
|
+
console.log(`[${fileName}] Keeping ${existingLeaves.length} existing translated keys.`);
|
|
829
|
+
}
|
|
694
830
|
if (hasProtectionRules(protection)) {
|
|
695
831
|
console.log(`[${fileName}] Protecting terms from: ${protection.filePath}`);
|
|
696
832
|
}
|
|
@@ -708,7 +844,11 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
708
844
|
customFn: args.translateFn,
|
|
709
845
|
onProgress: (info) => {
|
|
710
846
|
if (info.completed % args.progressInterval === 0 || info.completed === info.total) {
|
|
711
|
-
|
|
847
|
+
const stage = info.stage || 'Translating';
|
|
848
|
+
const unit = info.unit || 'items';
|
|
849
|
+
const keyPath = formatProgressKey(info.keyPath);
|
|
850
|
+
const keySuffix = keyPath ? ` | ${keyPath}` : '';
|
|
851
|
+
process.stdout.write(`\r[${fileName}] ${stage}: ${info.completed}/${info.total} ${unit}${keySuffix}`);
|
|
712
852
|
}
|
|
713
853
|
},
|
|
714
854
|
onError: (err) => {
|
|
@@ -719,8 +859,10 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
719
859
|
let translatedResults;
|
|
720
860
|
try {
|
|
721
861
|
if (toTranslate.length > 0) {
|
|
862
|
+
console.log(`[${fileName}] Preparing translation plan for ${toTranslate.length} keys.`);
|
|
722
863
|
translatedResults = await translateItems(toTranslate, targetLang, translateOptions, args.customRegex, protection);
|
|
723
864
|
process.stdout.write('\n');
|
|
865
|
+
console.log(`[${fileName}] Applying translated values.`);
|
|
724
866
|
} else {
|
|
725
867
|
translatedResults = [];
|
|
726
868
|
}
|
|
@@ -728,7 +870,8 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
728
870
|
cleanupPlaceholderManifest(manifestPath);
|
|
729
871
|
}
|
|
730
872
|
|
|
731
|
-
const output = applyResults(sourceData, translatedResults, toTranslate, toSkip);
|
|
873
|
+
const output = applyResults(sourceData, translatedResults, toTranslate, toSkip, targetData);
|
|
874
|
+
console.log(`[${fileName}] Writing output.`);
|
|
732
875
|
writeOutput(output, targetPath, args.bom);
|
|
733
876
|
|
|
734
877
|
console.log(`[${fileName}] Written: ${targetPath}`);
|
|
@@ -736,10 +879,11 @@ async function processFile(sourcePath, targetLang, args) {
|
|
|
736
879
|
return {
|
|
737
880
|
total: leaves.length,
|
|
738
881
|
translated: translatedResults.length,
|
|
739
|
-
skipped: toSkip.length,
|
|
740
|
-
skippedKeys: toSkip,
|
|
882
|
+
skipped: toSkip.filter((leaf) => leaf.skipReason !== 'existing').length,
|
|
883
|
+
skippedKeys: toSkip.filter((leaf) => leaf.skipReason !== 'existing'),
|
|
741
884
|
placeholderProtected,
|
|
742
885
|
protectedSkipped: protectedLeaves.length,
|
|
886
|
+
skippedExisting: existingLeaves.length,
|
|
743
887
|
};
|
|
744
888
|
}
|
|
745
889
|
|
|
@@ -787,6 +931,7 @@ async function run(args) {
|
|
|
787
931
|
let grandSkipped = 0;
|
|
788
932
|
let grandPlaceholderProtected = 0;
|
|
789
933
|
let grandProtectedSkipped = 0;
|
|
934
|
+
let grandSkippedExisting = 0;
|
|
790
935
|
|
|
791
936
|
for (const srcPath of sourceFiles) {
|
|
792
937
|
const result = await processFile(srcPath, args.targetLang, args);
|
|
@@ -796,6 +941,7 @@ async function run(args) {
|
|
|
796
941
|
grandSkipped += result.skipped;
|
|
797
942
|
grandPlaceholderProtected += result.placeholderProtected || 0;
|
|
798
943
|
grandProtectedSkipped += result.protectedSkipped || 0;
|
|
944
|
+
grandSkippedExisting += result.skippedExisting || 0;
|
|
799
945
|
if (result.skippedKeys && result.skippedKeys.length > 0) {
|
|
800
946
|
allSkippedKeys.push(...result.skippedKeys);
|
|
801
947
|
}
|
|
@@ -803,7 +949,7 @@ async function run(args) {
|
|
|
803
949
|
}
|
|
804
950
|
|
|
805
951
|
console.log('');
|
|
806
|
-
console.log(formatSummaryLine(grandSkipped, grandTranslated, grandTotal, grandPlaceholderProtected, grandProtectedSkipped));
|
|
952
|
+
console.log(formatSummaryLine(grandSkipped, grandTranslated, grandTotal, grandPlaceholderProtected, grandProtectedSkipped, grandSkippedExisting));
|
|
807
953
|
|
|
808
954
|
if (allSkippedKeys.length > 0 || args.reportFile || args.reportStdout) {
|
|
809
955
|
const report = generateReport(allSkippedKeys, grandTranslated, grandTotal, {
|
|
@@ -834,6 +980,7 @@ async function run(args) {
|
|
|
834
980
|
skipped: grandSkipped,
|
|
835
981
|
placeholderProtected: grandPlaceholderProtected,
|
|
836
982
|
protectedSkipped: grandProtectedSkipped,
|
|
983
|
+
skippedExisting: grandSkippedExisting,
|
|
837
984
|
};
|
|
838
985
|
}
|
|
839
986
|
|
|
@@ -852,6 +999,7 @@ if (require.main === module) {
|
|
|
852
999
|
module.exports = {
|
|
853
1000
|
parseArgs,
|
|
854
1001
|
resolveSourceFiles,
|
|
1002
|
+
isBrokenTranslationValue,
|
|
855
1003
|
processFile,
|
|
856
1004
|
run,
|
|
857
1005
|
};
|