gpt-po 1.2.4 → 1.3.0

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm install *)"
5
+ ]
6
+ }
7
+ }
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  Translation tool for gettext (po) files that supports custom system prompts and user dictionaries. It also supports translating specified po files to a designated target language and updating po files based on pot files.
7
7
 
8
+ **Supported LLM Providers:** OpenAI (GPT), Anthropic (Claude), and Google (Gemini)
9
+
8
10
  Read in other languages: English | [简体中文](./README_zh-CN.md)
9
11
 
10
12
  <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>
@@ -15,14 +17,18 @@ Read in other languages: English | [简体中文](./README_zh-CN.md)
15
17
  npm install gpt-po
16
18
  ```
17
19
 
18
- Set `OPENAI_API_KEY` before using this tool.
20
+ Set `API_KEY` before using this tool. Depending on the provider, use `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or `GEMINI_API_KEY`.
19
21
 
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.**
22
+ **It is recommended to use paid APIs to improve translation speed, as free tiers are often slower and have usage restrictions.**
21
23
 
22
24
  ### Environment Variables
23
25
  - `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
+ - `OPENAI_API_HOST`: OpenAI API host (default: `https://api.openai.com`).
27
+ - `ANTHROPIC_API_KEY`: Anthropic API key.
28
+ - `ANTHROPIC_API_HOST`: Anthropic API host.
29
+ - `GEMINI_API_KEY`: Google Gemini API key.
30
+ - `GEMINI_API_HOST`: Google Gemini API host.
31
+ - `MODEL_TMP`: Model temperature (default: 0.1). This will override provider-specific temperature settings.
26
32
 
27
33
  ## Usage Scenarios
28
34
 
@@ -56,9 +62,10 @@ Usage: gpt-po [options]
56
62
  translate po file (default command)
57
63
 
58
64
  Options:
59
- -k, --key <key> openai api key (env: OPENAI_API_KEY)
60
- --host <host> openai api host (env: OPENAI_API_HOST)
61
- --model <model> openai model (default: "gpt-4o-mini", env: OPENAI_MODEL)
65
+ -p, --provider <provider> API provider (choices: "openai", "anthropic", "gemini", default: "openai")
66
+ -k, --key <key> API key (can also be set via OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY)
67
+ --host <host> API host (can also be set via OPENAI_API_HOST, ANTHROPIC_API_HOST, GEMINI_API_HOST)
68
+ --model <model> Model to use (default: gpt-5-nano for openai, claude-haiku-4-5 for anthropic, gemini-2.5-flash for gemini, env: MODEL)
62
69
  --po <file> po file path
63
70
  --dir <dir> po file directory
64
71
  -src, --source <lang> source language (default: "english")
package/README_zh-CN.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  gettext(po)文件翻译工具,支持自定义系统提示词和用户字典,支持翻译指定的po文件到指定的目标语言,支持根据pot文件更新po文件。
7
7
 
8
+ **支持的LLM提供商:** OpenAI (GPT)、Anthropic (Claude) 和 Google (Gemini)
9
+
8
10
  使用其他语言阅读:[English](./README.md) | 简体中文
9
11
 
10
12
  <a href="https://buymeacoffee.com/ryanhex" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-red.png" alt="请我喝杯咖啡" height="41" width="174"></a>
@@ -15,16 +17,20 @@ gettext(po)文件翻译工具,支持自定义系统提示词和用户字典,
15
17
  npm install gpt-po
16
18
  ```
17
19
 
18
- 使用此工具前先设置 `OPENAI_API_KEY`,Windows中使用 `set OPENAI_API_KEY=<key>`, Linux中 `export OPENAI_API_KEY=<key>`
20
+ 使用此工具前先设置 `API_KEY`,根据不同的提供商,使用 `OPENAI_API_KEY`,`ANTHROPIC_API_KEY` `GEMINI_API_KEY`。
19
21
 
20
- **建议使用付费的OpenAI API以提高翻译速度,免费的OpenAI API速度较慢(一分钟3条一天200条),且有使用限制。**
22
+ **建议使用付费API以提高翻译速度,免费API速度较慢且有使用限制。**
21
23
 
22
- *国内用户要设置`HTTPS_PROXY`环境变量上梯子才能用*
24
+ *国内用户要设置`HTTPS_PROXY`环境变量上代理才能用,或者配置使用代理服务的HOST*
23
25
 
24
26
  ### 环境变量
25
27
  - `OPENAI_API_KEY`: OpenAI API密钥。
26
- - `OPENAI_API_HOST`: OpenAI API主机(默认:https://api.openai.com)。
27
- - `OPENAI_MODEL_TMP`: OpenAI模型温度(默认:0.1)。
28
+ - `OPENAI_API_HOST`: OpenAI API主机(默认:`https://api.openai.com`)。
29
+ - `ANTHROPIC_API_KEY`: Anthropic API密钥。
30
+ - `ANTHROPIC_API_HOST`: Anthropic API主机。
31
+ - `GEMINI_API_KEY`: Google Gemini API密钥。
32
+ - `GEMINI_API_HOST`: Google Gemini API主机。
33
+ - `MODEL_TMP`: 模型温度(默认:0.1)。这将覆盖特定于提供商的温度设置。
28
34
 
29
35
  ## 常见用法
30
36
 
@@ -58,9 +64,10 @@ npm install gpt-po
58
64
  翻译 po 文件(默认命令)
59
65
 
60
66
  选项:
61
- -k, --key <key> openai api key (环境变量: OPENAI_API_KEY)
62
- --host <host> openai api host (环境变量: OPENAI_API_HOST)
63
- --model <model> openai 模型 (默认: "gpt-4o-mini", 环境变量: OPENAI_MODEL)
67
+ -p, --provider <provider> API 提供商 (可选: "openai", "anthropic", "gemini", 默认: "openai")
68
+ -k, --key <key> API 密钥 (也可通过 OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY 设置)
69
+ --host <host> API 主机 (也可通过 OPENAI_API_HOST, ANTHROPIC_API_HOST, GEMINI_API_HOST 设置)
70
+ --model <model> 要使用的模型 (默认: openai 为 gpt-5-nano, anthropic 为 claude-haiku-4-5, gemini 为 gemini-2.5-flash, 环境变量: MODEL)
64
71
  --po <file> po 文件路径
65
72
  --dir <dir> po 文件目录
66
73
  -src, --source <lang> 源语言 (默认: "english")
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gpt-po",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "command tool for translate po files by gpt",
5
5
  "main": "lib/src/index.js",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  "start": "node lib/src/index.js",
12
12
  "test": "echo \"Error: no test specified\" && exit 1",
13
13
  "prebuild": "rimraf ./lib",
14
- "build": "tsc",
14
+ "build": "tsc -p tsconfig.json",
15
15
  "postbuild": "ncp ./src/ ./lib/src/ \"--filter=^(?!.*\\.ts$).*$\"",
16
16
  "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
17
17
  "lint": "tslint -p tsconfig.json"
@@ -34,7 +34,7 @@
34
34
  "openai"
35
35
  ],
36
36
  "devDependencies": {
37
- "@types/gettext-parser": "^4.0.4",
37
+ "@types/gettext-parser": "^9.0.0",
38
38
  "@types/jest": "^29.5.12",
39
39
  "jest": "^29.7.0",
40
40
  "ncp": "^2.0.0",
@@ -46,11 +46,13 @@
46
46
  "typescript": "^5.5.4"
47
47
  },
48
48
  "dependencies": {
49
+ "@anthropic-ai/sdk": "^0.99.0",
50
+ "@google/genai": "^2.6.0",
49
51
  "commander": "^12.1.0",
50
- "gettext-parser": "^8.0.0",
51
- "openai": "^4.56.0"
52
+ "gettext-parser": "^9.0.1",
53
+ "openai": "^6.39.0"
52
54
  },
53
55
  "engines": {
54
- "node": ">=18.0.0"
56
+ "node": ">=20.0.0"
55
57
  }
56
58
  }
package/lib/src/index.js CHANGED
@@ -30,9 +30,10 @@ class SharedOptionsCommand extends Command {
30
30
  }
31
31
  const translateCommand = new SharedOptionsCommand("translate")
32
32
  .description("translate po file (default command)")
33
- .addOption(new Option("-k, --key <key>", "openai api key").env("OPENAI_API_KEY"))
34
- .addOption(new Option("--host <host>", "openai api host").env("OPENAI_API_HOST"))
35
- .addOption(new Option("--model <model>", "openai model").env("OPENAI_MODEL").default("gpt-4o-mini"))
33
+ .addOption(new Option("-p, --provider <provider>", "api provider").choices(["openai", "anthropic", "gemini"]).default("openai"))
34
+ .addOption(new Option("-k, --key <key>", "api key (can also be set via OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY)"))
35
+ .addOption(new Option("--host <host>", "api host (can also be set via OPENAI_API_HOST, ANTHROPIC_API_HOST, GEMINI_API_HOST)"))
36
+ .addOption(new Option("--model <model>", "model to use (default: gpt-5-nano for openai, claude-haiku-4-5 for anthropic, gemini-2.5-flash for gemini)"))
36
37
  .addOption(new Option("--po <file>", "po file path").conflicts("dir"))
37
38
  .addOption(new Option("--dir <dir>", "po file directory").conflicts("po"))
38
39
  .option("-src, --source <lang>", "source language (ISO 639-1)", "en")
@@ -44,25 +45,43 @@ const translateCommand = new SharedOptionsCommand("translate")
44
45
  .addOption(new Option("-o, --output <file>", "output file path, overwirte po file by default").conflicts("dir"))
45
46
  .addCompileOptions()
46
47
  .action(async (args) => {
47
- const { key, host, model, po, dir, source, lang, verbose, output, context, contextLength, timeout } = args;
48
+ const { provider, key, host, model, po, dir, source, lang, verbose, output, context, contextLength, timeout } = args;
48
49
  if (host) {
49
- process.env.OPENAI_API_HOST = host;
50
+ if (provider === "openai")
51
+ process.env.OPENAI_API_HOST = host;
52
+ if (provider === "anthropic")
53
+ process.env.ANTHROPIC_API_HOST = host;
54
+ if (provider === "gemini")
55
+ process.env.GEMINI_API_HOST = host;
50
56
  }
51
57
  if (key) {
52
- process.env.OPENAI_API_KEY = key;
58
+ if (provider === "openai")
59
+ process.env.OPENAI_API_KEY = key;
60
+ if (provider === "anthropic")
61
+ process.env.ANTHROPIC_API_KEY = key;
62
+ if (provider === "gemini")
63
+ process.env.GEMINI_API_KEY = key;
53
64
  }
54
- // process.env.OPENAI_API_KEY is not set, exit
55
- if (!process.env.OPENAI_API_KEY) {
65
+ if (provider === "openai" && !process.env.OPENAI_API_KEY) {
56
66
  console.error("OPENAI_API_KEY is required");
57
67
  process.exit(1);
58
68
  }
59
- init();
69
+ if (provider === "anthropic" && !process.env.ANTHROPIC_API_KEY) {
70
+ console.error("ANTHROPIC_API_KEY is required");
71
+ process.exit(1);
72
+ }
73
+ if (provider === "gemini" && !process.env.GEMINI_API_KEY) {
74
+ console.error("GEMINI_API_KEY is required");
75
+ process.exit(1);
76
+ }
77
+ const finalModel = model || process.env.MODEL || process.env.OPENAI_MODEL || (provider === "anthropic" ? "claude-haiku-4-5" : provider === "gemini" ? "gemini-2.5-flash" : "gpt-5-nano");
78
+ init(provider);
60
79
  const compileOptions = getCompileOptions(args);
61
80
  if (po) {
62
- await translatePo(model, po, source, lang, verbose, output, context, parseInt(contextLength), parseInt(timeout), compileOptions);
81
+ await translatePo(provider, finalModel, po, source, lang, verbose, output, context, parseInt(contextLength), parseInt(timeout), compileOptions);
63
82
  }
64
83
  else if (dir) {
65
- await translatePoDir(model, dir, source, lang, verbose, context, parseInt(contextLength), parseInt(timeout), compileOptions);
84
+ await translatePoDir(provider, finalModel, dir, source, lang, verbose, context, parseInt(contextLength), parseInt(timeout), compileOptions);
66
85
  }
67
86
  else {
68
87
  console.error("po file or directory is required");
@@ -15,10 +15,6 @@ export function removeByOptions(potrans, options) {
15
15
  if (options?.fuzzy && fuzzyRegx.test(entry.comments?.flag || "")) {
16
16
  delete potrans.translations[ctx][msgid];
17
17
  }
18
- // remove obsolete
19
- if (options?.obsolete && obsoleteRegx.test(entry.comments?.flag || "")) {
20
- delete potrans.translations[ctx][msgid];
21
- }
22
18
  // remove untranslated
23
19
  if (options?.untranslated && msgstr.length === 0) {
24
20
  delete potrans.translations[ctx][msgid];
@@ -48,6 +44,8 @@ export function removeByOptions(potrans, options) {
48
44
  }
49
45
  }
50
46
  }
47
+ if (options?.obsolete && potrans.obsolete)
48
+ delete potrans.obsolete;
51
49
  return potrans;
52
50
  }
53
51
  //# sourceMappingURL=manipulate.js.map
package/lib/src/sync.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { CompileOptions } from "./utils.js";
2
- export declare function sync(po: string, pot: string, compileOptions?: CompileOptions): Promise<void>;
1
+ import { GetTextPoCompilerOptions } from "gettext-parser";
2
+ export declare function sync(po: string, pot: string, compileOptions?: GetTextPoCompilerOptions): Promise<void>;
@@ -1,7 +1,5 @@
1
- import { GetTextTranslation } from "gettext-parser";
2
- import { OpenAI } from "openai";
3
- import { CompileOptions } from "./utils.js";
4
- export declare function init(force?: boolean): OpenAI;
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>;
1
+ import { GetTextPoCompilerOptions, GetTextTranslation } from "gettext-parser";
2
+ export declare function init(provider: string, force?: boolean): void;
3
+ export declare function translate(provider: string, src: string, lang: string, model: string, translations: GetTextTranslation[], contextFile: string, timeout: number): Promise<void>;
4
+ export declare function translatePo(provider: string, model: string, po: string, source: string, lang: string, verbose: boolean, output: string, contextFile: string, contextLength: number, timeout: number, compileOptions?: GetTextPoCompilerOptions): Promise<void>;
5
+ export declare function translatePoDir(provider: string, model: string, dir: string, source: string, lang: string, verbose: boolean, contextFile: string, contextLength: number, timeout: number, compileOptions?: GetTextPoCompilerOptions): Promise<void>;
@@ -1,5 +1,7 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import { GoogleGenAI } from "@google/genai";
1
3
  import * as fs from "fs";
2
- import { OpenAI } from "openai";
4
+ import OpenAI from "openai";
3
5
  import path from "path";
4
6
  import { fileURLToPath } from "url";
5
7
  import pkg from "../package.json" with { type: "json" };
@@ -7,18 +9,36 @@ import { compilePo, copyFileIfNotExists, findConfig, parsePo, printProgress } fr
7
9
  const __filename = fileURLToPath(import.meta.url);
8
10
  const __dirname = path.dirname(__filename);
9
11
  let _openai;
12
+ let _anthropic;
13
+ let _gemini;
10
14
  let _systemprompt;
11
15
  let _userprompt;
12
16
  let _userdict;
13
- export function init(force) {
14
- if (!_openai || force) {
15
- let configuration = {
16
- apiKey: process.env.OPENAI_API_KEY
17
+ export function init(provider, force) {
18
+ if (provider === "openai" && (!_openai || force)) {
19
+ _openai = new OpenAI({
20
+ apiKey: process.env.OPENAI_API_KEY,
21
+ baseURL: process.env.OPENAI_API_HOST
22
+ ? process.env.OPENAI_API_HOST.replace(/\/+$/, "") + "/v1"
23
+ : undefined
24
+ });
25
+ }
26
+ else if (provider === "anthropic" && (!_anthropic || force)) {
27
+ _anthropic = new Anthropic({
28
+ apiKey: process.env.ANTHROPIC_API_KEY,
29
+ baseURL: process.env.ANTHROPIC_API_HOST ? process.env.ANTHROPIC_API_HOST.replace(/\/+$/, "") : undefined
30
+ });
31
+ }
32
+ else if (provider === "gemini" && (!_gemini || force)) {
33
+ const options = {
34
+ apiKey: process.env.GEMINI_API_KEY || ""
17
35
  };
18
- _openai = new OpenAI(configuration);
19
- if (process.env.OPENAI_API_HOST) {
20
- _openai.baseURL = process.env.OPENAI_API_HOST.replace(/\/+$/, "") + "/v1";
36
+ if (process.env.GEMINI_API_HOST) {
37
+ options.httpOptions = {
38
+ baseUrl: process.env.GEMINI_API_HOST.replace(/\/+$/, "")
39
+ };
21
40
  }
41
+ _gemini = new GoogleGenAI(options);
22
42
  }
23
43
  // load systemprompt.txt from project
24
44
  if (!_systemprompt || force) {
@@ -34,9 +54,8 @@ export function init(force) {
34
54
  copyFileIfNotExists(userdict, path.join(__dirname, "dictionary.json"));
35
55
  _userdict = { default: JSON.parse(fs.readFileSync(userdict, "utf-8")) };
36
56
  }
37
- return _openai;
38
57
  }
39
- export async function translate(src, lang, model, translations, contextFile, timeout) {
58
+ export async function translate(provider, src, lang, model, translations, contextFile, timeout) {
40
59
  const lang_code = lang
41
60
  .toLowerCase()
42
61
  .trim()
@@ -48,56 +67,98 @@ export async function translate(src, lang, model, translations, contextFile, tim
48
67
  }
49
68
  return acc;
50
69
  }, { 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");
70
+ const escapePseudoXmlAttr = (value) => String(value)
71
+ .replace(/&/g, "&amp;")
72
+ .replace(/"/g, "&quot;")
73
+ .replace(/</g, "&lt;")
74
+ .replace(/>/g, "&gt;")
75
+ .replace(/\r?\n/g, " &#10; ");
59
76
  const context = contextFile ? "\n\nContext: " + fs.readFileSync(contextFile, "utf-8") : "";
60
77
  const translationsContent = translations
61
78
  .map((tr, idx) => {
62
- const contextAttr = tr.msgctxt ? ` context="${tr.msgctxt}"` : "";
63
- return `<translate index="${idx + dicts.user.length + 1}"${contextAttr}>${tr.msgid}</translate>`;
79
+ const contextAttr = tr.msgctxt ? ` context="${escapePseudoXmlAttr(tr.msgctxt)}"` : "";
80
+ const noteAttr = tr.comments?.extracted ? ` note="${escapePseudoXmlAttr(tr.comments.extracted)}"` : "";
81
+ return `<translate index="${idx + dicts.user.length + 1}"${contextAttr}${noteAttr}>${tr.msgid}</translate>`;
64
82
  })
65
83
  .join("\n");
66
- const res = await _openai.chat.completions.create({
67
- model: model,
68
- temperature: process.env.OPENAI_MODEL_TMP ? parseFloat(process.env.OPENAI_MODEL_TMP) : 0.1,
69
- messages: [
70
- {
71
- role: "system",
72
- content: _systemprompt + context
73
- },
74
- {
75
- role: "user",
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). ` +
77
- notes
78
- },
79
- {
80
- role: "assistant",
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.`
82
- },
83
- // add userdict
84
+ const temperature = process.env.MODEL_TMP
85
+ ? parseFloat(process.env.MODEL_TMP)
86
+ : process.env.OPENAI_MODEL_TMP
87
+ ? parseFloat(process.env.OPENAI_MODEL_TMP)
88
+ : 0.1;
89
+ const systemContent = _systemprompt + context;
90
+ const initialUserContent = `${_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).`;
91
+ const initialAssistantContent = `Understood, I will translate your incoming \`${src}\` message(s) into \`${lang}\`, carefully following guidelines. Please go ahead and send your message(s) for translation.`;
92
+ let content = "";
93
+ if (provider === "openai") {
94
+ const messages = [
95
+ { role: "system", content: systemContent },
96
+ { role: "user", content: initialUserContent },
97
+ { role: "assistant", content: initialAssistantContent },
84
98
  ...(dicts.user.length > 0
85
99
  ? [
86
100
  { role: "user", content: dicts.user.join("\n") },
87
101
  { role: "assistant", content: dicts.assistant.join("\n") }
88
102
  ]
89
103
  : []),
90
- // add user translations
91
- {
92
- role: "user",
93
- content: translationsContent
104
+ { role: "user", content: translationsContent }
105
+ ];
106
+ const res = await _openai.chat.completions.create({ model, temperature, messages }, { timeout, stream: false });
107
+ content = res.choices[0].message.content ?? "";
108
+ }
109
+ else if (provider === "anthropic") {
110
+ const messages = [
111
+ { role: "user", content: initialUserContent },
112
+ { role: "assistant", content: initialAssistantContent },
113
+ ...(dicts.user.length > 0
114
+ ? [
115
+ { role: "user", content: dicts.user.join("\n") },
116
+ { role: "assistant", content: dicts.assistant.join("\n") }
117
+ ]
118
+ : []),
119
+ { role: "user", content: translationsContent }
120
+ ];
121
+ let res = await _anthropic.messages.create({ model, temperature, system: systemContent, messages, max_tokens: 4096 }, { timeout });
122
+ if (typeof res === "string") {
123
+ res = JSON.parse(res);
124
+ }
125
+ if (res.content && res.content.length > 0 && res.content[0].type === "text") {
126
+ content = res.content[0].text;
127
+ }
128
+ else {
129
+ console.error("Error: Anthropic response content is empty or not a text block.", res.content);
130
+ content = ""; // Default to empty string or handle error appropriately
131
+ }
132
+ }
133
+ else if (provider === "gemini") {
134
+ const contents = [
135
+ { role: "user", parts: [{ text: initialUserContent }] },
136
+ { role: "model", parts: [{ text: initialAssistantContent }] },
137
+ ...(dicts.user.length > 0
138
+ ? [
139
+ { role: "user", parts: [{ text: dicts.user.join("\n") }] },
140
+ { role: "model", parts: [{ text: dicts.assistant.join("\n") }] }
141
+ ]
142
+ : []),
143
+ { role: "user", parts: [{ text: translationsContent }] }
144
+ ];
145
+ const res = await _gemini.models.generateContent({
146
+ model,
147
+ contents,
148
+ config: {
149
+ systemInstruction: systemContent,
150
+ temperature,
151
+ maxOutputTokens: 4096,
152
+ httpOptions: { timeout }
94
153
  }
95
- ]
96
- }, {
97
- timeout,
98
- stream: false
99
- });
100
- const content = res.choices[0].message.content ?? "";
154
+ });
155
+ if (res.candidates && res.candidates.length > 0) {
156
+ const candidate = res.candidates[0];
157
+ if (candidate.content && candidate.content.parts && candidate.content.parts.length > 0) {
158
+ content = candidate.content.parts[0].text || "";
159
+ }
160
+ }
161
+ }
101
162
  translations.forEach((trans, idx) => {
102
163
  const tag = `<translated index="${idx + dicts.user.length + 1}">`;
103
164
  const s = content.indexOf(tag);
@@ -110,7 +171,7 @@ export async function translate(src, lang, model, translations, contextFile, tim
110
171
  }
111
172
  });
112
173
  }
113
- export async function translatePo(model, po, source, lang, verbose, output, contextFile, contextLength, timeout, compileOptions) {
174
+ export async function translatePo(provider, model, po, source, lang, verbose, output, contextFile, contextLength, timeout, compileOptions) {
114
175
  const potrans = await parsePo(po);
115
176
  if (!lang)
116
177
  lang = potrans.headers["Language"];
@@ -176,7 +237,7 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
176
237
  }
177
238
  if (c >= contextLength || i == list.length - 1) {
178
239
  try {
179
- await translate(source, lang, model, translations, contextFile, timeout);
240
+ await translate(provider, source, lang, model, translations, contextFile, timeout);
180
241
  if (verbose) {
181
242
  translations.forEach((trans) => {
182
243
  console.log(trans.msgid);
@@ -205,7 +266,7 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
205
266
  else {
206
267
  console.error(error.message);
207
268
  if (error.code == "ECONNABORTED") {
208
- console.log('you may need to set "HTTPS_PROXY" to reach openai api.');
269
+ console.log('you may need to set "HTTPS_PROXY" to reach api.');
209
270
  }
210
271
  }
211
272
  }
@@ -213,13 +274,13 @@ export async function translatePo(model, po, source, lang, verbose, output, cont
213
274
  }
214
275
  console.log("done.");
215
276
  }
216
- export async function translatePoDir(model = "gpt-3.5-turbo", dir, source, lang, verbose, contextFile, contextLength, timeout, compileOptions) {
277
+ export async function translatePoDir(provider, model, dir, source, lang, verbose, contextFile, contextLength, timeout, compileOptions) {
217
278
  const files = fs.readdirSync(dir);
218
279
  for (const file of files) {
219
280
  if (file.endsWith(".po")) {
220
281
  const po = path.join(dir, file);
221
282
  console.log(`translating ${po}`);
222
- await translatePo(model, po, source, lang, verbose, po, contextFile, contextLength, timeout, compileOptions);
283
+ await translatePo(provider, model, po, source, lang, verbose, po, contextFile, contextLength, timeout, compileOptions);
223
284
  }
224
285
  }
225
286
  }
@@ -13,12 +13,17 @@ Translation guidelines are as follows:
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
15
  4. **Context Handling**:
16
- - Some messages will include a context attribute in the translate tag, e.g., `<translate index="1" context="Menu">`.
16
+ - Some messages will include a context attribute in the `translate` tag, e.g., `<translate index="1" context="Menu">`.
17
17
  - Use this context to inform your translation but only return the translated text.
18
18
  - Example (translating from `en_GB` to `fr_FR`):
19
19
  Input: `<translate index="1" context="Menu">File</translate>`
20
20
  Output: `<translated index="1">Fichier</translated>`
21
21
 
22
+ 5. **Translator notes**:
23
+ - Some messages will include translator notes attribute in the `translate` tag, e.g., `<translate index="2" notes="Placeholder is a person's name">`.
24
+ - These notes are for you, to aid you in achieving an accurate translation.
25
+ - Pay attention to these notes, but do not include them in your response, and never respond to the notes.
26
+
22
27
  5. **Multiple Translations**:
23
28
  - You may receive multiple translation requests in a single input, each with a unique index.
24
29
  - Ensuring the complete number of requests are translated, even if they are repeated, while maintaining the original order when possible.
@@ -26,7 +31,7 @@ Translation guidelines are as follows:
26
31
  **Example (translating from `en_US` to `zh_CN`):**
27
32
  - Input:
28
33
  `<translate index="1">This is a message. </translate>`
29
- `<translate index="2"> Hello %s</translate>`
34
+ `<translate index="2" notes="TRANSLATORS: Placeholder is a person's first name"> Hello %s</translate>`
30
35
  - Output:
31
36
  `<translated index="1">这是一条信息</translated>`
32
37
  `<translated index="2"> 你好 %s</translated>`
@@ -1,4 +1,4 @@
1
- import { GetTextTranslations } from "gettext-parser";
1
+ import { GetTextTranslations, GetTextPoCompilerOptions } from "gettext-parser";
2
2
  /**
3
3
  * copy source file to destination file if destination file does not exist
4
4
  * @param file destination file path
@@ -8,12 +8,7 @@ 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 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>;
11
+ export declare function compilePo(data: GetTextTranslations, poFile: string, options?: GetTextPoCompilerOptions): Promise<void>;
17
12
  export declare function printProgress(progress: number, total: number, extra?: string): void;
18
13
  export declare function gitRootDir(dir?: string): string | null;
19
14
  /**
package/lib/src/utils.js CHANGED
@@ -36,7 +36,7 @@ export function parsePo(poFile, defaultCharset) {
36
36
  fs.readFile(poFile, (err, buffer) => {
37
37
  if (err)
38
38
  reject(err);
39
- const result = po.parse(buffer, defaultCharset ?? "utf-8");
39
+ const result = po.parse(buffer, { defaultCharset: defaultCharset ?? "utf-8" });
40
40
  resolve(result);
41
41
  });
42
42
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gpt-po",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "command tool for translate po files by gpt",
5
5
  "main": "lib/src/index.js",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  "start": "node lib/src/index.js",
12
12
  "test": "echo \"Error: no test specified\" && exit 1",
13
13
  "prebuild": "rimraf ./lib",
14
- "build": "tsc",
14
+ "build": "tsc -p tsconfig.json",
15
15
  "postbuild": "ncp ./src/ ./lib/src/ \"--filter=^(?!.*\\.ts$).*$\"",
16
16
  "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
17
17
  "lint": "tslint -p tsconfig.json"
@@ -34,7 +34,7 @@
34
34
  "openai"
35
35
  ],
36
36
  "devDependencies": {
37
- "@types/gettext-parser": "^4.0.4",
37
+ "@types/gettext-parser": "^9.0.0",
38
38
  "@types/jest": "^29.5.12",
39
39
  "jest": "^29.7.0",
40
40
  "ncp": "^2.0.0",
@@ -46,11 +46,13 @@
46
46
  "typescript": "^5.5.4"
47
47
  },
48
48
  "dependencies": {
49
+ "@anthropic-ai/sdk": "^0.99.0",
50
+ "@google/genai": "^2.6.0",
49
51
  "commander": "^12.1.0",
50
- "gettext-parser": "^8.0.0",
51
- "openai": "^4.56.0"
52
+ "gettext-parser": "^9.0.1",
53
+ "openai": "^6.39.0"
52
54
  },
53
55
  "engines": {
54
- "node": ">=18.0.0"
56
+ "node": ">=20.0.0"
55
57
  }
56
58
  }