gpt-po 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +1 -2
- package/README_zh-CN.md +1 -1
- package/lib/package.json +13 -12
- package/lib/src/index.js +36 -54
- package/lib/src/manipulate.js +12 -17
- package/lib/src/sync.js +14 -29
- package/lib/src/systemprompt.txt +31 -19
- package/lib/src/translate.d.ts +3 -3
- package/lib/src/translate.js +107 -125
- package/lib/src/utils.js +22 -33
- package/package.json +13 -12
- /package/lib/src/{dictionary-template.json → dictionary.json} +0 -0
package/README.md
CHANGED
@@ -54,8 +54,7 @@ translate po file (default command)
|
|
54
54
|
Options:
|
55
55
|
-k, --key <key> openai api key (env: OPENAI_API_KEY)
|
56
56
|
--host <host> openai api host (env: OPENAI_API_HOST)
|
57
|
-
--model <model> openai model (
|
58
|
-
default: "gpt-4o")
|
57
|
+
--model <model> openai model (default: "gpt-4o-mini", env: OPENAI_MODEL)
|
59
58
|
--po <file> po file path
|
60
59
|
--dir <dir> po file directory
|
61
60
|
-src, --source <lang> source language (default: "english")
|
package/README_zh-CN.md
CHANGED
@@ -56,7 +56,7 @@ npm install gpt-po
|
|
56
56
|
选项:
|
57
57
|
-k, --key <key> openai api key (环境变量: OPENAI_API_KEY)
|
58
58
|
--host <host> openai api host (环境变量: OPENAI_API_HOST)
|
59
|
-
--model <model> openai 模型 (
|
59
|
+
--model <model> openai 模型 (默认: "gpt-4o-mini", 环境变量: OPENAI_MODEL)
|
60
60
|
--po <file> po 文件路径
|
61
61
|
--dir <dir> po 文件目录
|
62
62
|
-src, --source <lang> 源语言 (默认: "english")
|
package/lib/package.json
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
{
|
2
2
|
"name": "gpt-po",
|
3
|
-
"version": "1.1.
|
3
|
+
"version": "1.1.2",
|
4
4
|
"description": "command tool for translate po files by gpt",
|
5
5
|
"main": "lib/src/index.js",
|
6
|
+
"type": "module",
|
6
7
|
"bin": {
|
7
8
|
"gpt-po": "lib/src/index.js"
|
8
9
|
},
|
@@ -33,23 +34,23 @@
|
|
33
34
|
"openai"
|
34
35
|
],
|
35
36
|
"devDependencies": {
|
36
|
-
"@types/gettext-parser": "^4.0.
|
37
|
-
"@types/jest": "^29.5.
|
38
|
-
"jest": "^29.
|
37
|
+
"@types/gettext-parser": "^4.0.4",
|
38
|
+
"@types/jest": "^29.5.12",
|
39
|
+
"jest": "^29.7.0",
|
39
40
|
"ncp": "^2.0.0",
|
40
|
-
"prettier": "^3.
|
41
|
-
"rimraf": "^
|
42
|
-
"ts-jest": "^29.
|
41
|
+
"prettier": "^3.3.3",
|
42
|
+
"rimraf": "^6.0.1",
|
43
|
+
"ts-jest": "^29.2.5",
|
43
44
|
"tslint": "^6.1.3",
|
44
45
|
"tslint-config-prettier": "^1.18.0",
|
45
|
-
"typescript": "^5.
|
46
|
+
"typescript": "^5.5.4"
|
46
47
|
},
|
47
48
|
"dependencies": {
|
48
|
-
"commander": "^
|
49
|
-
"gettext-parser": "^
|
50
|
-
"openai": "^
|
49
|
+
"commander": "^12.1.0",
|
50
|
+
"gettext-parser": "^8.0.0",
|
51
|
+
"openai": "^4.56.0"
|
51
52
|
},
|
52
53
|
"engines": {
|
53
|
-
"node": ">=
|
54
|
+
"node": ">=18.0.0"
|
54
55
|
}
|
55
56
|
}
|
package/lib/src/index.js
CHANGED
@@ -1,48 +1,30 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
const commander_1 = require("commander");
|
14
|
-
const pkg = require("../package.json");
|
15
|
-
const sync_1 = require("./sync");
|
16
|
-
const translate_1 = require("./translate");
|
17
|
-
const utils_1 = require("./utils");
|
18
|
-
const manipulate_1 = require("./manipulate");
|
19
|
-
const program = new commander_1.Command();
|
2
|
+
import { Command, Option } from "commander";
|
3
|
+
import path from "path";
|
4
|
+
import { fileURLToPath } from "url";
|
5
|
+
import pkg from "../package.json" with { type: "json" };
|
6
|
+
import { sync } from "./sync.js";
|
7
|
+
import { init, translatePo, translatePoDir } from "./translate.js";
|
8
|
+
import { copyFileIfNotExists, compilePo, findConfig, openFileByDefault, openFileExplorer, parsePo } from "./utils.js";
|
9
|
+
import { removeByOptions } from "./manipulate.js";
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
11
|
+
const __dirname = path.dirname(__filename);
|
12
|
+
const program = new Command();
|
20
13
|
program.name(pkg.name).version(pkg.version).description(pkg.description);
|
21
14
|
program
|
22
15
|
.command("translate", { isDefault: true })
|
23
16
|
.description("translate po file (default command)")
|
24
|
-
.addOption(new
|
25
|
-
.addOption(new
|
26
|
-
.addOption(new
|
27
|
-
.
|
28
|
-
.
|
29
|
-
"gpt-4o",
|
30
|
-
"gpt-4-turbo",
|
31
|
-
"gpt-4",
|
32
|
-
"gpt-4-0314",
|
33
|
-
"gpt-4-32k",
|
34
|
-
"gpt-4-32k-0314",
|
35
|
-
"gpt-3.5-turbo",
|
36
|
-
"gpt-3.5-turbo-0301",
|
37
|
-
]))
|
38
|
-
.addOption(new commander_1.Option("--po <file>", "po file path").conflicts("dir"))
|
39
|
-
.addOption(new commander_1.Option("--dir <dir>", "po file directory").conflicts("po"))
|
17
|
+
.addOption(new Option("-k, --key <key>", "openai api key").env("OPENAI_API_KEY"))
|
18
|
+
.addOption(new Option("--host <host>", "openai api host").env("OPENAI_API_HOST"))
|
19
|
+
.addOption(new Option("--model <model>", "openai model").env("OPENAI_MODEL").default("gpt-4o"))
|
20
|
+
.addOption(new Option("--po <file>", "po file path").conflicts("dir"))
|
21
|
+
.addOption(new Option("--dir <dir>", "po file directory").conflicts("po"))
|
40
22
|
.option("-src, --source <lang>", "source language (ISO 639-1)", "en")
|
41
23
|
.option("-l, --lang <lang>", "target language (ISO 639-1)")
|
42
24
|
.option("--verbose", "print verbose log")
|
43
25
|
.option("--context <file>", "text file that provides the bot additional context")
|
44
|
-
.addOption(new
|
45
|
-
.action((args) =>
|
26
|
+
.addOption(new Option("-o, --output <file>", "output file path, overwirte po file by default").conflicts("dir"))
|
27
|
+
.action(async (args) => {
|
46
28
|
const { key, host, model, po, dir, source, lang, verbose, output, context } = args;
|
47
29
|
if (host) {
|
48
30
|
process.env.OPENAI_API_HOST = host;
|
@@ -55,26 +37,26 @@ program
|
|
55
37
|
console.error("OPENAI_API_KEY is required");
|
56
38
|
process.exit(1);
|
57
39
|
}
|
58
|
-
|
40
|
+
init();
|
59
41
|
if (po) {
|
60
|
-
|
42
|
+
await translatePo(model, po, source, lang, verbose, output, context);
|
61
43
|
}
|
62
44
|
else if (dir) {
|
63
|
-
|
45
|
+
await translatePoDir(model, dir, source, lang, verbose, context);
|
64
46
|
}
|
65
47
|
else {
|
66
48
|
console.error("po file or directory is required");
|
67
49
|
process.exit(1);
|
68
50
|
}
|
69
|
-
})
|
51
|
+
});
|
70
52
|
program
|
71
53
|
.command("sync")
|
72
54
|
.description("update po from pot file")
|
73
55
|
.requiredOption("--po <file>", "po file path")
|
74
56
|
.requiredOption("--pot <file>", "pot file path")
|
75
|
-
.action(({ po, pot }) =>
|
76
|
-
|
77
|
-
})
|
57
|
+
.action(async ({ po, pot }) => {
|
58
|
+
await sync(po, pot);
|
59
|
+
});
|
78
60
|
// program command `systemprompt` with help text `open/edit system prompt`
|
79
61
|
program
|
80
62
|
.command("systemprompt")
|
@@ -85,12 +67,12 @@ program
|
|
85
67
|
// open `systemprompt.txt` file by system text default editor
|
86
68
|
const copyFile = __dirname + "/systemprompt.txt";
|
87
69
|
// user home path
|
88
|
-
const promptFile =
|
89
|
-
|
70
|
+
const promptFile = findConfig("systemprompt.txt");
|
71
|
+
copyFileIfNotExists(promptFile, copyFile, reset);
|
90
72
|
if (reset) {
|
91
73
|
console.log("systemprompt.txt reset to default");
|
92
74
|
}
|
93
|
-
|
75
|
+
openFileByDefault(promptFile);
|
94
76
|
});
|
95
77
|
// program command `userdict` with help text `open/edit user dictionary`
|
96
78
|
program
|
@@ -102,13 +84,13 @@ program
|
|
102
84
|
// open `dictionary.json` file by system text default editor
|
103
85
|
const copyFile = __dirname + "/dictionary.json";
|
104
86
|
// user home path
|
105
|
-
const dictFile =
|
87
|
+
const dictFile = findConfig("dictionary.json");
|
106
88
|
if (explore) {
|
107
89
|
// open user dictionary directory
|
108
|
-
return
|
90
|
+
return openFileExplorer(dictFile);
|
109
91
|
}
|
110
|
-
|
111
|
-
|
92
|
+
copyFileIfNotExists(dictFile, copyFile);
|
93
|
+
openFileByDefault(dictFile);
|
112
94
|
});
|
113
95
|
// program command `remove` with help text `remove po entries by options`
|
114
96
|
program
|
@@ -122,7 +104,7 @@ program
|
|
122
104
|
.option("-tnf, --translated-not-fuzzy", "remove translated not fuzzy entries")
|
123
105
|
.option("-ft, --fuzzy-translated", "remove fuzzy translated entries")
|
124
106
|
.option("-rc, --reference-contains <text>", "remove entries whose reference contains text, text can be a regular expression like /text/ig")
|
125
|
-
.action((args) =>
|
107
|
+
.action(async (args) => {
|
126
108
|
const { po, fuzzy, obsolete, untranslated, translated, translatedNotFuzzy, fuzzyTranslated, referenceContains } = args;
|
127
109
|
const options = {
|
128
110
|
fuzzy,
|
@@ -134,9 +116,9 @@ program
|
|
134
116
|
referenceContains,
|
135
117
|
};
|
136
118
|
const output = args.output || po;
|
137
|
-
const translations =
|
138
|
-
|
119
|
+
const translations = await parsePo(po);
|
120
|
+
await compilePo(removeByOptions(translations, options), output);
|
139
121
|
console.log("done");
|
140
|
-
})
|
122
|
+
});
|
141
123
|
program.parse(process.argv);
|
142
124
|
//# sourceMappingURL=index.js.map
|
package/lib/src/manipulate.js
CHANGED
@@ -1,52 +1,48 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.removeByOptions = void 0;
|
4
1
|
/**
|
5
2
|
* remove entity by options
|
6
3
|
*/
|
7
|
-
function removeByOptions(potrans, options) {
|
8
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
4
|
+
export function removeByOptions(potrans, options) {
|
9
5
|
const fuzzyRegx = /\bfuzzy\b/;
|
10
6
|
const obsoleteRegx = /\bobsolete\b/;
|
11
|
-
const refRegMatch =
|
12
|
-
? /^\/([^\/]+)\/([igmsuy]*)/.exec(options
|
7
|
+
const refRegMatch = options?.referenceContains
|
8
|
+
? /^\/([^\/]+)\/([igmsuy]*)/.exec(options?.referenceContains)
|
13
9
|
: null;
|
14
10
|
const refRegx = refRegMatch ? new RegExp(refRegMatch[1], refRegMatch[2]) : null;
|
15
11
|
for (const [ctx, entries] of Object.entries(potrans.translations)) {
|
16
12
|
for (const [msgid, entry] of Object.entries(entries)) {
|
17
13
|
const msgstr = entry.msgstr.join("\n");
|
18
14
|
// remove fuzzy
|
19
|
-
if (
|
15
|
+
if (options?.fuzzy && fuzzyRegx.test(entry.comments?.flag || "")) {
|
20
16
|
delete potrans.translations[ctx][msgid];
|
21
17
|
}
|
22
18
|
// remove obsolete
|
23
|
-
if (
|
19
|
+
if (options?.obsolete && obsoleteRegx.test(entry.comments?.flag || "")) {
|
24
20
|
delete potrans.translations[ctx][msgid];
|
25
21
|
}
|
26
22
|
// remove untranslated
|
27
|
-
if (
|
23
|
+
if (options?.untranslated && msgstr.length === 0) {
|
28
24
|
delete potrans.translations[ctx][msgid];
|
29
25
|
}
|
30
26
|
// remove translated
|
31
|
-
if (
|
27
|
+
if (options?.translated && msgstr.length > 0) {
|
32
28
|
delete potrans.translations[ctx][msgid];
|
33
29
|
}
|
34
30
|
// remove translated not fuzzy
|
35
|
-
if (
|
31
|
+
if (options?.translatedNotFuzzy && msgstr.length > 0 && !fuzzyRegx.test(entry.comments?.flag || "")) {
|
36
32
|
delete potrans.translations[ctx][msgid];
|
37
33
|
}
|
38
34
|
// remove fuzzy translated
|
39
|
-
if (
|
35
|
+
if (options?.fuzzyTranslated && msgstr.length > 0 && fuzzyRegx.test(entry.comments?.flag || "")) {
|
40
36
|
delete potrans.translations[ctx][msgid];
|
41
37
|
}
|
42
38
|
// remove reference contains
|
43
|
-
if (options
|
39
|
+
if (options?.referenceContains) {
|
44
40
|
if (refRegx) {
|
45
|
-
if (
|
41
|
+
if (entry.comments?.reference && refRegx.test(entry.comments?.reference)) {
|
46
42
|
delete potrans.translations[ctx][msgid];
|
47
43
|
}
|
48
44
|
}
|
49
|
-
else if (
|
45
|
+
else if (entry.comments?.reference?.includes(options?.referenceContains)) {
|
50
46
|
delete potrans.translations[ctx][msgid];
|
51
47
|
}
|
52
48
|
}
|
@@ -54,5 +50,4 @@ function removeByOptions(potrans, options) {
|
|
54
50
|
}
|
55
51
|
return potrans;
|
56
52
|
}
|
57
|
-
exports.removeByOptions = removeByOptions;
|
58
53
|
//# sourceMappingURL=manipulate.js.map
|
package/lib/src/sync.js
CHANGED
@@ -1,33 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
exports.sync = void 0;
|
13
|
-
const utils_1 = require("./utils");
|
14
|
-
function sync(po, pot) {
|
15
|
-
return __awaiter(this, void 0, void 0, function* () {
|
16
|
-
const potrans = yield (0, utils_1.parsePo)(po);
|
17
|
-
const potrans2 = yield (0, utils_1.parsePo)(pot);
|
18
|
-
for (const [ctx, entries] of Object.entries(potrans2.translations)) {
|
19
|
-
// copy msgstr from potrans to potrans2
|
20
|
-
for (const [msgid, _] of Object.entries(entries)) {
|
21
|
-
if (potrans.translations[ctx] &&
|
22
|
-
potrans.translations[ctx][msgid] &&
|
23
|
-
potrans.translations[ctx][msgid].msgstr[0]) {
|
24
|
-
potrans2.translations[ctx][msgid] = potrans.translations[ctx][msgid];
|
25
|
-
}
|
1
|
+
import { compilePo, parsePo } from "./utils.js";
|
2
|
+
export async function sync(po, pot) {
|
3
|
+
const potrans = await parsePo(po);
|
4
|
+
const potrans2 = await parsePo(pot);
|
5
|
+
for (const [ctx, entries] of Object.entries(potrans2.translations)) {
|
6
|
+
// copy msgstr from potrans to potrans2
|
7
|
+
for (const [msgid, _] of Object.entries(entries)) {
|
8
|
+
if (potrans.translations[ctx] &&
|
9
|
+
potrans.translations[ctx][msgid] &&
|
10
|
+
potrans.translations[ctx][msgid].msgstr[0]) {
|
11
|
+
potrans2.translations[ctx][msgid] = potrans.translations[ctx][msgid];
|
26
12
|
}
|
27
13
|
}
|
28
|
-
|
29
|
-
|
30
|
-
|
14
|
+
}
|
15
|
+
potrans.translations = potrans2.translations;
|
16
|
+
await compilePo(potrans, po);
|
31
17
|
}
|
32
|
-
exports.sync = sync;
|
33
18
|
//# sourceMappingURL=sync.js.map
|
package/lib/src/systemprompt.txt
CHANGED
@@ -1,19 +1,31 @@
|
|
1
|
-
You are a language translation expert.
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
You are a language translation expert. You will translate text from one language to another, following these guidelines:
|
2
|
+
|
3
|
+
1. **Language Codes**:
|
4
|
+
- If provided with an ISO 639 two-letter language code (e.g., `de`), use the language’s main dialect (e.g., `de` is equivalent to `de_DE`).
|
5
|
+
- If provided with both an ISO 639 language code and an ISO 3166 country code (e.g., `es_MX`), translate into that specific regional dialect.
|
6
|
+
|
7
|
+
2. **Placeholder Handling**:
|
8
|
+
- Maintain the positions of placeholders (e.g., %s, %d, {example}) in the translated text. Do not translate placeholders.
|
9
|
+
|
10
|
+
3. **Formatting**:
|
11
|
+
- Preserve the formatting of untranslatable portions.
|
12
|
+
- Retain any whitespace at the beginning or end of the message.
|
13
|
+
- Add or omit a period (.) at the end of your translation to match the incoming message.
|
14
|
+
|
15
|
+
4. **XML Tags**:
|
16
|
+
- Input messages will be wrapped in `<translate>` XML tags.
|
17
|
+
- Respond with the translated message wrapped in `<translated>` XML tags.
|
18
|
+
|
19
|
+
5. **Quality**:
|
20
|
+
- Translate in a colloquial, professional, and elegant manner without sounding like a machine translation.
|
21
|
+
|
22
|
+
6. **Error Handling**:
|
23
|
+
- If you cannot reliably translate a message, respond without `<translated>` XML tags and provide a short reason why.
|
24
|
+
|
25
|
+
**Examples**:
|
26
|
+
- Input: `<translate>This is a message. </translate>`
|
27
|
+
- Output: `<translated>Este es un mensaje. </translated>`
|
28
|
+
- Input: `<translate> Hello %s</translate>`
|
29
|
+
- Output: `<translated> Hola %s</translated>`
|
30
|
+
|
31
|
+
Do not answer questions or explain concepts. Only provide translations within `<translated>` XML tags unless you need to respond with a short error reason.
|
package/lib/src/translate.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { GetTextTranslation } from "gettext-parser";
|
2
|
-
import {
|
3
|
-
export declare function init(force?: boolean):
|
4
|
-
export declare function translate(text: string, src: string, lang: string, model: string, comments: GetTextTranslation["comments"] | undefined, contextFile: string):
|
2
|
+
import { OpenAI } from "openai";
|
3
|
+
export declare function init(force?: boolean): OpenAI;
|
4
|
+
export declare function translate(text: string, src: string, lang: string, model: string, comments: GetTextTranslation["comments"] | undefined, contextFile: string): import("openai/core.mjs").APIPromise<OpenAI.Chat.Completions.ChatCompletion>;
|
5
5
|
export declare function translatePo(model: string, po: string, source: string, lang: string, verbose: boolean, output: string, contextFile: string): Promise<void>;
|
6
6
|
export declare function translatePoDir(model: string | undefined, dir: string, source: string, lang: string, verbose: boolean, contextFile: string): Promise<void>;
|
package/lib/src/translate.js
CHANGED
@@ -1,49 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
});
|
10
|
-
};
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
-
exports.translatePoDir = exports.translatePo = exports.translate = exports.init = void 0;
|
13
|
-
const fs = require("fs");
|
14
|
-
const openai_1 = require("openai");
|
15
|
-
const path_1 = require("path");
|
16
|
-
const pkg = require("../package.json");
|
17
|
-
const utils_1 = require("./utils");
|
1
|
+
import * as fs from "fs";
|
2
|
+
import { OpenAI } from "openai";
|
3
|
+
import path from "path";
|
4
|
+
import { fileURLToPath } from "url";
|
5
|
+
import pkg from "../package.json" with { type: "json" };
|
6
|
+
import { compilePo, copyFileIfNotExists, findConfig, parsePo, printProgress } from "./utils.js";
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
8
|
+
const __dirname = path.dirname(__filename);
|
18
9
|
let _openai;
|
19
10
|
let _systemprompt;
|
20
11
|
let _userdict;
|
21
|
-
function init(force) {
|
12
|
+
export function init(force) {
|
22
13
|
if (!_openai || force) {
|
23
|
-
|
14
|
+
let configuration = {
|
24
15
|
apiKey: process.env.OPENAI_API_KEY,
|
25
|
-
}
|
16
|
+
};
|
17
|
+
_openai = new OpenAI(configuration);
|
26
18
|
if (process.env.OPENAI_API_HOST) {
|
27
|
-
|
19
|
+
_openai.baseURL = process.env.OPENAI_API_HOST.replace(/\/+$/, "") + "/v1";
|
28
20
|
}
|
29
|
-
_openai = new openai_1.OpenAIApi(configuration);
|
30
21
|
}
|
31
22
|
// load systemprompt.txt from homedir
|
32
23
|
if (!_systemprompt || force) {
|
33
|
-
const systemprompt =
|
34
|
-
|
24
|
+
const systemprompt = findConfig("systemprompt.txt");
|
25
|
+
copyFileIfNotExists(systemprompt, path.join(__dirname, "systemprompt.txt"));
|
35
26
|
_systemprompt = fs.readFileSync(systemprompt, "utf-8");
|
36
27
|
}
|
37
28
|
// load dictionary.json from homedir
|
38
29
|
if (!_userdict || force) {
|
39
|
-
const userdict =
|
40
|
-
|
30
|
+
const userdict = findConfig("dictionary.json");
|
31
|
+
copyFileIfNotExists(userdict, path.join(__dirname, "dictionary.json"));
|
41
32
|
_userdict = { "default": JSON.parse(fs.readFileSync(userdict, "utf-8")) };
|
42
33
|
}
|
43
34
|
return _openai;
|
44
35
|
}
|
45
|
-
|
46
|
-
function translate(text, src, lang, model, comments, contextFile) {
|
36
|
+
export function translate(text, src, lang, model, comments, contextFile) {
|
47
37
|
const lang_code = lang.toLowerCase().trim().replace(/[\W_]+/g, "-");
|
48
38
|
const dicts = Object.entries(_userdict[lang_code] || _userdict["default"])
|
49
39
|
.filter(([k, _]) => text.toLowerCase().includes(k.toLowerCase()))
|
@@ -58,8 +48,8 @@ function translate(text, src, lang, model, comments, contextFile) {
|
|
58
48
|
var context = "";
|
59
49
|
if (contextFile !== undefined)
|
60
50
|
context = "\n\n" + fs.readFileSync(contextFile, "utf-8");
|
61
|
-
return _openai.
|
62
|
-
model,
|
51
|
+
return _openai.chat.completions.create({
|
52
|
+
model: model,
|
63
53
|
temperature: 0.1,
|
64
54
|
messages: [
|
65
55
|
{
|
@@ -85,114 +75,106 @@ function translate(text, src, lang, model, comments, contextFile) {
|
|
85
75
|
timeout: 20000,
|
86
76
|
});
|
87
77
|
}
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
78
|
+
export async function translatePo(model, po, source, lang, verbose, output, contextFile) {
|
79
|
+
const potrans = await parsePo(po);
|
80
|
+
if (!lang)
|
81
|
+
lang = potrans.headers["Language"];
|
82
|
+
if (!lang) {
|
83
|
+
console.error("No language specified via po file or args");
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
// try to load dictionary by lang-code if it not loaded
|
87
|
+
const lang_code = lang.toLowerCase().trim().replace(/[\W_]+/g, "-");
|
88
|
+
if (!_userdict[lang_code]) {
|
89
|
+
const lang_dic_file = findConfig(`dictionary-${lang_code}.json`);
|
90
|
+
if (fs.existsSync(lang_dic_file)) {
|
91
|
+
_userdict[lang_code] = JSON.parse(fs.readFileSync(lang_dic_file, "utf-8"));
|
92
|
+
console.log(`dictionary-${lang_code}.json is loaded.`);
|
98
93
|
}
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
94
|
+
}
|
95
|
+
const list = [];
|
96
|
+
const trimRegx = /(?:^ )|(?: $)/;
|
97
|
+
let trimed = false;
|
98
|
+
for (const [ctx, entries] of Object.entries(potrans.translations)) {
|
99
|
+
for (const [msgid, trans] of Object.entries(entries)) {
|
100
|
+
if (msgid == "")
|
101
|
+
continue;
|
102
|
+
if (!trans.msgstr[0]) {
|
103
|
+
list.push(trans);
|
104
|
+
continue;
|
106
105
|
}
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
let trimed = false;
|
111
|
-
for (const [ctx, entries] of Object.entries(potrans.translations)) {
|
112
|
-
for (const [msgid, trans] of Object.entries(entries)) {
|
113
|
-
if (msgid == "")
|
114
|
-
continue;
|
115
|
-
if (!trans.msgstr[0]) {
|
116
|
-
list.push(trans);
|
117
|
-
continue;
|
118
|
-
}
|
119
|
-
else if (trimRegx.test(trans.msgstr[0])) {
|
120
|
-
trimed = true;
|
121
|
-
trans.msgstr[0] = trans.msgstr[0].trim();
|
122
|
-
}
|
106
|
+
else if (trimRegx.test(trans.msgstr[0])) {
|
107
|
+
trimed = true;
|
108
|
+
trans.msgstr[0] = trans.msgstr[0].trim();
|
123
109
|
}
|
124
110
|
}
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
111
|
+
}
|
112
|
+
if (trimed) {
|
113
|
+
await compilePo(potrans, po);
|
114
|
+
}
|
115
|
+
if (list.length == 0) {
|
116
|
+
console.log("done.");
|
117
|
+
return;
|
118
|
+
}
|
119
|
+
potrans.headers["Last-Translator"] = `gpt-po v${pkg.version}`;
|
120
|
+
let err429 = false;
|
121
|
+
let modified = false;
|
122
|
+
for (let i = 0; i < list.length; i++) {
|
123
|
+
if (i == 0)
|
124
|
+
printProgress(i, list.length);
|
125
|
+
if (err429) {
|
126
|
+
// sleep for 20 seconds.
|
127
|
+
await new Promise((resolve) => setTimeout(resolve, 20000));
|
131
128
|
}
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
if (
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
yield new Promise((resolve) => setTimeout(resolve, 20000));
|
129
|
+
const trans = list[i];
|
130
|
+
try {
|
131
|
+
const res = await translate(trans.msgid, source, lang, model, trans.comments, contextFile);
|
132
|
+
var translated = res.choices[0].message?.content || trans.msgstr[0];
|
133
|
+
if (!translated.startsWith('<translated>') && !translated.endsWith('</translated>')) {
|
134
|
+
// We got an error response
|
135
|
+
console.log("Error: Unable to translate string [" + trans.msgid + "]. Bot says [" + translated + "]");
|
136
|
+
continue;
|
141
137
|
}
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
console.log("Error: Unable to translate string [" + trans.msgid + "]. Bot says [" + translated + "]");
|
149
|
-
continue;
|
150
|
-
}
|
151
|
-
// We got a valid translation response
|
152
|
-
trans.msgstr[0] = translated.replace('<translated>', '').replace('</translated>', '');
|
153
|
-
modified = true;
|
154
|
-
if (verbose) {
|
155
|
-
console.log(trans.msgid);
|
156
|
-
console.log(trans.msgstr[0]);
|
157
|
-
}
|
158
|
-
(0, utils_1.printProgress)(i + 1, list.length);
|
159
|
-
yield (0, utils_1.compilePo)(potrans, output || po);
|
138
|
+
// We got a valid translation response
|
139
|
+
trans.msgstr[0] = translated.replace('<translated>', '').replace('</translated>', '');
|
140
|
+
modified = true;
|
141
|
+
if (verbose) {
|
142
|
+
console.log(trans.msgid);
|
143
|
+
console.log(trans.msgstr[0]);
|
160
144
|
}
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
console.log(error.response.data);
|
171
|
-
}
|
145
|
+
printProgress(i + 1, list.length);
|
146
|
+
await compilePo(potrans, output || po);
|
147
|
+
}
|
148
|
+
catch (error) {
|
149
|
+
if (error.response) {
|
150
|
+
if (error.response.status == 429) {
|
151
|
+
// caused by rate limit exceeded, should sleep for 20 seconds.
|
152
|
+
err429 = true;
|
153
|
+
--i;
|
172
154
|
}
|
173
155
|
else {
|
174
|
-
console.error(error.
|
175
|
-
|
176
|
-
|
177
|
-
|
156
|
+
console.error(error.response.status);
|
157
|
+
console.log(error.response.data);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
else {
|
161
|
+
console.error(error.message);
|
162
|
+
if (error.code == "ECONNABORTED") {
|
163
|
+
console.log('you may need to set "HTTPS_PROXY" to reach openai api.');
|
178
164
|
}
|
179
165
|
}
|
180
166
|
}
|
181
|
-
|
182
|
-
|
167
|
+
}
|
168
|
+
console.log("done.");
|
183
169
|
}
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
console.log(`translating ${po}`);
|
192
|
-
yield translatePo(model, po, source, lang, verbose, po, contextFile);
|
193
|
-
}
|
170
|
+
export async function translatePoDir(model = "gpt-3.5-turbo", dir, source, lang, verbose, contextFile) {
|
171
|
+
const files = fs.readdirSync(dir);
|
172
|
+
for (const file of files) {
|
173
|
+
if (file.endsWith(".po")) {
|
174
|
+
const po = path.join(dir, file);
|
175
|
+
console.log(`translating ${po}`);
|
176
|
+
await translatePo(model, po, source, lang, verbose, po, contextFile);
|
194
177
|
}
|
195
|
-
}
|
178
|
+
}
|
196
179
|
}
|
197
|
-
exports.translatePoDir = translatePoDir;
|
198
180
|
//# sourceMappingURL=translate.js.map
|
package/lib/src/utils.js
CHANGED
@@ -1,18 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
const gettext_parser_1 = require("gettext-parser");
|
7
|
-
const os_1 = require("os");
|
8
|
-
const path = require("path");
|
1
|
+
import { exec, spawn } from "child_process";
|
2
|
+
import * as fs from "fs";
|
3
|
+
import { po } from "gettext-parser";
|
4
|
+
import { homedir, platform } from "os";
|
5
|
+
import * as path from "path";
|
9
6
|
/**
|
10
7
|
* copy source file to destination file if destination file does not exist
|
11
8
|
* @param file destination file path
|
12
9
|
* @param copyFile source file path
|
13
10
|
* @param force force copy file
|
14
11
|
*/
|
15
|
-
function copyFileIfNotExists(file, copyFile, force = false) {
|
12
|
+
export function copyFileIfNotExists(file, copyFile, force = false) {
|
16
13
|
// make sure the directory exists
|
17
14
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
18
15
|
// check if file exists else create it
|
@@ -27,28 +24,25 @@ function copyFileIfNotExists(file, copyFile, force = false) {
|
|
27
24
|
fs.copyFileSync(copyFile, file);
|
28
25
|
}
|
29
26
|
}
|
30
|
-
|
31
|
-
function openFileByDefault(filePath) {
|
27
|
+
export function openFileByDefault(filePath) {
|
32
28
|
// Use the 'open' command on macOS or 'start' command on Windows to open the file with the default system editor
|
33
29
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
34
30
|
// Spawn a new process for the default editor and pass the file name as an argument
|
35
|
-
|
31
|
+
spawn(command, [filePath], { shell: true });
|
36
32
|
}
|
37
|
-
|
38
|
-
function parsePo(poFile, defaultCharset) {
|
33
|
+
export function parsePo(poFile, defaultCharset) {
|
39
34
|
// read poFile as buffer, then parse it
|
40
35
|
return new Promise((resolve, reject) => {
|
41
36
|
fs.readFile(poFile, (err, buffer) => {
|
42
37
|
if (err)
|
43
38
|
reject(err);
|
44
|
-
var result =
|
39
|
+
var result = po.parse(buffer, defaultCharset ?? "utf-8");
|
45
40
|
resolve(result);
|
46
41
|
});
|
47
42
|
});
|
48
43
|
}
|
49
|
-
|
50
|
-
|
51
|
-
const buffer = gettext_parser_1.po.compile(data, { foldLength: 120 });
|
44
|
+
export function compilePo(data, poFile) {
|
45
|
+
const buffer = po.compile(data, { foldLength: 120 });
|
52
46
|
return new Promise((resolve, reject) => {
|
53
47
|
fs.writeFile(poFile, buffer, (err) => {
|
54
48
|
if (err)
|
@@ -57,8 +51,7 @@ function compilePo(data, poFile) {
|
|
57
51
|
});
|
58
52
|
});
|
59
53
|
}
|
60
|
-
|
61
|
-
function printProgress(progress, total, extra) {
|
54
|
+
export function printProgress(progress, total, extra) {
|
62
55
|
const percent = Math.floor((progress / total) * 100);
|
63
56
|
const bar = Array(Math.floor(percent / 5))
|
64
57
|
.fill("█")
|
@@ -68,8 +61,7 @@ function printProgress(progress, total, extra) {
|
|
68
61
|
.join("");
|
69
62
|
process.stdout.write(`\r${bar}${dots} ${percent}% ${progress}/${total} ${extra || ""}`);
|
70
63
|
}
|
71
|
-
|
72
|
-
function gitRootDir(dir) {
|
64
|
+
export function gitRootDir(dir) {
|
73
65
|
// if dir is not provided, use current working directory
|
74
66
|
dir = dir || process.cwd();
|
75
67
|
// check if dir is a git repository
|
@@ -87,7 +79,6 @@ function gitRootDir(dir) {
|
|
87
79
|
}
|
88
80
|
}
|
89
81
|
}
|
90
|
-
exports.gitRootDir = gitRootDir;
|
91
82
|
/**
|
92
83
|
* find config file in the following order:
|
93
84
|
* 1. current working directory of the Node.js process
|
@@ -96,10 +87,10 @@ exports.gitRootDir = gitRootDir;
|
|
96
87
|
* @param fileName
|
97
88
|
* @returns full path of the config file
|
98
89
|
*/
|
99
|
-
function findConfig(fileName) {
|
90
|
+
export function findConfig(fileName) {
|
100
91
|
const currentDir = process.cwd();
|
101
92
|
const gitDir = gitRootDir() || currentDir;
|
102
|
-
const homeDir =
|
93
|
+
const homeDir = homedir();
|
103
94
|
const filePaths = [
|
104
95
|
path.join(currentDir, ".gpt-po", fileName),
|
105
96
|
path.join(currentDir, fileName),
|
@@ -115,22 +106,20 @@ function findConfig(fileName) {
|
|
115
106
|
// if no file exists, return the default one
|
116
107
|
return path.join(homeDir, ".gpt-po", fileName);
|
117
108
|
}
|
118
|
-
exports.findConfig = findConfig;
|
119
109
|
/**
|
120
110
|
* open file explorer by platform
|
121
111
|
* @param location folder or file path
|
122
112
|
*/
|
123
|
-
function openFileExplorer(location) {
|
124
|
-
if (
|
125
|
-
|
113
|
+
export function openFileExplorer(location) {
|
114
|
+
if (platform() === 'win32') {
|
115
|
+
exec(`explorer.exe "${path.dirname(location)}"`);
|
126
116
|
}
|
127
|
-
else if (
|
128
|
-
|
117
|
+
else if (platform() === 'darwin') {
|
118
|
+
exec(`open "${path.dirname(location)}"`);
|
129
119
|
}
|
130
120
|
else {
|
131
121
|
// Assuming a Linux-based system
|
132
|
-
|
122
|
+
exec(`xdg-open "${path.dirname(location)}"`);
|
133
123
|
}
|
134
124
|
}
|
135
|
-
exports.openFileExplorer = openFileExplorer;
|
136
125
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
{
|
2
2
|
"name": "gpt-po",
|
3
|
-
"version": "1.1.
|
3
|
+
"version": "1.1.2",
|
4
4
|
"description": "command tool for translate po files by gpt",
|
5
5
|
"main": "lib/src/index.js",
|
6
|
+
"type": "module",
|
6
7
|
"bin": {
|
7
8
|
"gpt-po": "lib/src/index.js"
|
8
9
|
},
|
@@ -33,23 +34,23 @@
|
|
33
34
|
"openai"
|
34
35
|
],
|
35
36
|
"devDependencies": {
|
36
|
-
"@types/gettext-parser": "^4.0.
|
37
|
-
"@types/jest": "^29.5.
|
38
|
-
"jest": "^29.
|
37
|
+
"@types/gettext-parser": "^4.0.4",
|
38
|
+
"@types/jest": "^29.5.12",
|
39
|
+
"jest": "^29.7.0",
|
39
40
|
"ncp": "^2.0.0",
|
40
|
-
"prettier": "^3.
|
41
|
-
"rimraf": "^
|
42
|
-
"ts-jest": "^29.
|
41
|
+
"prettier": "^3.3.3",
|
42
|
+
"rimraf": "^6.0.1",
|
43
|
+
"ts-jest": "^29.2.5",
|
43
44
|
"tslint": "^6.1.3",
|
44
45
|
"tslint-config-prettier": "^1.18.0",
|
45
|
-
"typescript": "^5.
|
46
|
+
"typescript": "^5.5.4"
|
46
47
|
},
|
47
48
|
"dependencies": {
|
48
|
-
"commander": "^
|
49
|
-
"gettext-parser": "^
|
50
|
-
"openai": "^
|
49
|
+
"commander": "^12.1.0",
|
50
|
+
"gettext-parser": "^8.0.0",
|
51
|
+
"openai": "^4.56.0"
|
51
52
|
},
|
52
53
|
"engines": {
|
53
|
-
"node": ">=
|
54
|
+
"node": ">=18.0.0"
|
54
55
|
}
|
55
56
|
}
|
File without changes
|