gpt-po 1.1.2 → 1.2.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/.github/FUNDING.yml +1 -0
- package/CHANGELOG.md +17 -0
- package/README.md +13 -3
- package/README_zh-CN.md +13 -3
- package/lib/package.json +1 -1
- package/lib/src/index.d.ts +1 -1
- package/lib/src/index.js +47 -37
- package/lib/src/sync.d.ts +2 -1
- package/lib/src/sync.js +2 -2
- package/lib/src/systemprompt.txt +1 -31
- package/lib/src/translate.d.ts +4 -3
- package/lib/src/translate.js +105 -67
- package/lib/src/userprompt.txt +27 -0
- package/lib/src/utils.d.ts +6 -1
- package/lib/src/utils.js +6 -6
- package/package.json +1 -1
@@ -0,0 +1 @@
|
|
1
|
+
buy_me_a_coffee: ryanhex
|
package/CHANGELOG.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [1.2.0] - 2024-11-26
|
4
|
+
- Submit multiple entries per chat to boost speed and reduce tokens
|
5
|
+
- Command `systemprompt` has been removed
|
6
|
+
- Bug fixes
|
7
|
+
|
8
|
+
## [1.1.2] - 2024-11-12
|
9
|
+
- Update command options for OpenAI model
|
10
|
+
- Dependency updates, target es2022, module(Nodejs 18+)
|
11
|
+
|
12
|
+
## [1.0.11] - 2023-10-26
|
13
|
+
- Add `gpt-po remove` command
|
14
|
+
- improve prompt for translation
|
15
|
+
|
16
|
+
## [1.0.9] - 2023-09-08
|
17
|
+
- Can add dictionaries for each languages
|
package/README.md
CHANGED
@@ -7,6 +7,8 @@ Translation tool for gettext (po) files that supports custom system prompts and
|
|
7
7
|
|
8
8
|
Read in other languages: English | [简体中文](./README_zh-CN.md)
|
9
9
|
|
10
|
+
<a href="https://buymeacoffee.com/ryanhex" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-red.png" alt="Buy Me A Coffee" height="41" width="174"></a>
|
11
|
+
|
10
12
|
## Installation
|
11
13
|
|
12
14
|
```
|
@@ -25,8 +27,6 @@ Set `OPENAI_API_KEY` before using this tool.
|
|
25
27
|
- `gpt-po --dir .` Translate all po files in current directory to a designated target language.
|
26
28
|
- `gpt-po userdict` Modify or view user dictionaries
|
27
29
|
- `gpt-po userdict --explore` Explore user dictionaries, if you want add new dictionaries or modify existing dictionaries. dictionaries can be named as `dictionary-<lang>.json`, for example, `dictionary-zh.json` is the dictionary for Simplified Chinese.
|
28
|
-
- `gpt-po systemprompt` Modify or view system prompts, if you are not sure how to use it, you can leave it alone
|
29
|
-
- `gpt-po systemprompt --reset` Reset system prompts
|
30
30
|
|
31
31
|
```
|
32
32
|
Usage: gpt-po [options] [command]
|
@@ -40,7 +40,6 @@ Options:
|
|
40
40
|
Commands:
|
41
41
|
translate [options] translate po file (default command)
|
42
42
|
sync [options] update po from pot file
|
43
|
-
systemprompt [options] open/edit system prompt
|
44
43
|
userdict [options] open/edit user dictionary
|
45
44
|
remove [options] remove po entries by options
|
46
45
|
help [command] display help for command
|
@@ -81,3 +80,14 @@ Options:
|
|
81
80
|
-rc, --reference-contains <text> remove entries whose reference contains text, text can be a regular expression like /text/ig
|
82
81
|
-h, --help display help for command
|
83
82
|
```
|
83
|
+
|
84
|
+
```
|
85
|
+
Usage: gpt-po sync [options]
|
86
|
+
|
87
|
+
update po from pot file
|
88
|
+
|
89
|
+
Options:
|
90
|
+
--po <file> po file path
|
91
|
+
--pot <file> pot file path
|
92
|
+
-h, --help display help for command
|
93
|
+
```
|
package/README_zh-CN.md
CHANGED
@@ -7,6 +7,8 @@ gettext(po)文件翻译工具,支持自定义系统提示词和用户字典,
|
|
7
7
|
|
8
8
|
使用其他语言阅读:[English](./README.md) | 简体中文
|
9
9
|
|
10
|
+
<a href="https://buymeacoffee.com/ryanhex" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-red.png" alt="请我喝杯咖啡" height="41" width="174"></a>
|
11
|
+
|
10
12
|
## 安装
|
11
13
|
|
12
14
|
```
|
@@ -27,8 +29,6 @@ npm install gpt-po
|
|
27
29
|
- `gpt-po --dir .` 将当前目录下的所有 po 文件翻译成目标语言。
|
28
30
|
- `gpt-po userdict` 修改或查看用户词典。
|
29
31
|
- `gpt-po userdict --explore` 浏览用户词典,如果您想添加新词典或修改现有词典,词典可以命名为 `dictionary-<lang>.json`,例如 `dictionary-zh.json` 是简体中文词典。
|
30
|
-
- `gpt-po systemprompt` 修改或查看系统提示,如果您不确定如何使用它,可以忽略。
|
31
|
-
- `gpt-po systemprompt --reset` 重置系统提示。
|
32
32
|
|
33
33
|
```
|
34
34
|
用法: gpt-po [options] [command]
|
@@ -42,7 +42,6 @@ npm install gpt-po
|
|
42
42
|
命令:
|
43
43
|
translate [options] 翻译 po 文件(默认命令)
|
44
44
|
sync [options] 根据 pot 文件更新 po 文件
|
45
|
-
systemprompt [options] 打开/编辑系统提示
|
46
45
|
userdict [options] 打开/编辑用户词典
|
47
46
|
remove [options] 通过选项删除 po 条目
|
48
47
|
help [command] 显示命令帮助
|
@@ -83,3 +82,14 @@ npm install gpt-po
|
|
83
82
|
-rc, --reference-contains <text> 删除引用包含文本的条目,文本可以是正则表达式,例如 /text/ig
|
84
83
|
-h, --help 显示命令帮助
|
85
84
|
```
|
85
|
+
|
86
|
+
```
|
87
|
+
用法: gpt-po sync [options]
|
88
|
+
|
89
|
+
从 pot 文件中更新条目到 po 文件
|
90
|
+
|
91
|
+
Options:
|
92
|
+
--po <file> po 文件路径
|
93
|
+
--pot <file> pot 文件路径
|
94
|
+
-h, --help 显示命令帮助
|
95
|
+
```
|
package/lib/package.json
CHANGED
package/lib/src/index.d.ts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
#!/usr/bin/env node
|
1
|
+
#!/usr/bin/env node --no-warnings=ExperimentalWarning
|
2
2
|
export {};
|
package/lib/src/index.js
CHANGED
@@ -1,22 +1,38 @@
|
|
1
|
-
#!/usr/bin/env node
|
1
|
+
#!/usr/bin/env node --no-warnings=ExperimentalWarning
|
2
2
|
import { Command, Option } from "commander";
|
3
3
|
import path from "path";
|
4
4
|
import { fileURLToPath } from "url";
|
5
5
|
import pkg from "../package.json" with { type: "json" };
|
6
|
+
import { removeByOptions } from "./manipulate.js";
|
6
7
|
import { sync } from "./sync.js";
|
7
8
|
import { init, translatePo, translatePoDir } from "./translate.js";
|
8
|
-
import {
|
9
|
-
import { removeByOptions } from "./manipulate.js";
|
9
|
+
import { compilePo, copyFileIfNotExists, findConfig, openFileByDefault, openFileExplorer, parsePo } from "./utils.js";
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
11
11
|
const __dirname = path.dirname(__filename);
|
12
12
|
const program = new Command();
|
13
13
|
program.name(pkg.name).version(pkg.version).description(pkg.description);
|
14
|
-
|
15
|
-
.
|
14
|
+
const getCompileOptions = (args) => {
|
15
|
+
const foldLength = args.poFoldLen === "false" ? 0 : parseInt(args.poFoldLen);
|
16
|
+
const sort = args.poSort;
|
17
|
+
const escapeCharacters = args.poEscChars;
|
18
|
+
if (isNaN(foldLength)) {
|
19
|
+
console.error("--po-fold-len must be a number or false");
|
20
|
+
process.exit(1);
|
21
|
+
}
|
22
|
+
return { foldLength, sort, escapeCharacters };
|
23
|
+
};
|
24
|
+
class SharedOptionsCommand extends Command {
|
25
|
+
addCompileOptions() {
|
26
|
+
return this.option("--po-fold-len <length>", "a gettext compile option, the length at which to fold message strings into newlines, set to 0 or false to disable folding", "120")
|
27
|
+
.option("--po-sort", "a gettext compile option, sort entries by msgid", false)
|
28
|
+
.option("--po-esc-chars", "a gettext compile option, escape characters in output, if false, will skip escape newlines and quotes characters functionality", true);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
const translateCommand = new SharedOptionsCommand("translate")
|
16
32
|
.description("translate po file (default command)")
|
17
33
|
.addOption(new Option("-k, --key <key>", "openai api key").env("OPENAI_API_KEY"))
|
18
34
|
.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"))
|
35
|
+
.addOption(new Option("--model <model>", "openai model").env("OPENAI_MODEL").default("gpt-4o-mini"))
|
20
36
|
.addOption(new Option("--po <file>", "po file path").conflicts("dir"))
|
21
37
|
.addOption(new Option("--dir <dir>", "po file directory").conflicts("po"))
|
22
38
|
.option("-src, --source <lang>", "source language (ISO 639-1)", "en")
|
@@ -24,6 +40,7 @@ program
|
|
24
40
|
.option("--verbose", "print verbose log")
|
25
41
|
.option("--context <file>", "text file that provides the bot additional context")
|
26
42
|
.addOption(new Option("-o, --output <file>", "output file path, overwirte po file by default").conflicts("dir"))
|
43
|
+
.addCompileOptions()
|
27
44
|
.action(async (args) => {
|
28
45
|
const { key, host, model, po, dir, source, lang, verbose, output, context } = args;
|
29
46
|
if (host) {
|
@@ -38,63 +55,52 @@ program
|
|
38
55
|
process.exit(1);
|
39
56
|
}
|
40
57
|
init();
|
58
|
+
const compileOptions = getCompileOptions(args);
|
41
59
|
if (po) {
|
42
|
-
await translatePo(model, po, source, lang, verbose, output, context);
|
60
|
+
await translatePo(model, po, source, lang, verbose, output, context, compileOptions);
|
43
61
|
}
|
44
62
|
else if (dir) {
|
45
|
-
await translatePoDir(model, dir, source, lang, verbose, context);
|
63
|
+
await translatePoDir(model, dir, source, lang, verbose, context, compileOptions);
|
46
64
|
}
|
47
65
|
else {
|
48
66
|
console.error("po file or directory is required");
|
49
67
|
process.exit(1);
|
50
68
|
}
|
51
69
|
});
|
52
|
-
program
|
53
|
-
|
70
|
+
program.addCommand(translateCommand, { isDefault: true });
|
71
|
+
const syncCommand = new SharedOptionsCommand("sync")
|
54
72
|
.description("update po from pot file")
|
55
73
|
.requiredOption("--po <file>", "po file path")
|
56
74
|
.requiredOption("--pot <file>", "pot file path")
|
57
|
-
.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
.command("systemprompt")
|
63
|
-
.description("open/edit system prompt")
|
64
|
-
.option("--reset", "reset system prompt to default")
|
65
|
-
.action((args) => {
|
66
|
-
const { reset } = args;
|
67
|
-
// open `systemprompt.txt` file by system text default editor
|
68
|
-
const copyFile = __dirname + "/systemprompt.txt";
|
69
|
-
// user home path
|
70
|
-
const promptFile = findConfig("systemprompt.txt");
|
71
|
-
copyFileIfNotExists(promptFile, copyFile, reset);
|
72
|
-
if (reset) {
|
73
|
-
console.log("systemprompt.txt reset to default");
|
74
|
-
}
|
75
|
-
openFileByDefault(promptFile);
|
75
|
+
.option("-o, --output <file>", "output file path, overwirte po file by default")
|
76
|
+
.addCompileOptions()
|
77
|
+
.action(async (args) => {
|
78
|
+
const { po, pot } = args;
|
79
|
+
await sync(po, pot, getCompileOptions(args));
|
76
80
|
});
|
81
|
+
program.addCommand(syncCommand);
|
77
82
|
// program command `userdict` with help text `open/edit user dictionary`
|
78
83
|
program
|
79
84
|
.command("userdict")
|
80
85
|
.description("open/edit user dictionary")
|
81
86
|
.option("--explore", "open user dictionary directory")
|
87
|
+
.option("-l, --lang <lang>", "target language (ISO 639-1)")
|
82
88
|
.action((args) => {
|
83
|
-
const { explore } = args;
|
89
|
+
const { explore, lang } = args;
|
84
90
|
// open `dictionary.json` file by system text default editor
|
85
91
|
const copyFile = __dirname + "/dictionary.json";
|
86
|
-
// user home path
|
87
|
-
const dictFile = findConfig("
|
92
|
+
// find from user home path
|
93
|
+
const dictFile = findConfig(`dictionary${lang ? "-" + lang : ""}.json`);
|
88
94
|
if (explore) {
|
89
95
|
// open user dictionary directory
|
90
96
|
return openFileExplorer(dictFile);
|
91
97
|
}
|
92
|
-
|
98
|
+
if (!lang)
|
99
|
+
copyFileIfNotExists(dictFile, copyFile);
|
93
100
|
openFileByDefault(dictFile);
|
94
101
|
});
|
95
102
|
// program command `remove` with help text `remove po entries by options`
|
96
|
-
|
97
|
-
.command("remove")
|
103
|
+
const removeCommand = new SharedOptionsCommand("remove")
|
98
104
|
.description("remove po entries by options")
|
99
105
|
.requiredOption("--po <file>", "po file path")
|
100
106
|
.option("--fuzzy", "remove fuzzy entries")
|
@@ -104,7 +110,10 @@ program
|
|
104
110
|
.option("-tnf, --translated-not-fuzzy", "remove translated not fuzzy entries")
|
105
111
|
.option("-ft, --fuzzy-translated", "remove fuzzy translated entries")
|
106
112
|
.option("-rc, --reference-contains <text>", "remove entries whose reference contains text, text can be a regular expression like /text/ig")
|
113
|
+
.option("-o, --output <file>", "output file path, overwirte po file by default")
|
114
|
+
.addCompileOptions()
|
107
115
|
.action(async (args) => {
|
116
|
+
this;
|
108
117
|
const { po, fuzzy, obsolete, untranslated, translated, translatedNotFuzzy, fuzzyTranslated, referenceContains } = args;
|
109
118
|
const options = {
|
110
119
|
fuzzy,
|
@@ -113,12 +122,13 @@ program
|
|
113
122
|
translated,
|
114
123
|
translatedNotFuzzy,
|
115
124
|
fuzzyTranslated,
|
116
|
-
referenceContains
|
125
|
+
referenceContains
|
117
126
|
};
|
118
127
|
const output = args.output || po;
|
119
128
|
const translations = await parsePo(po);
|
120
|
-
await compilePo(removeByOptions(translations, options), output);
|
129
|
+
await compilePo(removeByOptions(translations, options), output, getCompileOptions(args));
|
121
130
|
console.log("done");
|
122
131
|
});
|
132
|
+
program.addCommand(removeCommand);
|
123
133
|
program.parse(process.argv);
|
124
134
|
//# sourceMappingURL=index.js.map
|
package/lib/src/sync.d.ts
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
import { CompileOptions } from "./utils.js";
|
2
|
+
export declare function sync(po: string, pot: string, compileOptions?: CompileOptions): Promise<void>;
|
package/lib/src/sync.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { compilePo, parsePo } from "./utils.js";
|
2
|
-
export async function sync(po, pot) {
|
2
|
+
export async function sync(po, pot, compileOptions) {
|
3
3
|
const potrans = await parsePo(po);
|
4
4
|
const potrans2 = await parsePo(pot);
|
5
5
|
for (const [ctx, entries] of Object.entries(potrans2.translations)) {
|
@@ -13,6 +13,6 @@ export async function sync(po, pot) {
|
|
13
13
|
}
|
14
14
|
}
|
15
15
|
potrans.translations = potrans2.translations;
|
16
|
-
await compilePo(potrans, po);
|
16
|
+
await compilePo(potrans, po, compileOptions);
|
17
17
|
}
|
18
18
|
//# sourceMappingURL=sync.js.map
|
package/lib/src/systemprompt.txt
CHANGED
@@ -1,31 +1 @@
|
|
1
|
-
You are a language translation expert. You will translate
|
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.
|
1
|
+
You are a language translation expert. You will carefully follow the translation guidelines to translate the incoming XML messages from one language to another.
|
package/lib/src/translate.d.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { GetTextTranslation } from "gettext-parser";
|
2
2
|
import { OpenAI } from "openai";
|
3
|
+
import { CompileOptions } from "./utils.js";
|
3
4
|
export declare function init(force?: boolean): OpenAI;
|
4
|
-
export declare function translate(
|
5
|
-
export declare function translatePo(model: string, po: string, source: string, lang: string, verbose: boolean, output: string, contextFile: string): Promise<void>;
|
6
|
-
export declare function translatePoDir(model: string | undefined, dir: string, source: string, lang: string, verbose: boolean, contextFile: string): Promise<void>;
|
5
|
+
export declare function translate(src: string, lang: string, model: string, translations: GetTextTranslation[], contextFile: string): Promise<void>;
|
6
|
+
export declare function translatePo(model: string, po: string, source: string, lang: string, verbose: boolean, output: string, contextFile: string, compileOptions?: CompileOptions): Promise<void>;
|
7
|
+
export declare function translatePoDir(model: string | undefined, dir: string, source: string, lang: string, verbose: boolean, contextFile: string, compileOptions?: CompileOptions): Promise<void>;
|
package/lib/src/translate.js
CHANGED
@@ -8,47 +8,59 @@ const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
9
9
|
let _openai;
|
10
10
|
let _systemprompt;
|
11
|
+
let _userprompt;
|
11
12
|
let _userdict;
|
12
13
|
export function init(force) {
|
13
14
|
if (!_openai || force) {
|
14
15
|
let configuration = {
|
15
|
-
apiKey: process.env.OPENAI_API_KEY
|
16
|
+
apiKey: process.env.OPENAI_API_KEY
|
16
17
|
};
|
17
18
|
_openai = new OpenAI(configuration);
|
18
19
|
if (process.env.OPENAI_API_HOST) {
|
19
20
|
_openai.baseURL = process.env.OPENAI_API_HOST.replace(/\/+$/, "") + "/v1";
|
20
21
|
}
|
21
22
|
}
|
22
|
-
// load systemprompt.txt from
|
23
|
+
// load systemprompt.txt from project
|
23
24
|
if (!_systemprompt || force) {
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
_systemprompt = fs.readFileSync(path.join(__dirname, "systemprompt.txt"), "utf-8");
|
26
|
+
}
|
27
|
+
// load userprompt.txt from project
|
28
|
+
if (!_userprompt || force) {
|
29
|
+
_userprompt = fs.readFileSync(path.join(__dirname, "userprompt.txt"), "utf-8");
|
27
30
|
}
|
28
31
|
// load dictionary.json from homedir
|
29
32
|
if (!_userdict || force) {
|
30
33
|
const userdict = findConfig("dictionary.json");
|
31
34
|
copyFileIfNotExists(userdict, path.join(__dirname, "dictionary.json"));
|
32
|
-
_userdict = {
|
35
|
+
_userdict = { default: JSON.parse(fs.readFileSync(userdict, "utf-8")) };
|
33
36
|
}
|
34
37
|
return _openai;
|
35
38
|
}
|
36
|
-
export function translate(
|
37
|
-
const lang_code = lang
|
38
|
-
|
39
|
-
.
|
40
|
-
.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
39
|
+
export async function translate(src, lang, model, translations, contextFile) {
|
40
|
+
const lang_code = lang
|
41
|
+
.toLowerCase()
|
42
|
+
.trim()
|
43
|
+
.replace(/[\W_]+/g, "-");
|
44
|
+
const dicts = Object.entries(_userdict[lang_code] || _userdict["default"]).reduce((acc, [k, v], idx) => {
|
45
|
+
if (translations.some((tr) => tr.msgid.toLowerCase().includes(k.toLowerCase()))) {
|
46
|
+
acc.user.push(`<translate index="${idx + 1}">${k}</translate>`);
|
47
|
+
acc.assistant.push(`<translated index="${idx + 1}">${v}</translated>`);
|
48
|
+
}
|
49
|
+
return acc;
|
50
|
+
}, { user: [], assistant: [] });
|
51
|
+
const notes = translations
|
52
|
+
.reduce((acc, tr) => {
|
53
|
+
if (tr.comments?.extracted) {
|
54
|
+
acc.push(tr.comments?.extracted);
|
55
|
+
}
|
56
|
+
return acc;
|
57
|
+
}, [])
|
58
|
+
.join("\n");
|
59
|
+
const context = contextFile ? "\n\nContext: " + fs.readFileSync(contextFile, "utf-8") : "";
|
60
|
+
const translationsContent = translations
|
61
|
+
.map((tr, idx) => `<translate index="${idx + dicts.user.length + 1}">${tr.msgid}</translate>`)
|
62
|
+
.join("\n");
|
63
|
+
const res = await _openai.chat.completions.create({
|
52
64
|
model: model,
|
53
65
|
temperature: 0.1,
|
54
66
|
messages: [
|
@@ -58,24 +70,44 @@ export function translate(text, src, lang, model, comments, contextFile) {
|
|
58
70
|
},
|
59
71
|
{
|
60
72
|
role: "user",
|
61
|
-
content:
|
73
|
+
content: `${_userprompt}\n\nWait for my incoming message in "${src}" and translate it into "${lang}"(a language code and an optional region code). ` +
|
74
|
+
notes
|
62
75
|
},
|
63
76
|
{
|
64
77
|
role: "assistant",
|
65
|
-
content: `Understood, I will translate your incoming "${src}" message into "${lang}", carefully following
|
78
|
+
content: `Understood, I will translate your incoming "${src}" message into "${lang}", carefully following guidelines. Please go ahead and send your message for translation.`
|
66
79
|
},
|
67
|
-
// add userdict
|
68
|
-
...dicts
|
80
|
+
// add userdict
|
81
|
+
...(dicts.user.length > 0
|
82
|
+
? [
|
83
|
+
{ role: "user", content: dicts.user.join("\n") },
|
84
|
+
{ role: "assistant", content: dicts.assistant.join("\n") }
|
85
|
+
]
|
86
|
+
: []),
|
87
|
+
// add user translations
|
69
88
|
{
|
70
89
|
role: "user",
|
71
|
-
content:
|
72
|
-
}
|
73
|
-
]
|
90
|
+
content: translationsContent
|
91
|
+
}
|
92
|
+
]
|
74
93
|
}, {
|
75
94
|
timeout: 20000,
|
95
|
+
stream: false
|
96
|
+
});
|
97
|
+
const content = res.choices[0].message.content ?? "";
|
98
|
+
translations.forEach((trans, idx) => {
|
99
|
+
const tag = `<translated index="${idx + dicts.user.length + 1}">`;
|
100
|
+
const s = content.indexOf(tag);
|
101
|
+
if (s > -1) {
|
102
|
+
const e = content.indexOf("</translated>", s);
|
103
|
+
trans.msgstr[0] = content.slice(s + tag.length, e);
|
104
|
+
}
|
105
|
+
else {
|
106
|
+
console.error("Error: Unable to find translation for string [" + trans.msgid + "]");
|
107
|
+
}
|
76
108
|
});
|
77
109
|
}
|
78
|
-
export async function translatePo(model, po, source, lang, verbose, output, contextFile) {
|
110
|
+
export async function translatePo(model, po, source, lang, verbose, output, contextFile, compileOptions) {
|
79
111
|
const potrans = await parsePo(po);
|
80
112
|
if (!lang)
|
81
113
|
lang = potrans.headers["Language"];
|
@@ -84,7 +116,10 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
|
|
84
116
|
return;
|
85
117
|
}
|
86
118
|
// try to load dictionary by lang-code if it not loaded
|
87
|
-
const lang_code = lang
|
119
|
+
const lang_code = lang
|
120
|
+
.toLowerCase()
|
121
|
+
.trim()
|
122
|
+
.replace(/[\W_]+/g, "-");
|
88
123
|
if (!_userdict[lang_code]) {
|
89
124
|
const lang_dic_file = findConfig(`dictionary-${lang_code}.json`);
|
90
125
|
if (fs.existsSync(lang_dic_file)) {
|
@@ -110,16 +145,16 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
|
|
110
145
|
}
|
111
146
|
}
|
112
147
|
if (trimed) {
|
113
|
-
await compilePo(potrans, po);
|
148
|
+
await compilePo(potrans, po, compileOptions);
|
114
149
|
}
|
115
150
|
if (list.length == 0) {
|
116
151
|
console.log("done.");
|
117
152
|
return;
|
118
153
|
}
|
119
154
|
potrans.headers["Last-Translator"] = `gpt-po v${pkg.version}`;
|
155
|
+
const translations = [];
|
120
156
|
let err429 = false;
|
121
|
-
let
|
122
|
-
for (let i = 0; i < list.length; i++) {
|
157
|
+
for (let i = 0, c = 0; i < list.length; i++) {
|
123
158
|
if (i == 0)
|
124
159
|
printProgress(i, list.length);
|
125
160
|
if (err429) {
|
@@ -127,53 +162,56 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
|
|
127
162
|
await new Promise((resolve) => setTimeout(resolve, 20000));
|
128
163
|
}
|
129
164
|
const trans = list[i];
|
130
|
-
|
131
|
-
|
132
|
-
|
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;
|
137
|
-
}
|
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]);
|
144
|
-
}
|
145
|
-
printProgress(i + 1, list.length);
|
146
|
-
await compilePo(potrans, output || po);
|
165
|
+
if (c < 2000) {
|
166
|
+
translations.push(trans);
|
167
|
+
c += trans.msgid.length;
|
147
168
|
}
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
console.error(error.response.status);
|
157
|
-
console.log(error.response.data);
|
169
|
+
if (c >= 2000 || i == list.length - 1) {
|
170
|
+
try {
|
171
|
+
await translate(source, lang, model, translations, contextFile);
|
172
|
+
if (verbose) {
|
173
|
+
translations.forEach((trans) => {
|
174
|
+
console.log(trans.msgid);
|
175
|
+
console.log(trans.msgstr[0]);
|
176
|
+
});
|
158
177
|
}
|
178
|
+
translations.length = 0;
|
179
|
+
c = 0;
|
180
|
+
// update progress
|
181
|
+
printProgress(i + 1, list.length);
|
182
|
+
// save po file after each 2000 characters
|
183
|
+
await compilePo(potrans, output || po, compileOptions);
|
159
184
|
}
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
185
|
+
catch (error) {
|
186
|
+
if (error.response) {
|
187
|
+
if (error.response.status == 429) {
|
188
|
+
// caused by rate limit exceeded, should sleep for 20 seconds.
|
189
|
+
err429 = true;
|
190
|
+
--i;
|
191
|
+
}
|
192
|
+
else {
|
193
|
+
console.error(error.response.status);
|
194
|
+
console.log(error.response.data);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
else {
|
198
|
+
console.error(error.message);
|
199
|
+
if (error.code == "ECONNABORTED") {
|
200
|
+
console.log('you may need to set "HTTPS_PROXY" to reach openai api.');
|
201
|
+
}
|
164
202
|
}
|
165
203
|
}
|
166
204
|
}
|
167
205
|
}
|
168
206
|
console.log("done.");
|
169
207
|
}
|
170
|
-
export async function translatePoDir(model = "gpt-3.5-turbo", dir, source, lang, verbose, contextFile) {
|
208
|
+
export async function translatePoDir(model = "gpt-3.5-turbo", dir, source, lang, verbose, contextFile, compileOptions) {
|
171
209
|
const files = fs.readdirSync(dir);
|
172
210
|
for (const file of files) {
|
173
211
|
if (file.endsWith(".po")) {
|
174
212
|
const po = path.join(dir, file);
|
175
213
|
console.log(`translating ${po}`);
|
176
|
-
await translatePo(model, po, source, lang, verbose, po, contextFile);
|
214
|
+
await translatePo(model, po, source, lang, verbose, po, contextFile, compileOptions);
|
177
215
|
}
|
178
216
|
}
|
179
217
|
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Translation guidelines are as follows:
|
2
|
+
|
3
|
+
1. **Placeholder Handling**:
|
4
|
+
- Maintain the positions of placeholders (e.g., %s, %d, {example}) in the translated text. Do not translate placeholders.
|
5
|
+
|
6
|
+
2. **Formatting**:
|
7
|
+
- Preserve the formatting of untranslatable portions.
|
8
|
+
- Retain any whitespace at the beginning or end of the message.
|
9
|
+
- Add or omit a period (.) at the end of your translation to match the incoming message.
|
10
|
+
|
11
|
+
3. **XML Tags and Indexing**:
|
12
|
+
- Input messages will be wrapped in `<translate>` XML tags with an index attribute, e.g., `<translate index="1">`.
|
13
|
+
- Respond with the translated message wrapped in `<translated>` XML tags, including the same index attribute, e.g., `<translated index="1">`.
|
14
|
+
|
15
|
+
4. **Multiple Translations**:
|
16
|
+
- You may receive multiple translation requests in a single input, each with a unique index.
|
17
|
+
- Ensuring the complete number of requests are translated, even if they are repeated, while maintaining the original order when possible.
|
18
|
+
|
19
|
+
**Examples**:
|
20
|
+
- Input:
|
21
|
+
`<translate index="1">This is a message. </translate>`
|
22
|
+
`<translate index="2"> Hello %s</translate>`
|
23
|
+
- Output:
|
24
|
+
`<translated index="1">这是一条信息</translated>`
|
25
|
+
`<translated index="2"> 你好 %s</translated>`
|
26
|
+
|
27
|
+
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/utils.d.ts
CHANGED
@@ -8,7 +8,12 @@ import { GetTextTranslations } from "gettext-parser";
|
|
8
8
|
export declare function copyFileIfNotExists(file: string, copyFile: string, force?: boolean): void;
|
9
9
|
export declare function openFileByDefault(filePath: string): void;
|
10
10
|
export declare function parsePo(poFile: string, defaultCharset?: string): Promise<GetTextTranslations>;
|
11
|
-
export
|
11
|
+
export type CompileOptions = {
|
12
|
+
foldLength?: number;
|
13
|
+
sort?: boolean | ((a: never, b: never) => number);
|
14
|
+
escapeCharacters?: boolean;
|
15
|
+
};
|
16
|
+
export declare function compilePo(data: GetTextTranslations, poFile: string, options?: CompileOptions): Promise<void>;
|
12
17
|
export declare function printProgress(progress: number, total: number, extra?: string): void;
|
13
18
|
export declare function gitRootDir(dir?: string): string | null;
|
14
19
|
/**
|
package/lib/src/utils.js
CHANGED
@@ -36,13 +36,13 @@ export function parsePo(poFile, defaultCharset) {
|
|
36
36
|
fs.readFile(poFile, (err, buffer) => {
|
37
37
|
if (err)
|
38
38
|
reject(err);
|
39
|
-
|
39
|
+
const result = po.parse(buffer, defaultCharset ?? "utf-8");
|
40
40
|
resolve(result);
|
41
41
|
});
|
42
42
|
});
|
43
43
|
}
|
44
|
-
export function compilePo(data, poFile) {
|
45
|
-
const buffer = po.compile(data,
|
44
|
+
export function compilePo(data, poFile, options = { foldLength: 120, sort: false, escapeCharacters: true }) {
|
45
|
+
const buffer = po.compile(data, options);
|
46
46
|
return new Promise((resolve, reject) => {
|
47
47
|
fs.writeFile(poFile, buffer, (err) => {
|
48
48
|
if (err)
|
@@ -95,7 +95,7 @@ export function findConfig(fileName) {
|
|
95
95
|
path.join(currentDir, ".gpt-po", fileName),
|
96
96
|
path.join(currentDir, fileName),
|
97
97
|
path.join(gitDir, ".gpt-po", fileName),
|
98
|
-
path.join(homeDir, ".gpt-po", fileName)
|
98
|
+
path.join(homeDir, ".gpt-po", fileName),
|
99
99
|
];
|
100
100
|
// check if file exists and return the first one
|
101
101
|
for (const filePath of filePaths) {
|
@@ -111,10 +111,10 @@ export function findConfig(fileName) {
|
|
111
111
|
* @param location folder or file path
|
112
112
|
*/
|
113
113
|
export function openFileExplorer(location) {
|
114
|
-
if (platform() ===
|
114
|
+
if (platform() === "win32") {
|
115
115
|
exec(`explorer.exe "${path.dirname(location)}"`);
|
116
116
|
}
|
117
|
-
else if (platform() ===
|
117
|
+
else if (platform() === "darwin") {
|
118
118
|
exec(`open "${path.dirname(location)}"`);
|
119
119
|
}
|
120
120
|
else {
|