gpt-po 1.2.1 → 1.2.4

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 CHANGED
@@ -19,6 +19,11 @@ Set `OPENAI_API_KEY` before using this tool.
19
19
 
20
20
  **It is recommended to use the paid OpenAI API to improve translation speed, as the free OpenAI API is slower (only 3 translations per minute) and has usage restrictions.**
21
21
 
22
+ ### Environment Variables
23
+ - `OPENAI_API_KEY`: OpenAI API key.
24
+ - `OPENAI_API_HOST`: OpenAI API host (default: https://api.openai.com).
25
+ - `OPENAI_MODEL_TMP`: OpenAI model temperature (default: 0.1).
26
+
22
27
  ## Usage Scenarios
23
28
 
24
29
  - `gpt-po sync --po <file> --pot <file>` Update the po file based on the pot file, while preserving the original translations.
@@ -60,7 +65,9 @@ Options:
60
65
  --verbose show verbose log
61
66
  -l, --lang <lang> target language (ISO 639-1 code)
62
67
  -o, --output <file> output file path, overwirte po file by default
63
- --context context file path (provides additional context to the bot)
68
+ --context <file> context file path (provides additional context to the bot)
69
+ --context-length <length> Maximum accumulated length of source strings (msgid) to translate in each API request (default: 2000, env: API_CONTEXT_LENGTH)
70
+ --timeout <ms> Timeout in milliseconds for API requests (default: 20000, env: API_TIMEOUT)
64
71
  -h, --help display help for command
65
72
  ```
66
73
 
@@ -90,4 +97,4 @@ Options:
90
97
  --po <file> po file path
91
98
  --pot <file> pot file path
92
99
  -h, --help display help for command
93
- ```
100
+ ```
package/README_zh-CN.md CHANGED
@@ -21,6 +21,11 @@ npm install gpt-po
21
21
 
22
22
  *国内用户要设置`HTTPS_PROXY`环境变量上梯子才能用*
23
23
 
24
+ ### 环境变量
25
+ - `OPENAI_API_KEY`: OpenAI API密钥。
26
+ - `OPENAI_API_HOST`: OpenAI API主机(默认:https://api.openai.com)。
27
+ - `OPENAI_MODEL_TMP`: OpenAI模型温度(默认:0.1)。
28
+
24
29
  ## 常见用法
25
30
 
26
31
  - `gpt-po sync --po <file> --pot <file>` 根据 pot 文件更新 po 文件,同时保留原有翻译。
@@ -62,7 +67,9 @@ npm install gpt-po
62
67
  --verbose 显示详细日志
63
68
  -l, --lang <lang> 目标语言 (ISO 639-1 代码)
64
69
  -o, --output <file> 输出文件路径,默认覆盖 po 文件
65
- --context 上下文文件路径(为机器人提供额外的上下文)
70
+ --context <file> 上下文文件路径(为机器人提供额外的上下文)
71
+ --context-length <length> 每次 API 请求中源字符串(msgid)的最大累计长度(默认:2000,环境变量:API_CONTEXT_LENGTH)
72
+ --timeout <ms> API 请求超时时间(单位:毫秒,默认:20000,环境变量:API_TIMEOUT)
66
73
  -h, --help 显示命令帮助
67
74
  ```
68
75
 
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gpt-po",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "description": "command tool for translate po files by gpt",
5
5
  "main": "lib/src/index.js",
6
6
  "type": "module",
@@ -1,2 +1,2 @@
1
- #!/usr/bin/env node --no-warnings=ExperimentalWarning
1
+ #!/usr/bin/env -S node --no-warnings=ExperimentalWarning
2
2
  export {};
package/lib/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node --no-warnings=ExperimentalWarning
1
+ #!/usr/bin/env -S node --no-warnings=ExperimentalWarning
2
2
  import { Command, Option } from "commander";
3
3
  import path from "path";
4
4
  import { fileURLToPath } from "url";
@@ -39,10 +39,12 @@ const translateCommand = new SharedOptionsCommand("translate")
39
39
  .option("-l, --lang <lang>", "target language (ISO 639-1)")
40
40
  .option("--verbose", "print verbose log")
41
41
  .option("--context <file>", "text file that provides the bot additional context")
42
+ .addOption(new Option("--context-length <length>", "maximum accumulated length of source strings (msgid) to translate in each API request").env("API_CONTEXT_LENGTH").default("2000"))
43
+ .addOption(new Option("--timeout <ms>", "timeout in milliseconds for API requests").env("API_TIMEOUT").default("20000"))
42
44
  .addOption(new Option("-o, --output <file>", "output file path, overwirte po file by default").conflicts("dir"))
43
45
  .addCompileOptions()
44
46
  .action(async (args) => {
45
- const { key, host, model, po, dir, source, lang, verbose, output, context } = args;
47
+ const { key, host, model, po, dir, source, lang, verbose, output, context, contextLength, timeout } = args;
46
48
  if (host) {
47
49
  process.env.OPENAI_API_HOST = host;
48
50
  }
@@ -57,10 +59,10 @@ const translateCommand = new SharedOptionsCommand("translate")
57
59
  init();
58
60
  const compileOptions = getCompileOptions(args);
59
61
  if (po) {
60
- await translatePo(model, po, source, lang, verbose, output, context, compileOptions);
62
+ await translatePo(model, po, source, lang, verbose, output, context, parseInt(contextLength), parseInt(timeout), compileOptions);
61
63
  }
62
64
  else if (dir) {
63
- await translatePoDir(model, dir, source, lang, verbose, context, compileOptions);
65
+ await translatePoDir(model, dir, source, lang, verbose, context, parseInt(contextLength), parseInt(timeout), compileOptions);
64
66
  }
65
67
  else {
66
68
  console.error("po file or directory is required");
@@ -2,6 +2,6 @@ import { GetTextTranslation } from "gettext-parser";
2
2
  import { OpenAI } from "openai";
3
3
  import { CompileOptions } from "./utils.js";
4
4
  export declare function init(force?: boolean): OpenAI;
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>;
5
+ export declare function translate(src: string, lang: string, model: string, translations: GetTextTranslation[], contextFile: string, timeout: number): Promise<void>;
6
+ export declare function translatePo(model: string, po: string, source: string, lang: string, verbose: boolean, output: string, contextFile: string, contextLength: number, timeout: number, compileOptions?: CompileOptions): Promise<void>;
7
+ export declare function translatePoDir(model: string | undefined, dir: string, source: string, lang: string, verbose: boolean, contextFile: string, contextLength: number, timeout: number, compileOptions?: CompileOptions): Promise<void>;
@@ -36,7 +36,7 @@ export function init(force) {
36
36
  }
37
37
  return _openai;
38
38
  }
39
- export async function translate(src, lang, model, translations, contextFile) {
39
+ export async function translate(src, lang, model, translations, contextFile, timeout) {
40
40
  const lang_code = lang
41
41
  .toLowerCase()
42
42
  .trim()
@@ -58,11 +58,14 @@ export async function translate(src, lang, model, translations, contextFile) {
58
58
  .join("\n");
59
59
  const context = contextFile ? "\n\nContext: " + fs.readFileSync(contextFile, "utf-8") : "";
60
60
  const translationsContent = translations
61
- .map((tr, idx) => `<translate index="${idx + dicts.user.length + 1}">${tr.msgid}</translate>`)
61
+ .map((tr, idx) => {
62
+ const contextAttr = tr.msgctxt ? ` context="${tr.msgctxt}"` : "";
63
+ return `<translate index="${idx + dicts.user.length + 1}"${contextAttr}>${tr.msgid}</translate>`;
64
+ })
62
65
  .join("\n");
63
66
  const res = await _openai.chat.completions.create({
64
67
  model: model,
65
- temperature: 0.1,
68
+ temperature: process.env.OPENAI_MODEL_TMP ? parseFloat(process.env.OPENAI_MODEL_TMP) : 0.1,
66
69
  messages: [
67
70
  {
68
71
  role: "system",
@@ -70,12 +73,12 @@ export async function translate(src, lang, model, translations, contextFile) {
70
73
  },
71
74
  {
72
75
  role: "user",
73
- content: `${_userprompt}\n\nWait for my incoming message in "${src}" and translate it into "${lang}"(a language code and an optional region code). ` +
76
+ content: `${_userprompt}\n\nWait for my incoming message(s) in \`${src}\` and translate them into \`${lang}\` (\`${src}\` and \`${lang}\` are XPG/POSIX locale names, used in Unix-like systems and GNU Gettext). ` +
74
77
  notes
75
78
  },
76
79
  {
77
80
  role: "assistant",
78
- content: `Understood, I will translate your incoming "${src}" message into "${lang}", carefully following guidelines. Please go ahead and send your message for translation.`
81
+ content: `Understood, I will translate your incoming \`${src}\` message(s) into \`${lang}\`, carefully following guidelines. Please go ahead and send your message(s) for translation.`
79
82
  },
80
83
  // add userdict
81
84
  ...(dicts.user.length > 0
@@ -91,7 +94,7 @@ export async function translate(src, lang, model, translations, contextFile) {
91
94
  }
92
95
  ]
93
96
  }, {
94
- timeout: 20000,
97
+ timeout,
95
98
  stream: false
96
99
  });
97
100
  const content = res.choices[0].message.content ?? "";
@@ -107,7 +110,7 @@ export async function translate(src, lang, model, translations, contextFile) {
107
110
  }
108
111
  });
109
112
  }
110
- export async function translatePo(model, po, source, lang, verbose, output, contextFile, compileOptions) {
113
+ export async function translatePo(model, po, source, lang, verbose, output, contextFile, contextLength, timeout, compileOptions) {
111
114
  const potrans = await parsePo(po);
112
115
  if (!lang)
113
116
  lang = potrans.headers["Language"];
@@ -132,11 +135,16 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
132
135
  let trimed = false;
133
136
  for (const [ctx, entries] of Object.entries(potrans.translations)) {
134
137
  for (const [msgid, trans] of Object.entries(entries)) {
135
- if (msgid == "")
138
+ if (msgid === "")
136
139
  continue;
137
140
  if (!trans.msgstr[0]) {
138
- list.push(trans);
139
- continue;
141
+ list.push({
142
+ msgctxt: trans.msgctxt || ctx,
143
+ msgid,
144
+ msgid_plural: trans.msgid_plural,
145
+ msgstr: trans.msgstr,
146
+ comments: trans.comments
147
+ });
140
148
  }
141
149
  else if (trimRegx.test(trans.msgstr[0])) {
142
150
  trimed = true;
@@ -162,13 +170,13 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
162
170
  await new Promise((resolve) => setTimeout(resolve, 20000));
163
171
  }
164
172
  const trans = list[i];
165
- if (c < 2000) {
173
+ if (c < contextLength) {
166
174
  translations.push(trans);
167
175
  c += trans.msgid.length;
168
176
  }
169
- if (c >= 2000 || i == list.length - 1) {
177
+ if (c >= contextLength || i == list.length - 1) {
170
178
  try {
171
- await translate(source, lang, model, translations, contextFile);
179
+ await translate(source, lang, model, translations, contextFile, timeout);
172
180
  if (verbose) {
173
181
  translations.forEach((trans) => {
174
182
  console.log(trans.msgid);
@@ -179,7 +187,7 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
179
187
  c = 0;
180
188
  // update progress
181
189
  printProgress(i + 1, list.length);
182
- // save po file after each 2000 characters
190
+ // save po file after each 2000 characters by default
183
191
  await compilePo(potrans, output || po, compileOptions);
184
192
  }
185
193
  catch (error) {
@@ -205,13 +213,13 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
205
213
  }
206
214
  console.log("done.");
207
215
  }
208
- export async function translatePoDir(model = "gpt-3.5-turbo", dir, source, lang, verbose, contextFile, compileOptions) {
216
+ export async function translatePoDir(model = "gpt-3.5-turbo", dir, source, lang, verbose, contextFile, contextLength, timeout, compileOptions) {
209
217
  const files = fs.readdirSync(dir);
210
218
  for (const file of files) {
211
219
  if (file.endsWith(".po")) {
212
220
  const po = path.join(dir, file);
213
221
  console.log(`translating ${po}`);
214
- await translatePo(model, po, source, lang, verbose, po, contextFile, compileOptions);
222
+ await translatePo(model, po, source, lang, verbose, po, contextFile, contextLength, timeout, compileOptions);
215
223
  }
216
224
  }
217
225
  }
@@ -12,11 +12,18 @@ Translation guidelines are as follows:
12
12
  - Input messages will be wrapped in `<translate>` XML tags with an index attribute, e.g., `<translate index="1">`.
13
13
  - Respond with the translated message wrapped in `<translated>` XML tags, including the same index attribute, e.g., `<translated index="1">`.
14
14
 
15
- 4. **Multiple Translations**:
15
+ 4. **Context Handling**:
16
+ - Some messages will include a context attribute in the translate tag, e.g., `<translate index="1" context="Menu">`.
17
+ - Use this context to inform your translation but only return the translated text.
18
+ - Example (translating from `en_GB` to `fr_FR`):
19
+ Input: `<translate index="1" context="Menu">File</translate>`
20
+ Output: `<translated index="1">Fichier</translated>`
21
+
22
+ 5. **Multiple Translations**:
16
23
  - You may receive multiple translation requests in a single input, each with a unique index.
17
24
  - Ensuring the complete number of requests are translated, even if they are repeated, while maintaining the original order when possible.
18
25
 
19
- **Examples**:
26
+ **Example (translating from `en_US` to `zh_CN`):**
20
27
  - Input:
21
28
  `<translate index="1">This is a message. </translate>`
22
29
  `<translate index="2"> Hello %s</translate>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gpt-po",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "description": "command tool for translate po files by gpt",
5
5
  "main": "lib/src/index.js",
6
6
  "type": "module",