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,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateYmlNodes = exports.extractYmlNodes = void 0;
|
|
4
|
+
const yaml_generic_1 = require("./yaml-generic");
|
|
5
|
+
const flatten_1 = require("../../util/flatten");
|
|
6
|
+
const parse_utils_1 = require("../common/parse-utils");
|
|
7
|
+
function extractYmlNodes(args, document) {
|
|
8
|
+
const tSet = new Map();
|
|
9
|
+
const rootContext = {
|
|
10
|
+
partialKey: "",
|
|
11
|
+
node: getRootNode(document, args),
|
|
12
|
+
oldTargetNode: null,
|
|
13
|
+
};
|
|
14
|
+
traverseYml(rootContext, (innerContext, scalar) => {
|
|
15
|
+
const value = scalar.value;
|
|
16
|
+
if (typeof value === "string") {
|
|
17
|
+
tSet.set(innerContext.partialKey, value);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return tSet;
|
|
21
|
+
}
|
|
22
|
+
exports.extractYmlNodes = extractYmlNodes;
|
|
23
|
+
function updateYmlNodes(args) {
|
|
24
|
+
const rootContext = {
|
|
25
|
+
partialKey: "",
|
|
26
|
+
node: getRootNode(args.sourceYml, args.args),
|
|
27
|
+
oldTargetNode: args.oldTargetYml
|
|
28
|
+
? getRootNode(args.oldTargetYml, args.args)
|
|
29
|
+
: null,
|
|
30
|
+
};
|
|
31
|
+
traverseYml(rootContext, (innerContext, scalar) => {
|
|
32
|
+
const value = args.args.tSet.get(innerContext.partialKey);
|
|
33
|
+
if (value !== undefined) {
|
|
34
|
+
scalar.value = value;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
exports.updateYmlNodes = updateYmlNodes;
|
|
39
|
+
function getRootNode(document, args) {
|
|
40
|
+
const root = document.contents;
|
|
41
|
+
if (!root) {
|
|
42
|
+
(0, parse_utils_1.logParseError)("root node not found", args);
|
|
43
|
+
}
|
|
44
|
+
if (!(0, yaml_generic_1.isScalar)(root) && !(0, yaml_generic_1.isCollection)(root) && !(0, yaml_generic_1.isPair)(root)) {
|
|
45
|
+
(0, parse_utils_1.logParseError)("root node invalid", args);
|
|
46
|
+
}
|
|
47
|
+
return root;
|
|
48
|
+
}
|
|
49
|
+
function traverseYml(context, operation) {
|
|
50
|
+
var _a, _b, _c, _d;
|
|
51
|
+
const node = context.node;
|
|
52
|
+
if (!node) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (((_a = context.oldTargetNode) === null || _a === void 0 ? void 0 : _a.type) !== node.type) {
|
|
56
|
+
context.oldTargetNode = null;
|
|
57
|
+
}
|
|
58
|
+
if ((_b = context.oldTargetNode) === null || _b === void 0 ? void 0 : _b.comment) {
|
|
59
|
+
node.comment = context.oldTargetNode.comment;
|
|
60
|
+
}
|
|
61
|
+
if ((_c = context.oldTargetNode) === null || _c === void 0 ? void 0 : _c.commentBefore) {
|
|
62
|
+
node.commentBefore = context.oldTargetNode.commentBefore;
|
|
63
|
+
}
|
|
64
|
+
if ((_d = context.oldTargetNode) === null || _d === void 0 ? void 0 : _d.spaceBefore) {
|
|
65
|
+
node.spaceBefore = context.oldTargetNode.spaceBefore;
|
|
66
|
+
}
|
|
67
|
+
// if (context.oldTargetNode?.cstNode) {
|
|
68
|
+
// node.cstNode = context.oldTargetNode.cstNode;
|
|
69
|
+
// }
|
|
70
|
+
if ((0, yaml_generic_1.isScalar)(node)) {
|
|
71
|
+
operation(context, node);
|
|
72
|
+
}
|
|
73
|
+
if ((0, yaml_generic_1.isPair)(node)) {
|
|
74
|
+
const pairKey = getPairKey(node);
|
|
75
|
+
let partialKey;
|
|
76
|
+
if (context.partialKey.length) {
|
|
77
|
+
partialKey = context.partialKey + flatten_1.NESTED_JSON_SEPARATOR + pairKey;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
partialKey = pairKey;
|
|
81
|
+
}
|
|
82
|
+
let oldTargetPair = null;
|
|
83
|
+
if ((0, yaml_generic_1.isPair)(context.oldTargetNode) &&
|
|
84
|
+
getPairKey(context.oldTargetNode) === pairKey) {
|
|
85
|
+
oldTargetPair = context.oldTargetNode;
|
|
86
|
+
}
|
|
87
|
+
traverseYml({
|
|
88
|
+
node: node.value,
|
|
89
|
+
oldTargetNode: oldTargetPair
|
|
90
|
+
? oldTargetPair.value
|
|
91
|
+
: null,
|
|
92
|
+
partialKey,
|
|
93
|
+
}, operation);
|
|
94
|
+
}
|
|
95
|
+
if ((0, yaml_generic_1.isCollection)(node)) {
|
|
96
|
+
node.items.forEach((childNode, idx) => {
|
|
97
|
+
let partialKey = `${context.partialKey}`;
|
|
98
|
+
if ((0, yaml_generic_1.isSequence)(node)) {
|
|
99
|
+
partialKey += `[${idx}]`;
|
|
100
|
+
}
|
|
101
|
+
let oldTargetChild = null;
|
|
102
|
+
if ((0, yaml_generic_1.isCollection)(context.oldTargetNode)) {
|
|
103
|
+
oldTargetChild = findMatchingOldTargetChild(context.oldTargetNode.items, childNode, idx);
|
|
104
|
+
}
|
|
105
|
+
traverseYml({
|
|
106
|
+
node: childNode,
|
|
107
|
+
oldTargetNode: oldTargetChild,
|
|
108
|
+
partialKey: partialKey,
|
|
109
|
+
}, operation);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function findMatchingOldTargetChild(oldTargetItems, child, idx) {
|
|
114
|
+
if (!(0, yaml_generic_1.isPair)(child)) {
|
|
115
|
+
if (idx >= oldTargetItems.length) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return oldTargetItems[idx];
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const candidatePairs = oldTargetItems.filter((node) => (0, yaml_generic_1.isPair)(node));
|
|
122
|
+
const matchingPair = candidatePairs.find((pair) => getPairKey(pair) === getPairKey(child));
|
|
123
|
+
return matchingPair !== null && matchingPair !== void 0 ? matchingPair : null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function getPairKey(pair) {
|
|
127
|
+
var _a;
|
|
128
|
+
if (typeof pair.key === "string") {
|
|
129
|
+
return pair.key;
|
|
130
|
+
}
|
|
131
|
+
return (_a = pair.key) === null || _a === void 0 ? void 0 : _a.value;
|
|
132
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseYaml = void 0;
|
|
4
|
+
const yaml_1 = require("yaml");
|
|
5
|
+
const parse_utils_1 = require("../common/parse-utils");
|
|
6
|
+
const managed_utf8_1 = require("../common/managed-utf8");
|
|
7
|
+
function parseYaml(args) {
|
|
8
|
+
var _a;
|
|
9
|
+
const ymlString = (0, managed_utf8_1.readManagedUtf8)(args.path);
|
|
10
|
+
if (!ymlString) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const options = {
|
|
14
|
+
keepCstNodes: true,
|
|
15
|
+
keepNodeTypes: true,
|
|
16
|
+
keepUndefined: true,
|
|
17
|
+
prettyErrors: true,
|
|
18
|
+
};
|
|
19
|
+
let document;
|
|
20
|
+
try {
|
|
21
|
+
document = (0, yaml_1.parseDocument)(ymlString, options);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
console.error(e);
|
|
25
|
+
(0, parse_utils_1.logParseError)("YAML parsing error", args);
|
|
26
|
+
}
|
|
27
|
+
if ((_a = document.errors) === null || _a === void 0 ? void 0 : _a.length) {
|
|
28
|
+
const errorMsg = document.errors
|
|
29
|
+
.map((e) => {
|
|
30
|
+
e.makePretty();
|
|
31
|
+
return e.message;
|
|
32
|
+
})
|
|
33
|
+
.join("\n");
|
|
34
|
+
(0, parse_utils_1.logParseError)(errorMsg, args);
|
|
35
|
+
}
|
|
36
|
+
return document;
|
|
37
|
+
}
|
|
38
|
+
exports.parseYaml = parseYaml;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.run = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const commander_1 = (0, tslib_1.__importDefault)(require("commander"));
|
|
6
|
+
require("dotenv/config");
|
|
7
|
+
const translate_cli_1 = require("./core/translate-cli");
|
|
8
|
+
const file_format_definitions_1 = require("./file-formats/file-format-definitions");
|
|
9
|
+
const matcher_definitions_1 = require("./matchers/matcher-definitions");
|
|
10
|
+
const service_definitions_1 = require("./services/service-definitions");
|
|
11
|
+
const extract_version_1 = require("./util/extract-version");
|
|
12
|
+
process.on("unhandledRejection", (error) => {
|
|
13
|
+
console.error("[fatal]", error);
|
|
14
|
+
});
|
|
15
|
+
function formatOneOfOptions(options) {
|
|
16
|
+
return `One of ${(0, translate_cli_1.formatCliOptions)(options)}`;
|
|
17
|
+
}
|
|
18
|
+
function run(process, cliBinDir) {
|
|
19
|
+
var _a;
|
|
20
|
+
commander_1.default.storeOptionsAsProperties(false);
|
|
21
|
+
commander_1.default.addHelpCommand(false);
|
|
22
|
+
commander_1.default
|
|
23
|
+
.requiredOption("--srcFile <sourceFile>", "The source file to be translated")
|
|
24
|
+
.requiredOption("--srcLng <sourceLanguage>", "A language code for the source language")
|
|
25
|
+
.requiredOption("--srcFormat <sourceFileFormat>", formatOneOfOptions((0, file_format_definitions_1.getTFileFormatList)()))
|
|
26
|
+
.requiredOption("--targetFile <targetFile>", "The target file for the translations")
|
|
27
|
+
.requiredOption("--targetLng <targetLanguage>", "A language code for the target language")
|
|
28
|
+
.requiredOption("--targetFormat <targetFileFormat>", formatOneOfOptions((0, file_format_definitions_1.getTFileFormatList)()))
|
|
29
|
+
.requiredOption("--service <translationService>", formatOneOfOptions((0, service_definitions_1.getTServiceList)()))
|
|
30
|
+
.option("--serviceConfig <serviceKey>", "supply configuration for a translation service (either a path to a key-file or an API-key)")
|
|
31
|
+
.option("--matcher <matcher>", formatOneOfOptions((0, matcher_definitions_1.getTMatcherList)()), "none")
|
|
32
|
+
.option("--prompt <prompt>", "supply a prompt for the AI translation service")
|
|
33
|
+
.option("--sourceOverride <mapping>", "override source language for specific targets (e.g., 'zh-Hant:zh-Hans,pt-BR:pt-PT')")
|
|
34
|
+
.option("--baseUrl <url>", "custom API base URL for OpenAI-compatible services (e.g., 'https://api.openai.com/v1')")
|
|
35
|
+
.version((0, extract_version_1.extractVersion)({ cliBinDir }), "-v, --version")
|
|
36
|
+
.parse(process.argv);
|
|
37
|
+
if ((_a = commander_1.default.args) === null || _a === void 0 ? void 0 : _a.length) {
|
|
38
|
+
// Args are not permitted, only work with options.
|
|
39
|
+
commander_1.default.unknownCommand();
|
|
40
|
+
}
|
|
41
|
+
const args = {
|
|
42
|
+
srcFile: commander_1.default.opts().srcFile,
|
|
43
|
+
srcLng: commander_1.default.opts().srcLng,
|
|
44
|
+
srcFormat: commander_1.default.opts().srcFormat,
|
|
45
|
+
targetFile: commander_1.default.opts().targetFile,
|
|
46
|
+
targetLng: commander_1.default.opts().targetLng,
|
|
47
|
+
targetFormat: commander_1.default.opts().targetFormat,
|
|
48
|
+
service: commander_1.default.opts().service,
|
|
49
|
+
serviceConfig: commander_1.default.opts().serviceConfig,
|
|
50
|
+
matcher: commander_1.default.opts().matcher,
|
|
51
|
+
prompt: commander_1.default.opts().prompt,
|
|
52
|
+
sourceOverride: commander_1.default.opts().sourceOverride,
|
|
53
|
+
baseUrl: commander_1.default.opts().baseUrl,
|
|
54
|
+
};
|
|
55
|
+
(0, translate_cli_1.translateCli)(args)
|
|
56
|
+
.then(() => {
|
|
57
|
+
process.exit(0);
|
|
58
|
+
})
|
|
59
|
+
.catch((e) => {
|
|
60
|
+
console.error("An error occurred:");
|
|
61
|
+
console.error(e.message);
|
|
62
|
+
console.error(e.stack);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
exports.run = run;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchI18Next = void 0;
|
|
4
|
+
const matchI18Next = (input, replacer) => {
|
|
5
|
+
const matches = input.match(/(\{\{.+?\}\}|\$t\(.+?\)|\$\{.+?\})/g);
|
|
6
|
+
return (matches || []).map((match, index) => ({
|
|
7
|
+
from: match,
|
|
8
|
+
to: replacer(index),
|
|
9
|
+
}));
|
|
10
|
+
};
|
|
11
|
+
exports.matchI18Next = matchI18Next;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchIcu = void 0;
|
|
4
|
+
let parseModule = null;
|
|
5
|
+
const matchIcu = (input, replacer) => {
|
|
6
|
+
// Import parseModule on demand to optimize launch-performance.
|
|
7
|
+
if (!parseModule) {
|
|
8
|
+
parseModule = require("messageformat-parser");
|
|
9
|
+
}
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
11
|
+
const parts = parseModule.parse(input);
|
|
12
|
+
const regex = new RegExp(parts
|
|
13
|
+
.map((part) => typeof part === "string"
|
|
14
|
+
? part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
15
|
+
: "(.*)")
|
|
16
|
+
.join(""));
|
|
17
|
+
const matches = input.match(regex);
|
|
18
|
+
return (matches || []).slice(1).map((match, index) => ({
|
|
19
|
+
from: match,
|
|
20
|
+
to: replacer(index),
|
|
21
|
+
}));
|
|
22
|
+
};
|
|
23
|
+
exports.matchIcu = matchIcu;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.instantiateTMatcher = exports.reInsertInterpolations = exports.replaceInterpolations = exports.getTMatcherList = exports.matchNothing = exports.xmlStyleReplacer = void 0;
|
|
4
|
+
const icu_1 = require("./icu");
|
|
5
|
+
const i18next_1 = require("./i18next");
|
|
6
|
+
const sprintf_1 = require("./sprintf");
|
|
7
|
+
const xmlStyleReplacer = (index) => `<span>${index}</span>`;
|
|
8
|
+
exports.xmlStyleReplacer = xmlStyleReplacer;
|
|
9
|
+
const xmlLeftTag = "<span>";
|
|
10
|
+
const xmlRightTag = "</span>";
|
|
11
|
+
const spaceXmlRightTag = "</ span>";
|
|
12
|
+
const matchNothing = () => [];
|
|
13
|
+
exports.matchNothing = matchNothing;
|
|
14
|
+
function getTMatcherList() {
|
|
15
|
+
return Object.keys(matcherMap);
|
|
16
|
+
}
|
|
17
|
+
exports.getTMatcherList = getTMatcherList;
|
|
18
|
+
const matcherMap = {
|
|
19
|
+
none: exports.matchNothing,
|
|
20
|
+
icu: icu_1.matchIcu,
|
|
21
|
+
i18next: i18next_1.matchI18Next,
|
|
22
|
+
sprintf: sprintf_1.matchSprintf,
|
|
23
|
+
};
|
|
24
|
+
const replaceInterpolations = (input, matcher = exports.matchNothing, replacer = exports.xmlStyleReplacer) => {
|
|
25
|
+
const replacements = matcher(input, replacer);
|
|
26
|
+
const clean = replacements.reduce((acc, cur) => acc.replace(cur.from, cur.to), input);
|
|
27
|
+
return { clean, replacements };
|
|
28
|
+
};
|
|
29
|
+
exports.replaceInterpolations = replaceInterpolations;
|
|
30
|
+
function replaceAll(string, search, replace) {
|
|
31
|
+
return string.split(search).join(replace);
|
|
32
|
+
}
|
|
33
|
+
const reInsertInterpolations = (clean, replacements) => {
|
|
34
|
+
const c1 = replaceAll(clean, xmlLeftTag + " ", xmlLeftTag);
|
|
35
|
+
const c2 = replaceAll(c1, spaceXmlRightTag, xmlRightTag);
|
|
36
|
+
const c3 = replaceAll(c2, " " + xmlRightTag, xmlRightTag);
|
|
37
|
+
return replacements.reduce((acc, cur) => acc.replace(cur.to, cur.from), c3);
|
|
38
|
+
};
|
|
39
|
+
exports.reInsertInterpolations = reInsertInterpolations;
|
|
40
|
+
function instantiateTMatcher(matcher) {
|
|
41
|
+
if (typeof matcherMap[matcher] === "undefined") {
|
|
42
|
+
throw new Error(`matcher ${matcher} doesn't exist.`);
|
|
43
|
+
}
|
|
44
|
+
return matcherMap[matcher];
|
|
45
|
+
}
|
|
46
|
+
exports.instantiateTMatcher = instantiateTMatcher;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchSprintf = void 0;
|
|
4
|
+
const matchSprintf = (input, replacer) => {
|
|
5
|
+
const matches = input.match(/(%.)/g);
|
|
6
|
+
return (matches || []).map((match, index) => ({
|
|
7
|
+
from: match,
|
|
8
|
+
to: replacer(index),
|
|
9
|
+
}));
|
|
10
|
+
};
|
|
11
|
+
exports.matchSprintf = matchSprintf;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AzureTranslator = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_fetch_1 = (0, tslib_1.__importDefault)(require("node-fetch"));
|
|
6
|
+
const lodash_1 = require("lodash");
|
|
7
|
+
const util_1 = require("../util/util");
|
|
8
|
+
const TRANSLATE_ENDPOINT = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0";
|
|
9
|
+
class AzureTranslator {
|
|
10
|
+
async translateBatch(batch, args, config) {
|
|
11
|
+
var _a;
|
|
12
|
+
const [apiKey, region] = (_a = config === null || config === void 0 ? void 0 : config.split(',')) !== null && _a !== void 0 ? _a : [];
|
|
13
|
+
if (!apiKey) {
|
|
14
|
+
throw new Error('Missing API Key');
|
|
15
|
+
}
|
|
16
|
+
const headers = {
|
|
17
|
+
"Ocp-Apim-Subscription-Key": apiKey,
|
|
18
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
19
|
+
};
|
|
20
|
+
if (region) {
|
|
21
|
+
headers["Ocp-Apim-Subscription-Region"] = region;
|
|
22
|
+
}
|
|
23
|
+
const azureBody = batch.map((tString) => {
|
|
24
|
+
return {
|
|
25
|
+
Text: tString.value,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
const response = await (0, node_fetch_1.default)(`${TRANSLATE_ENDPOINT}&from=${args.srcLng}&to=${args.targetLng}&textType=html`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers,
|
|
31
|
+
body: JSON.stringify(azureBody),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error("Azure Translation failed: " + (await response.text()));
|
|
35
|
+
}
|
|
36
|
+
const data = (await response.json());
|
|
37
|
+
return data.map((res, i) => ({
|
|
38
|
+
key: batch[i].key,
|
|
39
|
+
translated: res.translations[0].text,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
async translateStrings(args) {
|
|
43
|
+
const config = args.serviceConfig;
|
|
44
|
+
if (!config) {
|
|
45
|
+
(0, util_1.logFatal)("Set '--serviceConfig' to an Azure API key");
|
|
46
|
+
}
|
|
47
|
+
const batches = (0, lodash_1.chunk)(args.strings, 50);
|
|
48
|
+
const results = await Promise.all(batches.map((batch) => this.translateBatch(batch, args, config)));
|
|
49
|
+
return (0, lodash_1.flatten)(results);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.AzureTranslator = AzureTranslator;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GoogleTranslate = void 0;
|
|
4
|
+
const translate_1 = require("@google-cloud/translate");
|
|
5
|
+
const util_1 = require("../util/util");
|
|
6
|
+
const lodash_1 = require("lodash");
|
|
7
|
+
const managed_json_1 = require("../file-formats/common/managed-json");
|
|
8
|
+
class GoogleTranslate {
|
|
9
|
+
async translateStrings(args) {
|
|
10
|
+
if (!args.serviceConfig) {
|
|
11
|
+
(0, util_1.logFatal)("Set '--serviceConfig' to a path that points to a GCloud service account JSON file");
|
|
12
|
+
}
|
|
13
|
+
(0, util_1.checkNotDir)(args.serviceConfig, { errorHint: "serviceConfig" });
|
|
14
|
+
const keyFile = (0, managed_json_1.readRawJson)(args.serviceConfig).object;
|
|
15
|
+
if (!keyFile.project_id) {
|
|
16
|
+
(0, util_1.logFatal)(`serviceConfig ${(0, util_1.getDebugPath)(args.serviceConfig)} does not contain a project_id`);
|
|
17
|
+
}
|
|
18
|
+
const projectId = keyFile.project_id;
|
|
19
|
+
const clientOptions = {
|
|
20
|
+
keyFile: args.serviceConfig,
|
|
21
|
+
};
|
|
22
|
+
const client = new translate_1.TranslationServiceClient(clientOptions);
|
|
23
|
+
const batches = (0, lodash_1.chunk)(args.strings, 10);
|
|
24
|
+
const results = await Promise.all(batches.map((batch) => this.translateBatch(batch, client, args, projectId)));
|
|
25
|
+
return (0, lodash_1.flatten)(results);
|
|
26
|
+
}
|
|
27
|
+
async translateBatch(batch, client, args, projectId) {
|
|
28
|
+
const location = "global";
|
|
29
|
+
const stringsToTranslate = batch.map((tString) => tString.value);
|
|
30
|
+
const request = {
|
|
31
|
+
parent: `projects/${projectId}/locations/${location}`,
|
|
32
|
+
contents: stringsToTranslate,
|
|
33
|
+
mimeType: "text/plain",
|
|
34
|
+
sourceLanguageCode: args.srcLng,
|
|
35
|
+
targetLanguageCode: args.targetLng,
|
|
36
|
+
};
|
|
37
|
+
const [response] = await client.translateText(request);
|
|
38
|
+
if (!response.translations) {
|
|
39
|
+
(0, util_1.logFatal)(`Google-translate did not return translations`);
|
|
40
|
+
}
|
|
41
|
+
return response.translations.map((value, index) => {
|
|
42
|
+
return this.transformGCloudResult(value, batch[index]);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
transformGCloudResult(result, input) {
|
|
46
|
+
if (!result.translatedText) {
|
|
47
|
+
(0, util_1.logFatal)(`Google-translate did not return a result for input '${input.value}' with key '${input.key}'`);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
key: input.key,
|
|
51
|
+
translated: result.translatedText,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.GoogleTranslate = GoogleTranslate;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeyAsTranslation = void 0;
|
|
4
|
+
const util_1 = require("../util/util");
|
|
5
|
+
class KeyAsTranslation {
|
|
6
|
+
translateStrings(args) {
|
|
7
|
+
if (args.srcLng !== args.targetLng) {
|
|
8
|
+
(0, util_1.logFatal)(`'key-as-translation' cannot translate between different languages -> You should either use equal languages or a different service`);
|
|
9
|
+
}
|
|
10
|
+
return Promise.resolve(args.strings.map((tString) => {
|
|
11
|
+
return {
|
|
12
|
+
key: tString.key,
|
|
13
|
+
translated: tString.key,
|
|
14
|
+
};
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.KeyAsTranslation = KeyAsTranslation;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ManualTranslation = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const inquirer_1 = (0, tslib_1.__importDefault)(require("inquirer"));
|
|
6
|
+
class ManualTranslation {
|
|
7
|
+
async translateStrings(args) {
|
|
8
|
+
const results = [];
|
|
9
|
+
console.info(`Total number of questions: ${args.strings.length}`);
|
|
10
|
+
console.info(`You can skip questions by pressing <ENTER> without any other character.`);
|
|
11
|
+
for (const { key, value } of args.strings) {
|
|
12
|
+
const result = await inquirer_1.default.prompt([
|
|
13
|
+
{
|
|
14
|
+
name: "result",
|
|
15
|
+
message: `(${key}) What is '${value}' in '${args.targetLng}'?`,
|
|
16
|
+
},
|
|
17
|
+
]);
|
|
18
|
+
const userInput = result.result;
|
|
19
|
+
if (userInput.trim().length) {
|
|
20
|
+
results.push({
|
|
21
|
+
key,
|
|
22
|
+
translated: result.result,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.ManualTranslation = ManualTranslation;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAITranslate = void 0;
|
|
4
|
+
const openai_1 = require("openai");
|
|
5
|
+
const util_1 = require("../util/util");
|
|
6
|
+
const lodash_1 = require("lodash");
|
|
7
|
+
async function translateSingleString(tString, args) {
|
|
8
|
+
var _a, _b, _c, _d;
|
|
9
|
+
const OPENAI_API_KEY = args.serviceConfig;
|
|
10
|
+
if (!OPENAI_API_KEY || !OPENAI_API_KEY.trim().length) {
|
|
11
|
+
(0, util_1.logFatal)('Missing OpenAI API Key: Please get an API key from https://platform.openai.com/account/api-keys and then call lingui-po-translate with --serviceConfig="YOUR API KEY"');
|
|
12
|
+
}
|
|
13
|
+
const configuration = new openai_1.Configuration({
|
|
14
|
+
apiKey: OPENAI_API_KEY,
|
|
15
|
+
basePath: (_b = (_a = args.baseUrl) !== null && _a !== void 0 ? _a : process.env.OPENAI_BASE_URL) !== null && _b !== void 0 ? _b : undefined,
|
|
16
|
+
});
|
|
17
|
+
const openai = new openai_1.OpenAIApi(configuration);
|
|
18
|
+
const prompt = generatePrompt(tString, args);
|
|
19
|
+
const messages = [
|
|
20
|
+
{
|
|
21
|
+
role: "user",
|
|
22
|
+
content: prompt,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* https://platform.openai.com/docs/api-reference/completions/create
|
|
27
|
+
* What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.
|
|
28
|
+
* We generally recommend altering this or top_p but not both.
|
|
29
|
+
*/
|
|
30
|
+
try {
|
|
31
|
+
const completion = await openai.createChatCompletion({
|
|
32
|
+
model: "gpt-4o-mini-2024-07-18",
|
|
33
|
+
messages: messages,
|
|
34
|
+
temperature: 0,
|
|
35
|
+
max_tokens: 2048,
|
|
36
|
+
});
|
|
37
|
+
const text = (_c = completion.data.choices[0].message) === null || _c === void 0 ? void 0 : _c.content;
|
|
38
|
+
if (text == undefined) {
|
|
39
|
+
(0, util_1.logFatal)("OpenAI returned undefined for prompt " + prompt);
|
|
40
|
+
}
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
if (typeof e.message === "string") {
|
|
45
|
+
(0, util_1.logFatal)("OpenAI: " +
|
|
46
|
+
e.message +
|
|
47
|
+
", Status text: " +
|
|
48
|
+
JSON.stringify((_d = e === null || e === void 0 ? void 0 : e.response) === null || _d === void 0 ? void 0 : _d.statusText));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function generatePrompt(tString, args) {
|
|
56
|
+
const capitalizedText = tString.value;
|
|
57
|
+
const initialPrompt = `only translate my software string from ${args.srcLng} to ${args.targetLng}. don't chat or explain. Using the correct terms for computer software in the target language, only show target language never repeat string. if you don't find something to translate, don't respond`;
|
|
58
|
+
let contextInfo = `\n\nkey (used for context): ${tString.key}`;
|
|
59
|
+
if (tString.context) {
|
|
60
|
+
contextInfo += `\n\ncontext: ${tString.context}`;
|
|
61
|
+
}
|
|
62
|
+
return (initialPrompt +
|
|
63
|
+
(args.prompt ? `\n\n${args.prompt}` : "") +
|
|
64
|
+
contextInfo +
|
|
65
|
+
`\n\nstring to translate: ${capitalizedText}`);
|
|
66
|
+
}
|
|
67
|
+
async function translateBatch(batch, args) {
|
|
68
|
+
console.log("Translate a batch of " + batch.length + " strings with OpenAI...");
|
|
69
|
+
const promises = batch.map(async (tString) => {
|
|
70
|
+
const rawResult = await translateSingleString(tString, args);
|
|
71
|
+
const result = {
|
|
72
|
+
key: tString.key,
|
|
73
|
+
translated: rawResult.trim(),
|
|
74
|
+
};
|
|
75
|
+
return result;
|
|
76
|
+
});
|
|
77
|
+
const resolvedPromises = await Promise.all(promises);
|
|
78
|
+
return resolvedPromises;
|
|
79
|
+
}
|
|
80
|
+
class OpenAITranslate {
|
|
81
|
+
async translateStrings(args) {
|
|
82
|
+
const batches = (0, lodash_1.chunk)(args.strings, 10);
|
|
83
|
+
const results = [];
|
|
84
|
+
for (const batch of batches) {
|
|
85
|
+
const result = await translateBatch(batch, args);
|
|
86
|
+
results.push(result);
|
|
87
|
+
}
|
|
88
|
+
return (0, lodash_1.flatten)(results);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.OpenAITranslate = OpenAITranslate;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.instantiateTService = exports.injectFakeService = exports.getTServiceList = void 0;
|
|
4
|
+
const util_1 = require("../util/util");
|
|
5
|
+
function getTServiceList() {
|
|
6
|
+
return Object.keys(serviceMap);
|
|
7
|
+
}
|
|
8
|
+
exports.getTServiceList = getTServiceList;
|
|
9
|
+
const serviceMap = {
|
|
10
|
+
openai: null,
|
|
11
|
+
typechat: null,
|
|
12
|
+
"typechat-manual": null,
|
|
13
|
+
manual: null,
|
|
14
|
+
"sync-without-translate": null,
|
|
15
|
+
"google-translate": null,
|
|
16
|
+
// deepl: null,
|
|
17
|
+
azure: null,
|
|
18
|
+
"key-as-translation": null,
|
|
19
|
+
};
|
|
20
|
+
function injectFakeService(serviceName, service) {
|
|
21
|
+
fakeServiceMap[serviceName] = service;
|
|
22
|
+
}
|
|
23
|
+
exports.injectFakeService = injectFakeService;
|
|
24
|
+
const fakeServiceMap = {};
|
|
25
|
+
async function instantiateTService(service) {
|
|
26
|
+
const fakeService = fakeServiceMap[service];
|
|
27
|
+
if (fakeService) {
|
|
28
|
+
return fakeService;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* To gain a reasonable launch-performance, we import services dynamically.
|
|
32
|
+
* This is especially important for google-translate, which uses a huge bunch of packages.
|
|
33
|
+
*/
|
|
34
|
+
switch (service) {
|
|
35
|
+
case "openai":
|
|
36
|
+
return new (await Promise.resolve().then(() => __importStar(require("./openai-translate")))).OpenAITranslate();
|
|
37
|
+
case "typechat":
|
|
38
|
+
(0, util_1.nodeVersionSatisfies)("typechat", ">=18");
|
|
39
|
+
return new (await Promise.resolve().then(() => __importStar(require("./typechat")))).TypeChatTranslate();
|
|
40
|
+
case "typechat-manual":
|
|
41
|
+
(0, util_1.nodeVersionSatisfies)("typechat", ">=18");
|
|
42
|
+
return new (await Promise.resolve().then(() => __importStar(require("./typechat")))).TypeChatTranslate(true);
|
|
43
|
+
case "azure":
|
|
44
|
+
return new (await Promise.resolve().then(() => __importStar(require("./azure-translator")))).AzureTranslator();
|
|
45
|
+
// case "deepl":
|
|
46
|
+
// return new (await import("./deepl")).DeepL();
|
|
47
|
+
case "google-translate":
|
|
48
|
+
return new (await Promise.resolve().then(() => __importStar(require("./google-translate")))).GoogleTranslate();
|
|
49
|
+
case "manual":
|
|
50
|
+
return new (await Promise.resolve().then(() => __importStar(require("./manual")))).ManualTranslation();
|
|
51
|
+
case "sync-without-translate":
|
|
52
|
+
return new (await Promise.resolve().then(() => __importStar(require("./sync-without-translate")))).SyncWithoutTranslate();
|
|
53
|
+
case "key-as-translation":
|
|
54
|
+
return new (await Promise.resolve().then(() => __importStar(require("./key-as-translation")))).KeyAsTranslation();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.instantiateTService = instantiateTService;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SyncWithoutTranslate = void 0;
|
|
4
|
+
const util_1 = require("../util/util");
|
|
5
|
+
class SyncWithoutTranslate {
|
|
6
|
+
translateStrings(args) {
|
|
7
|
+
if (args.srcLng !== args.targetLng) {
|
|
8
|
+
(0, util_1.logFatal)(`'sync-without-translate' cannot translate between different languages -> You should either use equal languages or a different service`);
|
|
9
|
+
}
|
|
10
|
+
return Promise.resolve(args.strings.map((tString) => {
|
|
11
|
+
return {
|
|
12
|
+
key: tString.key,
|
|
13
|
+
translated: tString.value,
|
|
14
|
+
};
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.SyncWithoutTranslate = SyncWithoutTranslate;
|