gpt-po
Version:
command tool for translate po files by gpt
134 lines • 6.2 kB
JavaScript
import { Command, Option } from "commander";
import path from "path";
import { fileURLToPath } from "url";
import pkg from "../package.json" with { type: "json" };
import { removeByOptions } from "./manipulate.js";
import { sync } from "./sync.js";
import { init, translatePo, translatePoDir } from "./translate.js";
import { compilePo, copyFileIfNotExists, findConfig, openFileByDefault, openFileExplorer, parsePo } from "./utils.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const program = new Command();
program.name(pkg.name).version(pkg.version).description(pkg.description);
const getCompileOptions = (args) => {
const foldLength = args.poFoldLen === "false" ? 0 : parseInt(args.poFoldLen);
const sort = args.poSort;
const escapeCharacters = args.poEscChars;
if (isNaN(foldLength)) {
console.error("--po-fold-len must be a number or false");
process.exit(1);
}
return { foldLength, sort, escapeCharacters };
};
class SharedOptionsCommand extends Command {
addCompileOptions() {
return this.option("--po-fold-len <length>", "a gettext compile option, the length at which to fold message strings into newlines, set to 0 or false to disable folding", "120")
.option("--po-sort", "a gettext compile option, sort entries by msgid", false)
.option("--po-esc-chars", "a gettext compile option, escape characters in output, if false, will skip escape newlines and quotes characters functionality", true);
}
}
const translateCommand = new SharedOptionsCommand("translate")
.description("translate po file (default command)")
.addOption(new Option("-k, --key <key>", "openai api key").env("OPENAI_API_KEY"))
.addOption(new Option("--host <host>", "openai api host").env("OPENAI_API_HOST"))
.addOption(new Option("--model <model>", "openai model").env("OPENAI_MODEL").default("gpt-4o-mini"))
.addOption(new Option("--po <file>", "po file path").conflicts("dir"))
.addOption(new Option("--dir <dir>", "po file directory").conflicts("po"))
.option("-src, --source <lang>", "source language (ISO 639-1)", "en")
.option("-l, --lang <lang>", "target language (ISO 639-1)")
.option("--verbose", "print verbose log")
.option("--context <file>", "text file that provides the bot additional context")
.addOption(new Option("-o, --output <file>", "output file path, overwirte po file by default").conflicts("dir"))
.addCompileOptions()
.action(async (args) => {
const { key, host, model, po, dir, source, lang, verbose, output, context } = args;
if (host) {
process.env.OPENAI_API_HOST = host;
}
if (key) {
process.env.OPENAI_API_KEY = key;
}
// process.env.OPENAI_API_KEY is not set, exit
if (!process.env.OPENAI_API_KEY) {
console.error("OPENAI_API_KEY is required");
process.exit(1);
}
init();
const compileOptions = getCompileOptions(args);
if (po) {
await translatePo(model, po, source, lang, verbose, output, context, compileOptions);
}
else if (dir) {
await translatePoDir(model, dir, source, lang, verbose, context, compileOptions);
}
else {
console.error("po file or directory is required");
process.exit(1);
}
});
program.addCommand(translateCommand, { isDefault: true });
const syncCommand = new SharedOptionsCommand("sync")
.description("update po from pot file")
.requiredOption("--po <file>", "po file path")
.requiredOption("--pot <file>", "pot file path")
.option("-o, --output <file>", "output file path, overwirte po file by default")
.addCompileOptions()
.action(async (args) => {
const { po, pot } = args;
await sync(po, pot, getCompileOptions(args));
});
program.addCommand(syncCommand);
// 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 <lang>", "target language (ISO 639-1)")
.action((args) => {
const { explore, lang } = args;
// open `dictionary.json` file by system text default editor
const copyFile = __dirname + "/dictionary.json";
// find from user home path
const dictFile = findConfig(`dictionary${lang ? "-" + lang : ""}.json`);
if (explore) {
// open user dictionary directory
return openFileExplorer(dictFile);
}
if (!lang)
copyFileIfNotExists(dictFile, copyFile);
openFileByDefault(dictFile);
});
// program command `remove` with help text `remove po entries by options`
const removeCommand = new SharedOptionsCommand("remove")
.description("remove po entries by options")
.requiredOption("--po <file>", "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 <text>", "remove entries whose reference contains text, text can be a regular expression like /text/ig")
.option("-o, --output <file>", "output file path, overwirte po file by default")
.addCompileOptions()
.action(async (args) => {
this;
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, getCompileOptions(args));
console.log("done");
});
program.addCommand(removeCommand);
program.parse(process.argv);
//# sourceMappingURL=index.js.map