lingui-po-translate 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/core-definitions.js +21 -0
- package/dist/core/core-util.js +65 -0
- package/dist/core/invoke-translation-service.js +82 -0
- package/dist/core/translate-cli.js +113 -0
- package/dist/core/translate-core.js +230 -0
- package/dist/core/tset-ops.js +110 -0
- package/dist/file-formats/common/format-cache.js +72 -0
- package/dist/file-formats/common/managed-json.js +37 -0
- package/dist/file-formats/common/managed-utf8.js +69 -0
- package/dist/file-formats/common/parse-utils.js +14 -0
- package/dist/file-formats/csv/csv.js +64 -0
- package/dist/file-formats/file-format-definitions.js +43 -0
- package/dist/file-formats/flat-json/flat-json.js +29 -0
- package/dist/file-formats/flutter-arb/flutter-arb.js +62 -0
- package/dist/file-formats/ios-strings/ios-read.js +72 -0
- package/dist/file-formats/ios-strings/ios-strings.js +22 -0
- package/dist/file-formats/ios-strings/ios-write.js +37 -0
- package/dist/file-formats/nested-json/nested-json.js +66 -0
- package/dist/file-formats/po/comment-parser.js +68 -0
- package/dist/file-formats/po/po-files.js +78 -0
- package/dist/file-formats/po/po-ops.js +111 -0
- package/dist/file-formats/xml/xml-generic.js +81 -0
- package/dist/file-formats/xml/xml-read.js +66 -0
- package/dist/file-formats/xml/xml-traverse.js +160 -0
- package/dist/file-formats/xml/xml-write.js +62 -0
- package/dist/file-formats/yaml/yaml-generic.js +113 -0
- package/dist/file-formats/yaml/yaml-manipulation.js +132 -0
- package/dist/file-formats/yaml/yaml-parse.js +38 -0
- package/dist/index.js +66 -0
- package/dist/matchers/i18next.js +11 -0
- package/dist/matchers/icu.js +23 -0
- package/dist/matchers/matcher-definitions.js +46 -0
- package/dist/matchers/sprintf.js +11 -0
- package/dist/services/azure-translator.js +52 -0
- package/dist/services/google-translate.js +55 -0
- package/dist/services/key-as-translation.js +18 -0
- package/dist/services/manual.js +29 -0
- package/dist/services/openai-translate.js +91 -0
- package/dist/services/service-definitions.js +57 -0
- package/dist/services/sync-without-translate.js +18 -0
- package/dist/services/typechat.js +214 -0
- package/dist/util/extract-version.js +20 -0
- package/dist/util/flatten.js +40 -0
- package/dist/util/util.js +87 -0
- package/package.json +2 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseSourceOverride = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Parse source override string into a map
|
|
6
|
+
*/
|
|
7
|
+
function parseSourceOverride(sourceOverride) {
|
|
8
|
+
const map = new Map();
|
|
9
|
+
if (!sourceOverride) {
|
|
10
|
+
return map;
|
|
11
|
+
}
|
|
12
|
+
const pairs = sourceOverride.split(",");
|
|
13
|
+
for (const pair of pairs) {
|
|
14
|
+
const [target, source] = pair.split(":").map((s) => s.trim());
|
|
15
|
+
if (target && source) {
|
|
16
|
+
map.set(target, source);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
21
|
+
exports.parseSourceOverride = parseSourceOverride;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getElementPosition = exports.insertAt = exports.readTFileCore = exports.writeTFileCore = exports.logCoreResults = void 0;
|
|
4
|
+
const util_1 = require("../util/util");
|
|
5
|
+
const file_format_definitions_1 = require("../file-formats/file-format-definitions");
|
|
6
|
+
function logCoreResults(args, results) {
|
|
7
|
+
var _a, _b;
|
|
8
|
+
const changeSet = results.changeSet;
|
|
9
|
+
const countAdded = changeSet.added.size;
|
|
10
|
+
if (countAdded) {
|
|
11
|
+
console.info(`Add ${countAdded} new translations`);
|
|
12
|
+
}
|
|
13
|
+
const countUpdated = changeSet.updated.size;
|
|
14
|
+
if (countUpdated) {
|
|
15
|
+
console.info(`Update ${countUpdated} existing translations`);
|
|
16
|
+
}
|
|
17
|
+
const countDeleted = (_b = (_a = changeSet.deleted) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0;
|
|
18
|
+
if (countDeleted) {
|
|
19
|
+
console.info(`Delete ${countDeleted} stale translations`);
|
|
20
|
+
}
|
|
21
|
+
const countSkipped = changeSet.skipped.size;
|
|
22
|
+
if (countSkipped) {
|
|
23
|
+
console.info(`Warning: Skipped ${countSkipped} translations`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.logCoreResults = logCoreResults;
|
|
27
|
+
async function writeTFileCore(args) {
|
|
28
|
+
args.tSet.forEach((value, key) => {
|
|
29
|
+
if (value === null) {
|
|
30
|
+
args.tSet.set(key, "");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const module = await (0, file_format_definitions_1.instantiateTFileFormat)(args.format);
|
|
34
|
+
module.writeTFile(args);
|
|
35
|
+
}
|
|
36
|
+
exports.writeTFileCore = writeTFileCore;
|
|
37
|
+
async function readTFileCore(fileFormat, args) {
|
|
38
|
+
const module = await (0, file_format_definitions_1.instantiateTFileFormat)(fileFormat);
|
|
39
|
+
const rawTSet = await module.readTFile(args);
|
|
40
|
+
const tSet = new Map();
|
|
41
|
+
rawTSet.forEach((value, key) => {
|
|
42
|
+
const replacedKey = key;
|
|
43
|
+
tSet.set(replacedKey, value);
|
|
44
|
+
});
|
|
45
|
+
return tSet;
|
|
46
|
+
}
|
|
47
|
+
exports.readTFileCore = readTFileCore;
|
|
48
|
+
function insertAt(array, index, ...elementsArray) {
|
|
49
|
+
if (index >= array.length) {
|
|
50
|
+
array.push(...elementsArray);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
array.splice(index, 0, ...elementsArray);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.insertAt = insertAt;
|
|
57
|
+
function getElementPosition(args) {
|
|
58
|
+
for (let idx = 0; idx < args.array.length; idx++) {
|
|
59
|
+
if (args.array[idx] === args.element) {
|
|
60
|
+
return idx;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
(0, util_1.logFatal)(`Did not find element ${args.element} in ${args.array}`);
|
|
64
|
+
}
|
|
65
|
+
exports.getElementPosition = getElementPosition;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.invokeTranslationService = void 0;
|
|
4
|
+
const matcher_definitions_1 = require("../matchers/matcher-definitions");
|
|
5
|
+
const service_definitions_1 = require("../services/service-definitions");
|
|
6
|
+
const po_files_1 = require("../file-formats/po/po-files");
|
|
7
|
+
async function invokeTranslationService(serviceInputs, args) {
|
|
8
|
+
if (args.prompt && !["openai", "typechat"].includes(args.service)) {
|
|
9
|
+
console.warn(`Warning: The '--prompt' parameter is only supported by 'openai' and 'typechat' services. Your prompt will be ignored when using '${args.service}'.`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Some translation services throw errors if they see empty strings.
|
|
13
|
+
* Therefore, we bypass empty strings without changing them.
|
|
14
|
+
*/
|
|
15
|
+
const rawInputs = [];
|
|
16
|
+
const results = new Map();
|
|
17
|
+
serviceInputs.forEach((value, key) => {
|
|
18
|
+
if (args.service != 'key-as-translation' && (!value || !value.trim().length)) {
|
|
19
|
+
results.set(key, value);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
rawInputs.push({
|
|
23
|
+
key,
|
|
24
|
+
value: value !== null && value !== void 0 ? value : "",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (results.size) {
|
|
29
|
+
console.info(`Bypass ${results.size} strings because they are empty...`);
|
|
30
|
+
}
|
|
31
|
+
let translateResults = [];
|
|
32
|
+
if (rawInputs.length) {
|
|
33
|
+
translateResults = await runTranslationService(rawInputs, args);
|
|
34
|
+
}
|
|
35
|
+
translateResults.forEach((tResult) => {
|
|
36
|
+
results.set(tResult.key, tResult.translated);
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
inputs: serviceInputs,
|
|
40
|
+
results,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
exports.invokeTranslationService = invokeTranslationService;
|
|
44
|
+
async function runTranslationService(rawInputs, args) {
|
|
45
|
+
const matcher = (0, matcher_definitions_1.instantiateTMatcher)(args.matcher);
|
|
46
|
+
const replacers = new Map();
|
|
47
|
+
rawInputs.forEach((rawString) => {
|
|
48
|
+
const replacer = (0, matcher_definitions_1.replaceInterpolations)(rawString.value, matcher);
|
|
49
|
+
replacers.set(rawString.key, replacer);
|
|
50
|
+
});
|
|
51
|
+
// Get parsed comments for context
|
|
52
|
+
const parsedComments = (0, po_files_1.getParsedComments)(args.srcFile);
|
|
53
|
+
const replacedInputs = rawInputs.map((rawString) => {
|
|
54
|
+
const parsed = parsedComments.get(rawString.key);
|
|
55
|
+
return {
|
|
56
|
+
key: rawString.key,
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
58
|
+
value: replacers.get(rawString.key).clean,
|
|
59
|
+
context: parsed === null || parsed === void 0 ? void 0 : parsed.context,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
const serviceArgs = {
|
|
63
|
+
strings: replacedInputs,
|
|
64
|
+
srcLng: args.srcLng,
|
|
65
|
+
targetLng: args.targetLng,
|
|
66
|
+
serviceConfig: args.serviceConfig,
|
|
67
|
+
prompt: args.prompt,
|
|
68
|
+
baseUrl: args.baseUrl,
|
|
69
|
+
};
|
|
70
|
+
console.info(`Invoke '${args.service}' from '${args.srcLng}' to '${args.targetLng}' with ${serviceArgs.strings.length} inputs...`);
|
|
71
|
+
const translationService = await (0, service_definitions_1.instantiateTService)(args.service);
|
|
72
|
+
const rawResults = await translationService.translateStrings(serviceArgs);
|
|
73
|
+
return rawResults.map((rawResult) => {
|
|
74
|
+
const cleanResult = (0, matcher_definitions_1.reInsertInterpolations)(rawResult.translated,
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
76
|
+
replacers.get(rawResult.key).replacements);
|
|
77
|
+
return {
|
|
78
|
+
key: rawResult.key,
|
|
79
|
+
translated: cleanResult,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.translateCli = exports.formatCliOptions = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const translate_core_1 = require("./translate-core");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const core_definitions_1 = require("./core-definitions");
|
|
8
|
+
const tset_ops_1 = require("./tset-ops");
|
|
9
|
+
const util_1 = require("../util/util");
|
|
10
|
+
const core_util_1 = require("./core-util");
|
|
11
|
+
const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
12
|
+
const file_format_definitions_1 = require("../file-formats/file-format-definitions");
|
|
13
|
+
const service_definitions_1 = require("../services/service-definitions");
|
|
14
|
+
const matcher_definitions_1 = require("../matchers/matcher-definitions");
|
|
15
|
+
async function resolveOldTarget(args, targetFileFormat) {
|
|
16
|
+
const targetPath = path_1.default.resolve(args.targetFile);
|
|
17
|
+
const targetDir = path_1.default.dirname(targetPath);
|
|
18
|
+
(0, util_1.checkDir)(targetDir, { errorHint: "Target path" });
|
|
19
|
+
if ((0, fs_1.existsSync)(targetPath)) {
|
|
20
|
+
return await (0, core_util_1.readTFileCore)(targetFileFormat, {
|
|
21
|
+
path: args.targetFile,
|
|
22
|
+
lng: args.targetLng,
|
|
23
|
+
format: targetFileFormat,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function formatCliOptions(options) {
|
|
31
|
+
return `${options.map((o) => `"${o}"`).join(", ")}`;
|
|
32
|
+
}
|
|
33
|
+
exports.formatCliOptions = formatCliOptions;
|
|
34
|
+
async function translateCli(cliArgs) {
|
|
35
|
+
var _a, _b, _c;
|
|
36
|
+
checkForEmptyStringOptions(cliArgs);
|
|
37
|
+
const fileFormats = (0, file_format_definitions_1.getTFileFormatList)();
|
|
38
|
+
const services = (0, service_definitions_1.getTServiceList)();
|
|
39
|
+
const matchers = (0, matcher_definitions_1.getTMatcherList)();
|
|
40
|
+
if (!services.includes(cliArgs.service)) {
|
|
41
|
+
(0, util_1.logFatal)(`Unknown service "${cliArgs.service}". Available services: ${formatCliOptions(services)}`);
|
|
42
|
+
}
|
|
43
|
+
if (!matchers.includes(cliArgs.matcher)) {
|
|
44
|
+
(0, util_1.logFatal)(`Unknown matcher "${cliArgs.matcher}". Available matchers: ${formatCliOptions(matchers)}`);
|
|
45
|
+
}
|
|
46
|
+
if (!fileFormats.includes(cliArgs.srcFormat)) {
|
|
47
|
+
(0, util_1.logFatal)(`Unknown source format "${cliArgs.srcFormat}". Available formats: ${formatCliOptions(fileFormats)}`);
|
|
48
|
+
}
|
|
49
|
+
const srcFileFormat = cliArgs.srcFormat;
|
|
50
|
+
if (!fileFormats.includes(cliArgs.targetFormat)) {
|
|
51
|
+
(0, util_1.logFatal)(`Unknown target format "${cliArgs.targetFormat}". Available formats: ${formatCliOptions(fileFormats)}`);
|
|
52
|
+
}
|
|
53
|
+
const targetFileFormat = cliArgs.targetFormat;
|
|
54
|
+
(0, util_1.checkNotDir)(cliArgs.srcFile, { errorHint: "srcFile" });
|
|
55
|
+
const src = await (0, core_util_1.readTFileCore)(srcFileFormat, {
|
|
56
|
+
path: cliArgs.srcFile,
|
|
57
|
+
lng: cliArgs.srcLng,
|
|
58
|
+
format: srcFileFormat,
|
|
59
|
+
});
|
|
60
|
+
if (!src.size) {
|
|
61
|
+
(0, util_1.logFatal)(`${(0, util_1.getDebugPath)(cliArgs.srcFile)} does not contain any translatable content`);
|
|
62
|
+
}
|
|
63
|
+
const oldTarget = await resolveOldTarget(cliArgs, targetFileFormat);
|
|
64
|
+
const coreArgs = {
|
|
65
|
+
src,
|
|
66
|
+
srcLng: cliArgs.srcLng,
|
|
67
|
+
srcFile: cliArgs.srcFile,
|
|
68
|
+
oldTarget,
|
|
69
|
+
targetLng: cliArgs.targetLng,
|
|
70
|
+
service: cliArgs.service,
|
|
71
|
+
serviceConfig: (_a = cliArgs.serviceConfig) !== null && _a !== void 0 ? _a : null,
|
|
72
|
+
matcher: cliArgs.matcher,
|
|
73
|
+
prompt: (_b = cliArgs.prompt) !== null && _b !== void 0 ? _b : "",
|
|
74
|
+
sourceOverride: (0, core_definitions_1.parseSourceOverride)(cliArgs.sourceOverride),
|
|
75
|
+
baseUrl: (_c = cliArgs.baseUrl) !== null && _c !== void 0 ? _c : null,
|
|
76
|
+
};
|
|
77
|
+
const result = await (0, translate_core_1.translateCore)(coreArgs);
|
|
78
|
+
const flushTarget = !oldTarget || !(0, tset_ops_1.areEqual)(oldTarget, result.newTarget);
|
|
79
|
+
if (flushTarget) {
|
|
80
|
+
console.info(`Write target ${(0, util_1.getDebugPath)(cliArgs.targetFile)}`);
|
|
81
|
+
await (0, core_util_1.writeTFileCore)({
|
|
82
|
+
path: cliArgs.targetFile,
|
|
83
|
+
tSet: result.newTarget,
|
|
84
|
+
lng: cliArgs.targetLng,
|
|
85
|
+
changeSet: result.changeSet,
|
|
86
|
+
format: targetFileFormat,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (!flushTarget) {
|
|
90
|
+
console.info(`Target is up-to-date: '${cliArgs.targetFile}'`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.translateCli = translateCli;
|
|
94
|
+
// function parseBooleanOption(rawOption: string, optionKey: string): boolean {
|
|
95
|
+
// const option = rawOption.trim().toLowerCase();
|
|
96
|
+
// if (option === "true") {
|
|
97
|
+
// return true;
|
|
98
|
+
// } else if (option === "false") {
|
|
99
|
+
// return false;
|
|
100
|
+
// } else {
|
|
101
|
+
// logFatal(
|
|
102
|
+
// `Invalid option '--${optionKey}=${rawOption}'. Should be either true or false.`
|
|
103
|
+
// );
|
|
104
|
+
// }
|
|
105
|
+
// }
|
|
106
|
+
function checkForEmptyStringOptions(args) {
|
|
107
|
+
Object.keys(args).forEach((key) => {
|
|
108
|
+
const arg = args[key];
|
|
109
|
+
if (typeof arg === "string" && (arg === "" || !arg.trim().length)) {
|
|
110
|
+
(0, util_1.logFatal)(`option '--${key}' is empty -> Either omit it or provide a value`);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.translateCore = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const tset_ops_1 = require("./tset-ops");
|
|
6
|
+
const core_util_1 = require("./core-util");
|
|
7
|
+
const util_1 = require("../util/util");
|
|
8
|
+
const invoke_translation_service_1 = require("./invoke-translation-service");
|
|
9
|
+
const po_files_1 = require("../file-formats/po/po-files");
|
|
10
|
+
const comment_parser_1 = require("../file-formats/po/comment-parser");
|
|
11
|
+
const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
12
|
+
/**
|
|
13
|
+
* Filter entries based on @manual marking
|
|
14
|
+
*/
|
|
15
|
+
function filterByManualMarking(inputs, args) {
|
|
16
|
+
var _a;
|
|
17
|
+
const parsedComments = (0, po_files_1.getParsedComments)(args.srcFile);
|
|
18
|
+
const overrideSourceLng = (_a = args.sourceOverride) === null || _a === void 0 ? void 0 : _a.get(args.targetLng);
|
|
19
|
+
const toTranslate = new Map();
|
|
20
|
+
const toSkip = new Map();
|
|
21
|
+
const toCopyOriginal = new Map();
|
|
22
|
+
const toTranslateFromOverride = new Map();
|
|
23
|
+
inputs.forEach((value, key) => {
|
|
24
|
+
const parsed = parsedComments.get(key);
|
|
25
|
+
if (!parsed || !(0, comment_parser_1.hasManualMarking)(parsed)) {
|
|
26
|
+
// No @manual marking, translate normally
|
|
27
|
+
toTranslate.set(key, value);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if ((0, comment_parser_1.shouldSkipForManual)(parsed, args.targetLng)) {
|
|
31
|
+
// Target language is in @manual list, skip
|
|
32
|
+
toSkip.set(key, value);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Has @manual but target not in list
|
|
36
|
+
if (overrideSourceLng) {
|
|
37
|
+
// Has source override, translate from override source
|
|
38
|
+
toTranslateFromOverride.set(key, value);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// No override, copy original text
|
|
42
|
+
toCopyOriginal.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return { toTranslate, toSkip, toCopyOriginal, toTranslateFromOverride };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Infer override source file path from current source file path
|
|
49
|
+
* e.g., /path/to/en.po with override zh-Hans -> /path/to/zh-Hans.po
|
|
50
|
+
*/
|
|
51
|
+
function inferOverrideSourcePath(srcFile, overrideLng) {
|
|
52
|
+
const dir = path_1.default.dirname(srcFile);
|
|
53
|
+
const ext = path_1.default.extname(srcFile);
|
|
54
|
+
return path_1.default.join(dir, `${overrideLng}${ext}`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read translations from override source file
|
|
58
|
+
*/
|
|
59
|
+
async function readOverrideSource(srcFile, overrideLng) {
|
|
60
|
+
const overridePath = inferOverrideSourcePath(srcFile, overrideLng);
|
|
61
|
+
try {
|
|
62
|
+
const result = await (0, core_util_1.readTFileCore)("po", {
|
|
63
|
+
path: overridePath,
|
|
64
|
+
lng: overrideLng,
|
|
65
|
+
format: "po",
|
|
66
|
+
});
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
console.warn(`Warning: Could not read override source file ${overridePath}`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function extractStringsToTranslate(args) {
|
|
75
|
+
const src = args.src;
|
|
76
|
+
if (!src.size) {
|
|
77
|
+
(0, util_1.logFatal)("Did not find any source translations");
|
|
78
|
+
}
|
|
79
|
+
const oldTarget = args.oldTarget;
|
|
80
|
+
if (!oldTarget) {
|
|
81
|
+
// Translate everything if an old target does not yet exist.
|
|
82
|
+
return src;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Translate values whose keys are not in the target.
|
|
86
|
+
return (0, tset_ops_1.selectLeftDistinct)(src, oldTarget, "COMPARE_KEYS_AND_NULL_VALUES");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function extractStaleTranslations(args) {
|
|
90
|
+
if (args.oldTarget) {
|
|
91
|
+
return (0, tset_ops_1.leftMinusRight)(args.oldTarget, args.src);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function computeChangeSet(args, serviceInvocation) {
|
|
98
|
+
const deleted = extractStaleTranslations(args);
|
|
99
|
+
if (!serviceInvocation) {
|
|
100
|
+
return {
|
|
101
|
+
added: new Map(),
|
|
102
|
+
updated: new Map(),
|
|
103
|
+
skipped: new Map(),
|
|
104
|
+
deleted,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const skipped = (0, tset_ops_1.selectLeftDistinct)(serviceInvocation.inputs, serviceInvocation.results, "COMPARE_KEYS");
|
|
108
|
+
if (!args.oldTarget) {
|
|
109
|
+
return {
|
|
110
|
+
added: serviceInvocation.results,
|
|
111
|
+
updated: new Map(),
|
|
112
|
+
skipped,
|
|
113
|
+
deleted,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const added = (0, tset_ops_1.selectLeftDistinct)(serviceInvocation.results, args.oldTarget, "COMPARE_KEYS");
|
|
117
|
+
const updated = (0, tset_ops_1.selectLeftDistinct)(serviceInvocation.results, args.oldTarget, "COMPARE_VALUES");
|
|
118
|
+
return {
|
|
119
|
+
added,
|
|
120
|
+
updated,
|
|
121
|
+
skipped,
|
|
122
|
+
deleted,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function computeNewTarget(args, changeSet, serviceInvocation) {
|
|
126
|
+
const oldTargetRef = args.oldTarget && changeSet.deleted
|
|
127
|
+
? (0, tset_ops_1.leftMinusRight)(args.oldTarget, changeSet.deleted)
|
|
128
|
+
: args.oldTarget;
|
|
129
|
+
if (!serviceInvocation) {
|
|
130
|
+
return oldTargetRef !== null && oldTargetRef !== void 0 ? oldTargetRef : new Map();
|
|
131
|
+
}
|
|
132
|
+
if (!oldTargetRef) {
|
|
133
|
+
return serviceInvocation.results;
|
|
134
|
+
}
|
|
135
|
+
return (0, tset_ops_1.joinResultsPreserveOrder)({
|
|
136
|
+
translateResults: serviceInvocation.results,
|
|
137
|
+
changeSet,
|
|
138
|
+
oldTarget: oldTargetRef,
|
|
139
|
+
src: args.src,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function computeCoreResults(args, serviceInvocation, changeSet) {
|
|
143
|
+
return {
|
|
144
|
+
changeSet,
|
|
145
|
+
serviceInvocation,
|
|
146
|
+
newTarget: computeNewTarget(args, changeSet, serviceInvocation),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function translateCore(args) {
|
|
150
|
+
const rawServiceInputs = extractStringsToTranslate(args);
|
|
151
|
+
// Filter entries based on @manual marking
|
|
152
|
+
const filtered = filterByManualMarking(rawServiceInputs, args);
|
|
153
|
+
// Log what we're doing
|
|
154
|
+
if (filtered.toSkip.size > 0) {
|
|
155
|
+
console.info(`Skip ${filtered.toSkip.size} entries marked as @manual:${args.targetLng}`);
|
|
156
|
+
}
|
|
157
|
+
if (filtered.toCopyOriginal.size > 0) {
|
|
158
|
+
console.info(`Copy original text for ${filtered.toCopyOriginal.size} entries with @manual (no override)`);
|
|
159
|
+
}
|
|
160
|
+
if (filtered.toTranslateFromOverride.size > 0) {
|
|
161
|
+
const overrideLng = args.sourceOverride.get(args.targetLng);
|
|
162
|
+
console.info(`Translate ${filtered.toTranslateFromOverride.size} entries from override source ${overrideLng}`);
|
|
163
|
+
}
|
|
164
|
+
let serviceInvocation = null;
|
|
165
|
+
// Translate entries without @manual marking
|
|
166
|
+
if (filtered.toTranslate.size >= 1) {
|
|
167
|
+
serviceInvocation = await (0, invoke_translation_service_1.invokeTranslationService)(filtered.toTranslate, args);
|
|
168
|
+
}
|
|
169
|
+
// Handle entries that need to copy original text
|
|
170
|
+
if (filtered.toCopyOriginal.size > 0) {
|
|
171
|
+
if (!serviceInvocation) {
|
|
172
|
+
serviceInvocation = { inputs: new Map(), results: new Map() };
|
|
173
|
+
}
|
|
174
|
+
filtered.toCopyOriginal.forEach((value, key) => {
|
|
175
|
+
serviceInvocation.inputs.set(key, value);
|
|
176
|
+
serviceInvocation.results.set(key, value); // Copy original
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// Handle entries that need to translate from override source
|
|
180
|
+
if (filtered.toTranslateFromOverride.size > 0) {
|
|
181
|
+
const overrideLng = args.sourceOverride.get(args.targetLng);
|
|
182
|
+
if (overrideLng) {
|
|
183
|
+
const overrideSource = await readOverrideSource(args.srcFile, overrideLng);
|
|
184
|
+
if (overrideSource) {
|
|
185
|
+
// Create inputs from override source
|
|
186
|
+
const overrideInputs = new Map();
|
|
187
|
+
filtered.toTranslateFromOverride.forEach((_, key) => {
|
|
188
|
+
const overrideValue = overrideSource.get(key);
|
|
189
|
+
if (overrideValue) {
|
|
190
|
+
overrideInputs.set(key, overrideValue);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
if (overrideInputs.size > 0) {
|
|
194
|
+
// Translate from override source
|
|
195
|
+
const overrideArgs = {
|
|
196
|
+
...args,
|
|
197
|
+
src: overrideInputs,
|
|
198
|
+
srcLng: overrideLng,
|
|
199
|
+
};
|
|
200
|
+
const overrideResult = await (0, invoke_translation_service_1.invokeTranslationService)(overrideInputs, overrideArgs);
|
|
201
|
+
// Merge results
|
|
202
|
+
if (!serviceInvocation) {
|
|
203
|
+
serviceInvocation = { inputs: new Map(), results: new Map() };
|
|
204
|
+
}
|
|
205
|
+
overrideResult.inputs.forEach((value, key) => {
|
|
206
|
+
serviceInvocation.inputs.set(key, value);
|
|
207
|
+
});
|
|
208
|
+
overrideResult.results.forEach((value, key) => {
|
|
209
|
+
serviceInvocation.results.set(key, value);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// Fallback: copy original if override source not available
|
|
215
|
+
if (!serviceInvocation) {
|
|
216
|
+
serviceInvocation = { inputs: new Map(), results: new Map() };
|
|
217
|
+
}
|
|
218
|
+
filtered.toTranslateFromOverride.forEach((value, key) => {
|
|
219
|
+
serviceInvocation.inputs.set(key, value);
|
|
220
|
+
serviceInvocation.results.set(key, value);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const changeSet = computeChangeSet(args, serviceInvocation);
|
|
226
|
+
const results = computeCoreResults(args, serviceInvocation, changeSet);
|
|
227
|
+
(0, core_util_1.logCoreResults)(args, results);
|
|
228
|
+
return results;
|
|
229
|
+
}
|
|
230
|
+
exports.translateCore = translateCore;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.areEqual = exports.leftMinusRight = exports.joinResultsPreserveOrder = exports.selectLeftDistinct = void 0;
|
|
4
|
+
const core_util_1 = require("./core-util");
|
|
5
|
+
function selectLeftDistinct(left, right, strategy) {
|
|
6
|
+
const leftDistinct = new Map();
|
|
7
|
+
left.forEach((leftValue, key) => {
|
|
8
|
+
const rightValue = right.get(key);
|
|
9
|
+
const distinct = compareLeftRight({ leftValue, rightValue, strategy });
|
|
10
|
+
if (distinct) {
|
|
11
|
+
leftDistinct.set(key, leftValue);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
return leftDistinct;
|
|
15
|
+
}
|
|
16
|
+
exports.selectLeftDistinct = selectLeftDistinct;
|
|
17
|
+
function compareLeftRight(args) {
|
|
18
|
+
switch (args.strategy) {
|
|
19
|
+
case "COMPARE_KEYS":
|
|
20
|
+
return args.rightValue === undefined;
|
|
21
|
+
case "COMPARE_KEYS_AND_NULL_VALUES":
|
|
22
|
+
return (args.rightValue === undefined ||
|
|
23
|
+
(args.rightValue === null && args.leftValue !== null));
|
|
24
|
+
case "COMPARE_VALUES":
|
|
25
|
+
return (args.rightValue !== undefined && args.leftValue !== args.rightValue);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function joinResultsPreserveOrder(args) {
|
|
29
|
+
// To prevent file-scrambling, we have to preserve the order of the old target.
|
|
30
|
+
const targetOrder = [];
|
|
31
|
+
args.oldTarget.forEach((value, key) => {
|
|
32
|
+
targetOrder.push(key);
|
|
33
|
+
});
|
|
34
|
+
// Newly added translations are more flexible in its position.
|
|
35
|
+
// Therefore, we try to mimic the order of src.
|
|
36
|
+
injectNewKeysIntoTargetOrder({
|
|
37
|
+
targetOrder,
|
|
38
|
+
newlyAdded: args.changeSet.added,
|
|
39
|
+
oldTarget: args.oldTarget,
|
|
40
|
+
src: args.src,
|
|
41
|
+
});
|
|
42
|
+
// Create an in-order map out of the determined targetOrder
|
|
43
|
+
const joinResult = new Map();
|
|
44
|
+
targetOrder.forEach((key) => {
|
|
45
|
+
const freshResult = args.translateResults.get(key);
|
|
46
|
+
const oldResult = args.oldTarget.get(key);
|
|
47
|
+
if (freshResult !== undefined) {
|
|
48
|
+
joinResult.set(key, freshResult);
|
|
49
|
+
}
|
|
50
|
+
else if (oldResult !== undefined) {
|
|
51
|
+
joinResult.set(key, oldResult);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// Add any remaining newly added translations whose target order was not determined.
|
|
55
|
+
args.changeSet.added.forEach((value, key) => {
|
|
56
|
+
if (!joinResult.has(key)) {
|
|
57
|
+
joinResult.set(key, value);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return joinResult;
|
|
61
|
+
}
|
|
62
|
+
exports.joinResultsPreserveOrder = joinResultsPreserveOrder;
|
|
63
|
+
function injectNewKeysIntoTargetOrder(args) {
|
|
64
|
+
let injectPosition = 0;
|
|
65
|
+
args.src.forEach((srcValue, srcKey) => {
|
|
66
|
+
if (args.oldTarget.get(srcKey) !== undefined) {
|
|
67
|
+
injectPosition =
|
|
68
|
+
1 +
|
|
69
|
+
(0, core_util_1.getElementPosition)({
|
|
70
|
+
array: args.targetOrder,
|
|
71
|
+
element: srcKey,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (args.newlyAdded.get(srcKey) !== undefined) {
|
|
75
|
+
(0, core_util_1.insertAt)(args.targetOrder, injectPosition, srcKey);
|
|
76
|
+
injectPosition++;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function leftMinusRight(left, right) {
|
|
81
|
+
const leftRemaining = new Map();
|
|
82
|
+
left.forEach((value, key) => {
|
|
83
|
+
if (!right.has(key)) {
|
|
84
|
+
leftRemaining.set(key, value);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return leftRemaining;
|
|
88
|
+
}
|
|
89
|
+
exports.leftMinusRight = leftMinusRight;
|
|
90
|
+
function areEqual(set1, set2) {
|
|
91
|
+
if (set1.size !== set2.size) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
for (const key1 of set1.keys()) {
|
|
95
|
+
const value1 = set1.get(key1);
|
|
96
|
+
const value2 = set2.get(key1);
|
|
97
|
+
if (value1 !== value2) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const key2 of set2.keys()) {
|
|
102
|
+
const value1 = set1.get(key2);
|
|
103
|
+
const value2 = set2.get(key2);
|
|
104
|
+
if (value1 !== value2) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
exports.areEqual = areEqual;
|