gpt-po 1.1.0 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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 (choices: "gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-4-0314", "gpt-4-32k", "gpt-4-32k-0314", "gpt-3.5-turbo", "gpt-3.5-turbo-0301",
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 模型 (选项: "gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-4-0314", "gpt-4-32k", "gpt-4-32k-0314", "gpt-3.5-turbo", "gpt-3.5-turbo-0301", 默认: "gpt-4o")
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.0",
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.2",
37
- "@types/jest": "^29.5.1",
38
- "jest": "^29.5.0",
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.0.3",
41
- "rimraf": "^5.0.0",
42
- "ts-jest": "^29.1.0",
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.0.4"
46
+ "typescript": "^5.5.4"
46
47
  },
47
48
  "dependencies": {
48
- "commander": "^10.0.1",
49
- "gettext-parser": "^6.0.0",
50
- "openai": "^3.2.1"
49
+ "commander": "^12.1.0",
50
+ "gettext-parser": "^8.0.0",
51
+ "openai": "^4.56.0"
51
52
  },
52
53
  "engines": {
53
- "node": ">=12.0.0"
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
- "use strict";
3
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
- return new (P || (P = Promise))(function (resolve, reject) {
6
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
- step((generator = generator.apply(thisArg, _arguments || [])).next());
10
- });
11
- };
12
- Object.defineProperty(exports, "__esModule", { value: true });
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 commander_1.Option("-k, --key <key>", "openai api key").env("OPENAI_API_KEY"))
25
- .addOption(new commander_1.Option("--host <host>", "openai api host").env("OPENAI_API_HOST"))
26
- .addOption(new commander_1.Option("--model <model>", "openai model")
27
- .default("gpt-4o")
28
- .choices([
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 commander_1.Option("-o, --output <file>", "output file path, overwirte po file by default").conflicts("dir"))
45
- .action((args) => __awaiter(void 0, void 0, void 0, function* () {
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
- (0, translate_1.init)();
40
+ init();
59
41
  if (po) {
60
- yield (0, translate_1.translatePo)(model, po, source, lang, verbose, output, context);
42
+ await translatePo(model, po, source, lang, verbose, output, context);
61
43
  }
62
44
  else if (dir) {
63
- yield (0, translate_1.translatePoDir)(model, dir, source, lang, verbose, context);
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 }) => __awaiter(void 0, void 0, void 0, function* () {
76
- yield (0, sync_1.sync)(po, pot);
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 = (0, utils_1.findConfig)("systemprompt.txt");
89
- (0, utils_1.copyFileIfNotExists)(promptFile, copyFile, reset);
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
- (0, utils_1.openFileByDefault)(promptFile);
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 = (0, utils_1.findConfig)("dictionary.json");
87
+ const dictFile = findConfig("dictionary.json");
106
88
  if (explore) {
107
89
  // open user dictionary directory
108
- return (0, utils_1.openFileExplorer)(dictFile);
90
+ return openFileExplorer(dictFile);
109
91
  }
110
- (0, utils_1.copyFileIfNotExists)(dictFile, copyFile);
111
- (0, utils_1.openFileByDefault)(dictFile);
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) => __awaiter(void 0, void 0, void 0, function* () {
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 = yield (0, utils_1.parsePo)(po);
138
- yield (0, utils_1.compilePo)((0, manipulate_1.removeByOptions)(translations, options), output);
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
@@ -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 = (options === null || options === void 0 ? void 0 : options.referenceContains)
12
- ? /^\/([^\/]+)\/([igmsuy]*)/.exec(options === null || options === void 0 ? void 0 : options.referenceContains)
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 ((options === null || options === void 0 ? void 0 : options.fuzzy) && fuzzyRegx.test(((_a = entry.comments) === null || _a === void 0 ? void 0 : _a.flag) || "")) {
15
+ if (options?.fuzzy && fuzzyRegx.test(entry.comments?.flag || "")) {
20
16
  delete potrans.translations[ctx][msgid];
21
17
  }
22
18
  // remove obsolete
23
- if ((options === null || options === void 0 ? void 0 : options.obsolete) && obsoleteRegx.test(((_b = entry.comments) === null || _b === void 0 ? void 0 : _b.flag) || "")) {
19
+ if (options?.obsolete && obsoleteRegx.test(entry.comments?.flag || "")) {
24
20
  delete potrans.translations[ctx][msgid];
25
21
  }
26
22
  // remove untranslated
27
- if ((options === null || options === void 0 ? void 0 : options.untranslated) && msgstr.length === 0) {
23
+ if (options?.untranslated && msgstr.length === 0) {
28
24
  delete potrans.translations[ctx][msgid];
29
25
  }
30
26
  // remove translated
31
- if ((options === null || options === void 0 ? void 0 : options.translated) && msgstr.length > 0) {
27
+ if (options?.translated && msgstr.length > 0) {
32
28
  delete potrans.translations[ctx][msgid];
33
29
  }
34
30
  // remove translated not fuzzy
35
- if ((options === null || options === void 0 ? void 0 : options.translatedNotFuzzy) && msgstr.length > 0 && !fuzzyRegx.test(((_c = entry.comments) === null || _c === void 0 ? void 0 : _c.flag) || "")) {
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 ((options === null || options === void 0 ? void 0 : options.fuzzyTranslated) && msgstr.length > 0 && fuzzyRegx.test(((_d = entry.comments) === null || _d === void 0 ? void 0 : _d.flag) || "")) {
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 === null || options === void 0 ? void 0 : options.referenceContains) {
39
+ if (options?.referenceContains) {
44
40
  if (refRegx) {
45
- if (((_e = entry.comments) === null || _e === void 0 ? void 0 : _e.reference) && refRegx.test((_f = entry.comments) === null || _f === void 0 ? void 0 : _f.reference)) {
41
+ if (entry.comments?.reference && refRegx.test(entry.comments?.reference)) {
46
42
  delete potrans.translations[ctx][msgid];
47
43
  }
48
44
  }
49
- else if ((_h = (_g = entry.comments) === null || _g === void 0 ? void 0 : _g.reference) === null || _h === void 0 ? void 0 : _h.includes(options === null || options === void 0 ? void 0 : options.referenceContains)) {
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
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
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
- potrans.translations = potrans2.translations;
29
- yield (0, utils_1.compilePo)(potrans, po);
30
- });
14
+ }
15
+ potrans.translations = potrans2.translations;
16
+ await compilePo(potrans, po);
31
17
  }
32
- exports.sync = sync;
33
18
  //# sourceMappingURL=sync.js.map
@@ -1,20 +1,31 @@
1
- You are a language translation expert.
2
- You will be translating text from one language to another.
3
- Languages shall be indetified to you via their ISO 639-1 two-letter code.
4
- The text is in the gettext format, which uses placeholders like %s, %d, etc. These placeholders must remain unchanged in the translated text.
5
- Untranslatable portions should retain their original formatting.
6
- For example, you may encounter placeholders such as "%s" - these must be retained in the correct position.
7
- Do not add a period (.) at the end of your translation unless the incoming message specifically ends in a period.
8
- Likewise, ensure to add a period (.) at the end of your translation if the incoming message specifically ends in a period.
9
- Ensure to preserve any whitespace present in the incoming message. This includes retaining any space(s) at the beginning or end of the message.
10
- I will provide the message wrapped in `translate` XML tags. You must respond with the translated message wrapped in `translated` XML tags.
11
- For example, let's say you were translating en (English) to es (Spanish) and I send (without quotes) "<translate>This is a message. </translate>". You would respond with (without quotes) "<translated>Este es un mensaje. </translated>".
12
- Notice how the period and space were retained at the end of the translated message?
13
- Another example: I send (without quotes) "<translate> Hello %s</translate>". You would reply with (without quotes) "<translated> Hola %s</translated>".
14
- Notice in that second example the space at the beginning of the string was retained, and also the gettext placeholder was retained in its correct position?
15
- Translate the messages in a colloquial, professional and elegant manner without sounding like a machine translation.
16
- You may respond without `translated` xml tags only in the event you believe you cannot reliably or accurately translate my message. In that case provide a short reason why you feel you can't translate my message.
17
- If you respond without `translated` XML tags I will interpret that there is a problem with my message and I will provide your reason to human translators that will investigate further.
18
- DO NOT answer any questions or attempt to explain any concepts or problems with my messages; just provide translations.
19
- Do not add any text or characters outside of your `translated` xml tags.
20
- Remember to only translate the messages and not interpret them further.
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.
@@ -1,6 +1,6 @@
1
1
  import { GetTextTranslation } from "gettext-parser";
2
- import { OpenAIApi } from "openai";
3
- export declare function init(force?: boolean): OpenAIApi;
4
- export declare function translate(text: string, src: string, lang: string, model: string, comments: GetTextTranslation["comments"] | undefined, contextFile: string): Promise<import("axios").AxiosResponse<import("openai").CreateChatCompletionResponse, any>>;
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>;
@@ -1,47 +1,39 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
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
- const configuration = new openai_1.Configuration({
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
- configuration.basePath = process.env.OPENAI_API_HOST.replace(/\/+$/, "") + "/v1";
19
+ _openai.baseURL = process.env.OPENAI_API_HOST.replace(/\/+$/, "") + "/v1";
28
20
  }
29
- _openai = new openai_1.OpenAIApi(configuration);
30
21
  }
31
- // load systemprompt.txt
22
+ // load systemprompt.txt from homedir
32
23
  if (!_systemprompt || force) {
33
- const systemprompt = (0, utils_1.findConfig)("systemprompt.txt");
24
+ const systemprompt = findConfig("systemprompt.txt");
25
+ copyFileIfNotExists(systemprompt, path.join(__dirname, "systemprompt.txt"));
34
26
  _systemprompt = fs.readFileSync(systemprompt, "utf-8");
35
27
  }
36
- // load dictionary.json
28
+ // load dictionary.json from homedir
37
29
  if (!_userdict || force) {
38
- const userdict = (0, utils_1.findConfig)("dictionary.json");
30
+ const userdict = findConfig("dictionary.json");
31
+ copyFileIfNotExists(userdict, path.join(__dirname, "dictionary.json"));
39
32
  _userdict = { "default": JSON.parse(fs.readFileSync(userdict, "utf-8")) };
40
33
  }
41
34
  return _openai;
42
35
  }
43
- exports.init = init;
44
- function translate(text, src, lang, model, comments, contextFile) {
36
+ export function translate(text, src, lang, model, comments, contextFile) {
45
37
  const lang_code = lang.toLowerCase().trim().replace(/[\W_]+/g, "-");
46
38
  const dicts = Object.entries(_userdict[lang_code] || _userdict["default"])
47
39
  .filter(([k, _]) => text.toLowerCase().includes(k.toLowerCase()))
@@ -56,8 +48,8 @@ function translate(text, src, lang, model, comments, contextFile) {
56
48
  var context = "";
57
49
  if (contextFile !== undefined)
58
50
  context = "\n\n" + fs.readFileSync(contextFile, "utf-8");
59
- return _openai.createChatCompletion({
60
- model,
51
+ return _openai.chat.completions.create({
52
+ model: model,
61
53
  temperature: 0.1,
62
54
  messages: [
63
55
  {
@@ -66,11 +58,11 @@ function translate(text, src, lang, model, comments, contextFile) {
66
58
  },
67
59
  {
68
60
  role: "user",
69
- content: `Wait for my incoming message in "${src.toLowerCase()}" (an ISO 639-1 code) and translate it into "${lang.toLowerCase()}" (also an ISO 639-1 code), carefully following your system prompt. ` + notes
61
+ content: `Wait for my incoming message in "${src}" and translate it into "${lang}", carefully following your system prompt. ` + notes
70
62
  },
71
63
  {
72
64
  role: "assistant",
73
- content: `Understood, I will translate your incoming "${src.toLowerCase()}" message into "${lang.toUpperCase()}", interpreting those as ISO 639-1 codes and carefully following my system prompt. Please go ahead and send your message for translation.`
65
+ content: `Understood, I will translate your incoming "${src}" message into "${lang}", carefully following my system prompt. Please go ahead and send your message for translation.`
74
66
  },
75
67
  // add userdict here
76
68
  ...dicts,
@@ -83,114 +75,106 @@ function translate(text, src, lang, model, comments, contextFile) {
83
75
  timeout: 20000,
84
76
  });
85
77
  }
86
- exports.translate = translate;
87
- function translatePo(model, po, source, lang, verbose, output, contextFile) {
88
- var _a;
89
- return __awaiter(this, void 0, void 0, function* () {
90
- const potrans = yield (0, utils_1.parsePo)(po);
91
- if (!lang)
92
- lang = potrans.headers["Language"];
93
- if (!lang) {
94
- console.error("No language specified via po file or args");
95
- return;
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.`);
96
93
  }
97
- // try to load dictionary by lang-code if it not loaded
98
- const lang_code = lang.toLowerCase().trim().replace(/[\W_]+/g, "-");
99
- if (!_userdict[lang_code]) {
100
- const lang_dic_file = (0, utils_1.findConfig)(`dictionary-${lang_code}.json`);
101
- if (fs.existsSync(lang_dic_file)) {
102
- _userdict[lang_code] = JSON.parse(fs.readFileSync(lang_dic_file, "utf-8"));
103
- console.log(`dictionary-${lang_code}.json is loaded.`);
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;
104
105
  }
105
- }
106
- const list = [];
107
- const trimRegx = /(?:^ )|(?: $)/;
108
- let trimed = false;
109
- for (const [ctx, entries] of Object.entries(potrans.translations)) {
110
- for (const [msgid, trans] of Object.entries(entries)) {
111
- if (msgid == "")
112
- continue;
113
- if (!trans.msgstr[0]) {
114
- list.push(trans);
115
- continue;
116
- }
117
- else if (trimRegx.test(trans.msgstr[0])) {
118
- trimed = true;
119
- trans.msgstr[0] = trans.msgstr[0].trim();
120
- }
106
+ else if (trimRegx.test(trans.msgstr[0])) {
107
+ trimed = true;
108
+ trans.msgstr[0] = trans.msgstr[0].trim();
121
109
  }
122
110
  }
123
- if (trimed) {
124
- yield (0, utils_1.compilePo)(potrans, po);
125
- }
126
- if (list.length == 0) {
127
- console.log("done.");
128
- return;
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));
129
128
  }
130
- potrans.headers["Last-Translator"] = `gpt-po v${pkg.version}`;
131
- let err429 = false;
132
- let modified = false;
133
- for (let i = 0; i < list.length; i++) {
134
- if (i == 0)
135
- (0, utils_1.printProgress)(i, list.length);
136
- if (err429) {
137
- // sleep for 20 seconds.
138
- 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;
139
137
  }
140
- const trans = list[i];
141
- try {
142
- const res = yield translate(trans.msgid, source, lang, model, trans.comments, contextFile);
143
- var translated = ((_a = res.data.choices[0].message) === null || _a === void 0 ? void 0 : _a.content) || trans.msgstr[0];
144
- if (!translated.startsWith('<translated>') && !translated.endsWith('</translated>')) {
145
- // We got an error response
146
- console.log("Error: Unable to translate string [" + trans.msgid + "]. Bot says [" + translated + "]");
147
- continue;
148
- }
149
- // We got a valid translation response
150
- trans.msgstr[0] = translated.replace('<translated>', '').replace('</translated>', '');
151
- modified = true;
152
- if (verbose) {
153
- console.log(trans.msgid);
154
- console.log(trans.msgstr[0]);
155
- }
156
- (0, utils_1.printProgress)(i + 1, list.length);
157
- 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]);
158
144
  }
159
- catch (error) {
160
- if (error.response) {
161
- if (error.response.status == 429) {
162
- // caused by rate limit exceeded, should sleep for 20 seconds.
163
- err429 = true;
164
- --i;
165
- }
166
- else {
167
- console.error(error.response.status);
168
- console.log(error.response.data);
169
- }
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;
170
154
  }
171
155
  else {
172
- console.error(error.message);
173
- if (error.code == "ECONNABORTED") {
174
- console.log('you may need to set "HTTPS_PROXY" to reach openai api.');
175
- }
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.');
176
164
  }
177
165
  }
178
166
  }
179
- console.log("done.");
180
- });
167
+ }
168
+ console.log("done.");
181
169
  }
182
- exports.translatePo = translatePo;
183
- function translatePoDir(model = "gpt-3.5-turbo", dir, source, lang, verbose, contextFile) {
184
- return __awaiter(this, void 0, void 0, function* () {
185
- const files = fs.readdirSync(dir);
186
- for (const file of files) {
187
- if (file.endsWith(".po")) {
188
- const po = (0, path_1.join)(dir, file);
189
- console.log(`translating ${po}`);
190
- yield translatePo(model, po, source, lang, verbose, po, contextFile);
191
- }
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);
192
177
  }
193
- });
178
+ }
194
179
  }
195
- exports.translatePoDir = translatePoDir;
196
180
  //# sourceMappingURL=translate.js.map
@@ -13,7 +13,7 @@ export declare function printProgress(progress: number, total: number, extra?: s
13
13
  export declare function gitRootDir(dir?: string): string | null;
14
14
  /**
15
15
  * find config file in the following order:
16
- * 1. current directory
16
+ * 1. current working directory of the Node.js process
17
17
  * 2. git root directory
18
18
  * 3. home directory
19
19
  * @param fileName
package/lib/src/utils.js CHANGED
@@ -1,18 +1,15 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.openFileExplorer = exports.findConfig = exports.gitRootDir = exports.printProgress = exports.compilePo = exports.parsePo = exports.openFileByDefault = exports.copyFileIfNotExists = void 0;
4
- const child_process_1 = require("child_process");
5
- const fs = require("fs");
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
- exports.copyFileIfNotExists = copyFileIfNotExists;
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
- (0, child_process_1.spawn)(command, [filePath], { shell: true });
31
+ spawn(command, [filePath], { shell: true });
36
32
  }
37
- exports.openFileByDefault = openFileByDefault;
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 = gettext_parser_1.po.parse(buffer, defaultCharset !== null && defaultCharset !== void 0 ? defaultCharset : "utf-8");
39
+ var result = po.parse(buffer, defaultCharset ?? "utf-8");
45
40
  resolve(result);
46
41
  });
47
42
  });
48
43
  }
49
- exports.parsePo = parsePo;
50
- function compilePo(data, poFile) {
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
- exports.compilePo = compilePo;
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
- exports.printProgress = printProgress;
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,23 +79,23 @@ 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
- * 1. current directory
84
+ * 1. current working directory of the Node.js process
94
85
  * 2. git root directory
95
86
  * 3. home directory
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 = (0, os_1.homedir)();
93
+ const homeDir = homedir();
103
94
  const filePaths = [
104
- path.join(currentDir, "src", fileName),
95
+ path.join(currentDir, ".gpt-po", fileName),
96
+ path.join(currentDir, fileName),
105
97
  path.join(gitDir, ".gpt-po", fileName),
106
- path.join(homeDir, ".config", ".gpt-po", fileName)
98
+ path.join(homeDir, ".gpt-po", fileName)
107
99
  ];
108
100
  // check if file exists and return the first one
109
101
  for (const filePath of filePaths) {
@@ -114,22 +106,20 @@ function findConfig(fileName) {
114
106
  // if no file exists, return the default one
115
107
  return path.join(homeDir, ".gpt-po", fileName);
116
108
  }
117
- exports.findConfig = findConfig;
118
109
  /**
119
110
  * open file explorer by platform
120
111
  * @param location folder or file path
121
112
  */
122
- function openFileExplorer(location) {
123
- if ((0, os_1.platform)() === 'win32') {
124
- (0, child_process_1.exec)(`explorer.exe "${path.dirname(location)}"`);
113
+ export function openFileExplorer(location) {
114
+ if (platform() === 'win32') {
115
+ exec(`explorer.exe "${path.dirname(location)}"`);
125
116
  }
126
- else if ((0, os_1.platform)() === 'darwin') {
127
- (0, child_process_1.exec)(`open "${path.dirname(location)}"`);
117
+ else if (platform() === 'darwin') {
118
+ exec(`open "${path.dirname(location)}"`);
128
119
  }
129
120
  else {
130
121
  // Assuming a Linux-based system
131
- (0, child_process_1.exec)(`xdg-open "${path.dirname(location)}"`);
122
+ exec(`xdg-open "${path.dirname(location)}"`);
132
123
  }
133
124
  }
134
- exports.openFileExplorer = openFileExplorer;
135
125
  //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "gpt-po",
3
- "version": "1.1.0",
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.2",
37
- "@types/jest": "^29.5.1",
38
- "jest": "^29.5.0",
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.0.3",
41
- "rimraf": "^5.0.0",
42
- "ts-jest": "^29.1.0",
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.0.4"
46
+ "typescript": "^5.5.4"
46
47
  },
47
48
  "dependencies": {
48
- "commander": "^10.0.1",
49
- "gettext-parser": "^6.0.0",
50
- "openai": "^3.2.1"
49
+ "commander": "^12.1.0",
50
+ "gettext-parser": "^8.0.0",
51
+ "openai": "^4.56.0"
51
52
  },
52
53
  "engines": {
53
- "node": ">=12.0.0"
54
+ "node": ">=18.0.0"
54
55
  }
55
56
  }