diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..b6b8127
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+buy_me_a_coffee: ryanhex
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index f36e365..c7bb5c2 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,8 @@
{
"tabWidth": 2,
"useTabs": false,
- "printWidth": 100
+ "quoteProps": "preserve",
+ "printWidth": 110,
+ "trailingComma": "none",
+ "semi": true
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2a22a14
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog
+
+## [1.2.0] - 2024-11-26
+- Submit multiple entries per chat to boost speed and reduce tokens
+- Command `systemprompt` has been removed
+- Bug fixes
+
+## [1.1.2] - 2024-11-12
+- Update command options for OpenAI model
+- Dependency updates, target es2022, module(Nodejs 18+)
+
+## [1.0.11] - 2023-10-26
+- Add `gpt-po remove` command
+- improve prompt for translation
+
+## [1.0.9] - 2023-09-08
+- Can add dictionaries for each languages
\ No newline at end of file
diff --git a/README.md b/README.md
index 6ca9948..4e9342e 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,8 @@ Translation tool for gettext (po) files that supports custom system prompts and
Read in other languages: English | [简体中文](./README_zh-CN.md)
+
+
## Installation
```
@@ -25,8 +27,6 @@ Set `OPENAI_API_KEY` before using this tool.
- `gpt-po --dir .` Translate all po files in current directory to a designated target language.
- `gpt-po userdict` Modify or view user dictionaries
- `gpt-po userdict --explore` Explore user dictionaries, if you want add new dictionaries or modify existing dictionaries. dictionaries can be named as `dictionary-.json`, for example, `dictionary-zh.json` is the dictionary for Simplified Chinese.
-- `gpt-po systemprompt` Modify or view system prompts, if you are not sure how to use it, you can leave it alone
-- `gpt-po systemprompt --reset` Reset system prompts
```
Usage: gpt-po [options] [command]
@@ -40,7 +40,6 @@ Options:
Commands:
translate [options] translate po file (default command)
sync [options] update po from pot file
- systemprompt [options] open/edit system prompt
userdict [options] open/edit user dictionary
remove [options] remove po entries by options
help [command] display help for command
@@ -81,3 +80,14 @@ Options:
-rc, --reference-contains remove entries whose reference contains text, text can be a regular expression like /text/ig
-h, --help display help for command
```
+
+```
+Usage: gpt-po sync [options]
+
+update po from pot file
+
+Options:
+ --po po file path
+ --pot pot file path
+ -h, --help display help for command
+```
\ No newline at end of file
diff --git a/README_zh-CN.md b/README_zh-CN.md
index 853c1ad..f4d5eb4 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -7,6 +7,8 @@ gettext(po)文件翻译工具,支持自定义系统提示词和用户字典,
使用其他语言阅读:[English](./README.md) | 简体中文
+
+
## 安装
```
@@ -27,8 +29,6 @@ npm install gpt-po
- `gpt-po --dir .` 将当前目录下的所有 po 文件翻译成目标语言。
- `gpt-po userdict` 修改或查看用户词典。
- `gpt-po userdict --explore` 浏览用户词典,如果您想添加新词典或修改现有词典,词典可以命名为 `dictionary-.json`,例如 `dictionary-zh.json` 是简体中文词典。
-- `gpt-po systemprompt` 修改或查看系统提示,如果您不确定如何使用它,可以忽略。
-- `gpt-po systemprompt --reset` 重置系统提示。
```
用法: gpt-po [options] [command]
@@ -42,7 +42,6 @@ npm install gpt-po
命令:
translate [options] 翻译 po 文件(默认命令)
sync [options] 根据 pot 文件更新 po 文件
- systemprompt [options] 打开/编辑系统提示
userdict [options] 打开/编辑用户词典
remove [options] 通过选项删除 po 条目
help [command] 显示命令帮助
@@ -83,3 +82,14 @@ npm install gpt-po
-rc, --reference-contains 删除引用包含文本的条目,文本可以是正则表达式,例如 /text/ig
-h, --help 显示命令帮助
```
+
+```
+用法: gpt-po sync [options]
+
+从 pot 文件中更新条目到 po 文件
+
+Options:
+ --po po 文件路径
+ --pot pot 文件路径
+ -h, --help 显示命令帮助
+```
diff --git a/package-lock.json b/package-lock.json
index 526d2a5..d05fb83 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gpt-po",
- "version": "1.1.1",
+ "version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gpt-po",
- "version": "1.1.1",
+ "version": "1.2.0",
"license": "MIT",
"dependencies": {
"commander": "^12.1.0",
@@ -14,7 +14,7 @@
"openai": "^4.56.0"
},
"bin": {
- "gpt-po": "lib/src/wrapper.js"
+ "gpt-po": "lib/src/index.js"
},
"devDependencies": {
"@types/gettext-parser": "^4.0.4",
@@ -29,7 +29,7 @@
"typescript": "^5.5.4"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=18.0.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -2151,10 +2151,11 @@
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://mirrors.tencent.com/npm/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
diff --git a/package.json b/package.json
index d2ef86d..16178dc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gpt-po",
- "version": "1.1.2",
+ "version": "1.2.0",
"description": "command tool for translate po files by gpt",
"main": "lib/src/index.js",
"type": "module",
diff --git a/src/index.ts b/src/index.ts
index 83906fc..ac1dcaa 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env node
+#!/usr/bin/env node --no-warnings=ExperimentalWarning
import { Command, Option } from "commander";
import path from "path";
@@ -6,7 +6,14 @@ import { fileURLToPath } from "url";
import pkg from "../package.json" with { type: "json" };
import { sync } from "./sync.js";
import { init, translatePo, translatePoDir } from "./translate.js";
-import { copyFileIfNotExists, compilePo, findConfig, openFileByDefault, openFileExplorer, parsePo } from "./utils.js";
+import {
+ copyFileIfNotExists,
+ compilePo,
+ findConfig,
+ openFileByDefault,
+ openFileExplorer,
+ parsePo
+} from "./utils.js";
import { removeByOptions } from "./manipulate.js";
const __filename = fileURLToPath(import.meta.url);
@@ -29,9 +36,7 @@ program
.option("--verbose", "print verbose log")
.option("--context ", "text file that provides the bot additional context")
.addOption(
- new Option("-o, --output ", "output file path, overwirte po file by default").conflicts(
- "dir",
- ),
+ new Option("-o, --output ", "output file path, overwirte po file by default").conflicts("dir")
)
.action(async (args) => {
const { key, host, model, po, dir, source, lang, verbose, output, context } = args;
@@ -66,70 +71,65 @@ program
await sync(po, pot);
});
-// program command `systemprompt` with help text `open/edit system prompt`
-program
- .command("systemprompt")
- .description("open/edit system prompt")
- .option("--reset", "reset system prompt to default")
- .action((args) => {
- const { reset } = args;
- // open `systemprompt.txt` file by system text default editor
- const copyFile = __dirname + "/systemprompt.txt";
- // user home path
- const promptFile = findConfig("systemprompt.txt");
- copyFileIfNotExists(promptFile, copyFile, reset);
- if (reset) {
- console.log("systemprompt.txt reset to default");
- }
- openFileByDefault(promptFile);
- });
-
// program command `userdict` with help text `open/edit user dictionary`
program
.command("userdict")
.description("open/edit user dictionary")
.option("--explore", "open user dictionary directory")
+ .option("-l, --lang ", "target language (ISO 639-1)")
.action((args) => {
- const { explore } = args;
+ const { explore, lang } = args;
// open `dictionary.json` file by system text default editor
const copyFile = __dirname + "/dictionary.json";
- // user home path
- const dictFile = findConfig("dictionary.json");
+ // find from user home path
+ const dictFile = findConfig(`dictionary${lang ? "-" + lang : ""}.json`);
if (explore) {
// open user dictionary directory
return openFileExplorer(dictFile);
}
- copyFileIfNotExists(dictFile, copyFile);
+ if (!lang) copyFileIfNotExists(dictFile, copyFile);
openFileByDefault(dictFile);
});
- // program command `remove` with help text `remove po entries by options`
- program
- .command("remove")
- .description("remove po entries by options")
- .requiredOption("--po ", "po file path")
- .option("--fuzzy", "remove fuzzy entries")
- .option("-obs, --obsolete", "remove obsolete entries")
- .option("-ut, --untranslated", "remove untranslated entries")
- .option("-t, --translated", "remove translated entries")
- .option("-tnf, --translated-not-fuzzy", "remove translated not fuzzy entries")
- .option("-ft, --fuzzy-translated", "remove fuzzy translated entries")
- .option("-rc, --reference-contains ", "remove entries whose reference contains text, text can be a regular expression like /text/ig")
- .action(async (args) => {
- const { po, fuzzy, obsolete, untranslated, translated, translatedNotFuzzy, fuzzyTranslated, referenceContains } = args;
- const options = {
- fuzzy,
- obsolete,
- untranslated,
- translated,
- translatedNotFuzzy,
- fuzzyTranslated,
- referenceContains,
- };
- const output = args.output || po;
- const translations = await parsePo(po);
- await compilePo(removeByOptions(translations, options), output);
- console.log("done")
- });
+// program command `remove` with help text `remove po entries by options`
+program
+ .command("remove")
+ .description("remove po entries by options")
+ .requiredOption("--po ", "po file path")
+ .option("--fuzzy", "remove fuzzy entries")
+ .option("-obs, --obsolete", "remove obsolete entries")
+ .option("-ut, --untranslated", "remove untranslated entries")
+ .option("-t, --translated", "remove translated entries")
+ .option("-tnf, --translated-not-fuzzy", "remove translated not fuzzy entries")
+ .option("-ft, --fuzzy-translated", "remove fuzzy translated entries")
+ .option(
+ "-rc, --reference-contains ",
+ "remove entries whose reference contains text, text can be a regular expression like /text/ig"
+ )
+ .action(async (args) => {
+ const {
+ po,
+ fuzzy,
+ obsolete,
+ untranslated,
+ translated,
+ translatedNotFuzzy,
+ fuzzyTranslated,
+ referenceContains
+ } = args;
+ const options = {
+ fuzzy,
+ obsolete,
+ untranslated,
+ translated,
+ translatedNotFuzzy,
+ fuzzyTranslated,
+ referenceContains
+ };
+ const output = args.output || po;
+ const translations = await parsePo(po);
+ await compilePo(removeByOptions(translations, options), output);
+ console.log("done");
+ });
program.parse(process.argv);
diff --git a/src/manipulate.ts b/src/manipulate.ts
index f71367b..cf50226 100644
--- a/src/manipulate.ts
+++ b/src/manipulate.ts
@@ -15,7 +15,7 @@ export interface RemoveByOptions {
*/
export function removeByOptions(
potrans: GetTextTranslations,
- options: RemoveByOptions | undefined,
+ options: RemoveByOptions | undefined
): GetTextTranslations {
const fuzzyRegx = /\bfuzzy\b/;
const obsoleteRegx = /\bobsolete\b/;
@@ -60,7 +60,6 @@ export function removeByOptions(
delete potrans.translations[ctx][msgid];
}
}
-
}
}
return potrans;
diff --git a/src/systemprompt.txt b/src/systemprompt.txt
index b05c21d..bcc336d 100644
--- a/src/systemprompt.txt
+++ b/src/systemprompt.txt
@@ -1,31 +1 @@
-You are a language translation expert. You will translate text from one language to another, following these guidelines:
-
-1. **Language Codes**:
- - 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`).
- - 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.
-
-2. **Placeholder Handling**:
- - Maintain the positions of placeholders (e.g., %s, %d, {example}) in the translated text. Do not translate placeholders.
-
-3. **Formatting**:
- - Preserve the formatting of untranslatable portions.
- - Retain any whitespace at the beginning or end of the message.
- - Add or omit a period (.) at the end of your translation to match the incoming message.
-
-4. **XML Tags**:
- - Input messages will be wrapped in `` XML tags.
- - Respond with the translated message wrapped in `` XML tags.
-
-5. **Quality**:
- - Translate in a colloquial, professional, and elegant manner without sounding like a machine translation.
-
-6. **Error Handling**:
- - If you cannot reliably translate a message, respond without `` XML tags and provide a short reason why.
-
-**Examples**:
-- Input: `This is a message. `
- - Output: `Este es un mensaje. `
-- Input: ` Hello %s`
- - Output: ` Hola %s`
-
-Do not answer questions or explain concepts. Only provide translations within `` XML tags unless you need to respond with a short error reason.
+You are a language translation expert. You will carefully follow the translation guidelines to translate the incoming XML messages from one language to another.
\ No newline at end of file
diff --git a/src/translate.ts b/src/translate.ts
index d3d09a2..583c6cd 100644
--- a/src/translate.ts
+++ b/src/translate.ts
@@ -1,6 +1,7 @@
import * as fs from "fs";
import { GetTextTranslation } from "gettext-parser";
import { OpenAI } from "openai";
+import { ChatCompletionMessageParam } from "openai/resources/index.mjs";
import path from "path";
import { fileURLToPath } from "url";
import pkg from "../package.json" with { type: "json" };
@@ -11,12 +12,13 @@ const __dirname = path.dirname(__filename);
let _openai: OpenAI;
let _systemprompt: string;
+let _userprompt: string;
let _userdict: { [lang: string]: { [key: string]: string } };
export function init(force?: boolean): OpenAI {
if (!_openai || force) {
let configuration = {
- apiKey: process.env.OPENAI_API_KEY,
+ apiKey: process.env.OPENAI_API_KEY
};
_openai = new OpenAI(configuration);
@@ -25,48 +27,62 @@ export function init(force?: boolean): OpenAI {
_openai.baseURL = process.env.OPENAI_API_HOST.replace(/\/+$/, "") + "/v1";
}
}
- // load systemprompt.txt from homedir
+ // load systemprompt.txt from project
if (!_systemprompt || force) {
- const systemprompt = findConfig("systemprompt.txt");
- copyFileIfNotExists(systemprompt, path.join(__dirname, "systemprompt.txt"));
- _systemprompt = fs.readFileSync(systemprompt, "utf-8");
+ _systemprompt = fs.readFileSync(path.join(__dirname, "systemprompt.txt"), "utf-8");
+ }
+ // load userprompt.txt from project
+ if (!_userprompt || force) {
+ _userprompt = fs.readFileSync(path.join(__dirname, "userprompt.txt"), "utf-8");
}
// load dictionary.json from homedir
if (!_userdict || force) {
const userdict = findConfig("dictionary.json");
copyFileIfNotExists(userdict, path.join(__dirname, "dictionary.json"));
- _userdict = { "default": JSON.parse(fs.readFileSync(userdict, "utf-8")) };
+ _userdict = { default: JSON.parse(fs.readFileSync(userdict, "utf-8")) };
}
return _openai;
}
-export function translate(
- text: string,
+export async function translate(
src: string,
lang: string,
model: string,
- comments: GetTextTranslation["comments"]|undefined,
+ translations: GetTextTranslation[],
contextFile: string
) {
- const lang_code = lang.toLowerCase().trim().replace(/[\W_]+/g, "-");
- const dicts = Object.entries(_userdict[lang_code] || _userdict["default"])
- .filter(([k, _]) => text.toLowerCase().includes(k.toLowerCase()))
- .map(([k, v]) => [
- { role: "user", content: k },
- { role: "assistant", content: v },
- ])
- .flat();
+ const lang_code = lang
+ .toLowerCase()
+ .trim()
+ .replace(/[\W_]+/g, "-");
+
+ const dicts = Object.entries(_userdict[lang_code] || _userdict["default"]).reduce(
+ (acc, [k, v], idx) => {
+ if (translations.some((tr) => tr.msgid.toLowerCase().includes(k.toLowerCase()))) {
+ acc.user.push(`${k}`);
+ acc.assistant.push(`${v}`);
+ }
+ return acc;
+ },
+ { user: [], assistant: [] }
+ );
- var notes: string = ""
+ const notes = translations
+ .reduce((acc: string[], tr) => {
+ if (tr.comments?.extracted) {
+ acc.push(tr.comments?.extracted);
+ }
+ return acc;
+ }, [])
+ .join("\n");
- if(comments != undefined && comments.extracted != undefined)
- notes = comments.extracted
+ const context = contextFile ? "\n\nContext: " + fs.readFileSync(contextFile, "utf-8") : "";
- var context = "";
- if(contextFile !== undefined)
- context = "\n\n" + fs.readFileSync(contextFile, "utf-8");
+ const translationsContent = translations
+ .map((tr, idx) => `${tr.msgid}`)
+ .join("\n");
- return _openai.chat.completions.create(
+ const res = await _openai.chat.completions.create(
{
model: model,
temperature: 0.1,
@@ -77,24 +93,44 @@ export function translate(
},
{
role: "user",
- content: `Wait for my incoming message in "${src}" and translate it into "${lang}", carefully following your system prompt. ` + notes
+ content: `${_userprompt}\n\nWait for my incoming message in "${src}" and translate it into "${lang}"(a language code and an optional region code). ` +
+ notes
},
{
role: "assistant",
- 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.`
+ content: `Understood, I will translate your incoming "${src}" message into "${lang}", carefully following guidelines. Please go ahead and send your message for translation.`
},
- // add userdict here
- ...dicts,
+ // add userdict
+ ...(dicts.user.length > 0
+ ? [
+ { role: "user", content: dicts.user.join("\n") },
+ { role: "assistant", content: dicts.assistant.join("\n") }
+ ]
+ : []),
+ // add user translations
{
role: "user",
- content: "" + text + ""
- },
- ],
- } as any,
+ content: translationsContent
+ }
+ ]
+ },
{
timeout: 20000,
- },
+ stream: false
+ }
);
+
+ const content = res.choices[0].message.content ?? "";
+ translations.forEach((trans, idx) => {
+ const tag = ``;
+ const s = content.indexOf(tag);
+ if (s > -1) {
+ const e = content.indexOf("", s);
+ trans.msgstr[0] = content.slice(s + tag.length, e);
+ } else {
+ console.error("Error: Unable to find translation for string [" + trans.msgid + "]");
+ }
+ });
}
export async function translatePo(
@@ -108,16 +144,18 @@ export async function translatePo(
) {
const potrans = await parsePo(po);
- if(!lang)
- lang = potrans.headers["Language"]
+ if (!lang) lang = potrans.headers["Language"];
- if(!lang) {
+ if (!lang) {
console.error("No language specified via po file or args");
return;
}
// try to load dictionary by lang-code if it not loaded
- const lang_code = lang.toLowerCase().trim().replace(/[\W_]+/g, "-");
+ const lang_code = lang
+ .toLowerCase()
+ .trim()
+ .replace(/[\W_]+/g, "-");
if (!_userdict[lang_code]) {
const lang_dic_file = findConfig(`dictionary-${lang_code}.json`);
if (fs.existsSync(lang_dic_file)) {
@@ -148,50 +186,49 @@ export async function translatePo(
return;
}
potrans.headers["Last-Translator"] = `gpt-po v${pkg.version}`;
+ const translations = [];
let err429 = false;
- let modified = false;
- for (let i = 0; i < list.length; i++) {
+ for (let i = 0, c = 0; i < list.length; i++) {
if (i == 0) printProgress(i, list.length);
if (err429) {
// sleep for 20 seconds.
await new Promise((resolve) => setTimeout(resolve, 20000));
}
const trans = list[i];
- try {
- const res = await translate(trans.msgid, source, lang, model, trans.comments, contextFile);
- var translated = res.choices[0].message?.content || trans.msgstr[0];
-
- if(!translated.startsWith('') && !translated.endsWith(''))
- {
- // We got an error response
- console.log("Error: Unable to translate string [" + trans.msgid + "]. Bot says [" + translated + "]");
- continue;
- }
-
- // We got a valid translation response
- trans.msgstr[0] = translated.replace('', '').replace('', '');
-
- modified = true;
- if (verbose) {
- console.log(trans.msgid);
- console.log(trans.msgstr[0]);
- }
- printProgress(i + 1, list.length);
- await compilePo(potrans, output || po);
- } catch (error: any) {
- if (error.response) {
- if (error.response.status == 429) {
- // caused by rate limit exceeded, should sleep for 20 seconds.
- err429 = true;
- --i;
- } else {
- console.error(error.response.status);
- console.log(error.response.data);
+ if (c < 2000) {
+ translations.push(trans);
+ c += trans.msgid.length;
+ }
+ if (c >= 2000 || i == list.length - 1) {
+ try {
+ await translate(source, lang, model, translations, contextFile);
+ if (verbose) {
+ translations.forEach((trans) => {
+ console.log(trans.msgid);
+ console.log(trans.msgstr[0]);
+ });
}
- } else {
- console.error(error.message);
- if (error.code == "ECONNABORTED") {
- console.log('you may need to set "HTTPS_PROXY" to reach openai api.')
+ translations.length = 0;
+ c = 0;
+ // update progress
+ printProgress(i + 1, list.length);
+ // save po file after each 2000 characters
+ await compilePo(potrans, output || po);
+ } catch (error: any) {
+ if (error.response) {
+ if (error.response.status == 429) {
+ // caused by rate limit exceeded, should sleep for 20 seconds.
+ err429 = true;
+ --i;
+ } else {
+ console.error(error.response.status);
+ console.log(error.response.data);
+ }
+ } else {
+ console.error(error.message);
+ if (error.code == "ECONNABORTED") {
+ console.log('you may need to set "HTTPS_PROXY" to reach openai api.');
+ }
}
}
}
diff --git a/src/userprompt.txt b/src/userprompt.txt
new file mode 100644
index 0000000..c19a0bb
--- /dev/null
+++ b/src/userprompt.txt
@@ -0,0 +1,27 @@
+Translation guidelines are as follows:
+
+1. **Placeholder Handling**:
+ - Maintain the positions of placeholders (e.g., %s, %d, {example}) in the translated text. Do not translate placeholders.
+
+2. **Formatting**:
+ - Preserve the formatting of untranslatable portions.
+ - Retain any whitespace at the beginning or end of the message.
+ - Add or omit a period (.) at the end of your translation to match the incoming message.
+
+3. **XML Tags and Indexing**:
+ - Input messages will be wrapped in `` XML tags with an index attribute, e.g., ``.
+ - Respond with the translated message wrapped in `` XML tags, including the same index attribute, e.g., ``.
+
+4. **Multiple Translations**:
+ - You may receive multiple translation requests in a single input, each with a unique index.
+ - Ensuring the complete number of requests are translated, even if they are repeated, while maintaining the original order when possible.
+
+**Examples**:
+- Input:
+ `This is a message. `
+ ` Hello %s`
+- Output:
+ `这是一条信息`
+ ` 你好 %s`
+
+Do not answer questions or explain concepts. Only provide translations within `` XML tags unless you need to respond with a short error reason.
\ No newline at end of file
diff --git a/src/utils.ts b/src/utils.ts
index abce6e7..180207d 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -38,14 +38,24 @@ export function parsePo(poFile: string, defaultCharset?: string): Promise {
fs.readFile(poFile, (err, buffer) => {
if (err) reject(err);
- var result = po.parse(buffer, defaultCharset ?? "utf-8");
+ const result = po.parse(buffer, defaultCharset ?? "utf-8");
resolve(result);
});
});
}
-export function compilePo(data: GetTextTranslations, poFile: string): Promise {
- const buffer = po.compile(data, { foldLength: 120 });
+export type compileOptions = {
+ foldLength?: number;
+ sort?: boolean | ((a: never, b: never) => number);
+ escapeCharacters?: boolean;
+};
+
+export function compilePo(
+ data: GetTextTranslations,
+ poFile: string,
+ options: compileOptions = { foldLength: 120, sort: false, escapeCharacters: true }
+): Promise {
+ const buffer = po.compile(data, options);
return new Promise((resolve, reject) => {
fs.writeFile(poFile, buffer, (err) => {
if (err) reject(err);
@@ -65,7 +75,7 @@ export function printProgress(progress: number, total: number, extra?: string):
process.stdout.write(`\r${bar}${dots} ${percent}% ${progress}/${total} ${extra || ""}`);
}
-export function gitRootDir(dir?: string): string|null {
+export function gitRootDir(dir?: string): string | null {
// if dir is not provided, use current working directory
dir = dir || process.cwd();
// check if dir is a git repository
@@ -87,7 +97,7 @@ export function gitRootDir(dir?: string): string|null {
* 1. current working directory of the Node.js process
* 2. git root directory
* 3. home directory
- * @param fileName
+ * @param fileName
* @returns full path of the config file
*/
export function findConfig(fileName: string): string {
@@ -99,7 +109,7 @@ export function findConfig(fileName: string): string {
path.join(currentDir, ".gpt-po", fileName),
path.join(currentDir, fileName),
path.join(gitDir, ".gpt-po", fileName),
- path.join(homeDir, ".gpt-po", fileName)
+ path.join(homeDir, ".gpt-po", fileName),
];
// check if file exists and return the first one
for (const filePath of filePaths) {
@@ -115,12 +125,12 @@ export function findConfig(fileName: string): string {
* @param location folder or file path
*/
export function openFileExplorer(location: string): void {
- if (platform() === 'win32') {
+ if (platform() === "win32") {
exec(`explorer.exe "${path.dirname(location)}"`);
- } else if (platform() === 'darwin') {
+ } else if (platform() === "darwin") {
exec(`open "${path.dirname(location)}"`);
} else {
// Assuming a Linux-based system
exec(`xdg-open "${path.dirname(location)}"`);
}
-}
\ No newline at end of file
+}