gpt-po 1.1.1 → 1.1.2
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/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
|