i18n-ai-translate
Version:
Use LLMs to translate your i18n JSON to any language.
196 lines (187 loc) • 7.67 kB
text/typescript
import {
CLI_HELP,
DEFAULT_TEMPLATED_STRING_PREFIX,
DEFAULT_TEMPLATED_STRING_SUFFIX,
} from "./constants";
import { Command } from "commander";
import { printError } from "./utils";
import { processModelArgs, processOverridePromptFile } from "./cli_helpers";
import { translateDirectoryDiff } from "./translate_directory";
import { translateFileDiff } from "./translate_file";
import fs, { mkdtempSync } from "fs";
import path from "path";
import type DryRun from "./interfaces/dry_run";
import type OverridePrompt from "./interfaces/override_prompt";
/**
* Builds the diff command for comparing i18n files or directories.
* @returns the diff command with its options and action.
*/
export default function buildDiffCommand(): Command {
return new Command("diff")
.requiredOption(
"-b, --before <fileOrDirectoryBefore>",
"Source i18n file or directory before changes, in the jsons/ directory if a relative path is given",
)
.requiredOption(
"-a, --after <fileOrDirectoryAfter>",
"Source i18n file or directory after changes, in the jsons/ directory if a relative path is given",
)
.requiredOption(
"-l, --input-language <inputLanguageCode>",
"The input language's code, in ISO6391 (e.g. en, fr)",
)
.requiredOption("-e, --engine <engine>", CLI_HELP.Engine)
.option("-m, --model <model>", CLI_HELP.Model)
.option("-r, --rate-limit-ms <rateLimitMs>", CLI_HELP.RateLimit)
.option("-k, --api-key <API key>", "API key")
.option("-h, --host <hostIP:port>", CLI_HELP.OllamaHost)
.option(
"--ensure-changed-translation",
CLI_HELP.EnsureChangedTranslation,
false,
)
.option(
"-p, --templated-string-prefix <prefix>",
"Prefix for templated strings",
DEFAULT_TEMPLATED_STRING_PREFIX,
)
.option(
"-s, --templated-string-suffix <suffix>",
"Suffix for templated strings",
DEFAULT_TEMPLATED_STRING_SUFFIX,
)
.option("-n, --batch-size <batchSize>", CLI_HELP.BatchSize)
.option(
"--skip-translation-verification",
CLI_HELP.SkipTranslationVerification,
false,
)
.option(
"--skip-styling-verification",
CLI_HELP.SkipStylingVerification,
false,
)
.option(
"--override-prompt <path to JSON file>",
CLI_HELP.OverridePromptFile,
)
.option("--verbose", CLI_HELP.Verbose, false)
.option("--prompt-mode <prompt-mode>", CLI_HELP.PromptMode)
.option("--batch-max-tokens <batch-max-tokens>", CLI_HELP.MaxTokens)
.option("--dry-run", CLI_HELP.DryRun, false)
.action(async (options: any) => {
const {
model,
chatParams,
rateLimitMs,
apiKey,
host,
promptMode,
batchSize,
batchMaxTokens,
} = processModelArgs(options);
let overridePrompt: OverridePrompt | undefined;
if (options.overridePrompt) {
overridePrompt = processOverridePromptFile(
options.overridePrompt,
);
}
let dryRun: DryRun | undefined;
if (options.dryRun) {
dryRun = {
basePath: mkdtempSync(
`/tmp/i18n-ai-translate-${new Date().toISOString().replace(/[:.]/g, "-")}-`,
),
};
}
const jsonFolder = path.resolve(process.cwd(), "jsons");
let beforeInputPath: string;
if (path.isAbsolute(options.before)) {
beforeInputPath = path.resolve(options.before);
} else {
beforeInputPath = path.resolve(jsonFolder, options.before);
if (!fs.existsSync(beforeInputPath)) {
beforeInputPath = path.resolve(
process.cwd(),
options.before,
);
}
}
let afterInputPath: string;
if (path.isAbsolute(options.after)) {
afterInputPath = path.resolve(options.after);
} else {
afterInputPath = path.resolve(jsonFolder, options.after);
if (!fs.existsSync(afterInputPath)) {
afterInputPath = path.resolve(process.cwd(), options.after);
}
}
if (
fs.statSync(beforeInputPath).isFile() !==
fs.statSync(afterInputPath).isFile()
) {
printError(
"--before and --after arguments must be both files or both directories",
);
return;
}
if (fs.statSync(beforeInputPath).isFile()) {
// Ensure they're in the same path
if (
path.dirname(beforeInputPath) !==
path.dirname(afterInputPath)
) {
printError("Input files are not in the same directory");
return;
}
await translateFileDiff({
apiKey,
batchMaxTokens,
batchSize,
chatParams,
dryRun,
engine: options.engine,
ensureChangedTranslation: options.ensureChangedTranslation,
host,
inputAfterFileOrPath: afterInputPath,
inputBeforeFileOrPath: beforeInputPath,
inputLanguageCode: options.inputLanguage,
model,
overridePrompt,
promptMode,
rateLimitMs,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification:
options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
verbose: options.verbose,
});
} else {
await translateDirectoryDiff({
apiKey,
baseDirectory: path.resolve(beforeInputPath, ".."),
batchMaxTokens: options.batchMaxTokens,
batchSize: options.batchSize,
chatParams,
dryRun,
engine: options.engine,
ensureChangedTranslation: options.ensureChangedTranslation,
host,
inputFolderNameAfter: afterInputPath,
inputFolderNameBefore: beforeInputPath,
inputLanguageCode: options.inputLanguage,
model,
overridePrompt,
promptMode,
rateLimitMs,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification:
options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
verbose: options.verbose,
});
}
});
}