i18n-ai-translate
Version:
Use LLMs to translate your i18n JSON to any language.
667 lines • 30.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.translate = translate;
exports.translateDiff = translateDiff;
exports.translateFile = translateFile;
exports.translateFileDiff = translateFileDiff;
exports.translateDirectory = translateDirectory;
exports.translateDirectoryDiff = translateDirectoryDiff;
const constants_1 = require("./constants");
const fastest_levenshtein_1 = require("fastest-levenshtein");
const flat_1 = require("flat");
const utils_1 = require("./utils");
const chat_factory_1 = __importDefault(require("./chat_interface/chat_factory"));
const rate_limiter_1 = __importDefault(require("./rate_limiter"));
const fs_1 = __importDefault(require("fs"));
const generate_1 = __importDefault(require("./generate"));
const path_1 = __importStar(require("path"));
/**
* Translate the input JSON to the given language
* @param options - The options for the translation
*/
async function translate(options) {
if (options.verbose) {
console.log(`Translating from ${options.inputLanguage} to ${options.outputLanguage}...`);
}
const rateLimiter = new rate_limiter_1.default(options.rateLimitMs, options.verbose ?? false);
const chats = {
generateTranslationChat: chat_factory_1.default.newChat(options.engine, options.model, rateLimiter, options.apiKey, options.host),
verifyStylingChat: chat_factory_1.default.newChat(options.engine, options.model, rateLimiter, options.apiKey, options.host),
verifyTranslationChat: chat_factory_1.default.newChat(options.engine, options.model, rateLimiter, options.apiKey, options.host),
};
const output = {};
const templatedStringPrefix = options.templatedStringPrefix || constants_1.DEFAULT_TEMPLATED_STRING_PREFIX;
const templatedStringSuffix = options.templatedStringSuffix || constants_1.DEFAULT_TEMPLATED_STRING_SUFFIX;
let flatInput = (0, flat_1.flatten)(options.inputJSON, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
for (const key in flatInput) {
if (Object.prototype.hasOwnProperty.call(flatInput, key)) {
flatInput[key] = flatInput[key].replaceAll("\n", `${templatedStringPrefix}NEWLINE${templatedStringSuffix}`);
}
}
const groups = [];
for (const key in flatInput) {
if (Object.prototype.hasOwnProperty.call(flatInput, key)) {
const val = flatInput[key];
const existingGroup = groups.find((group) => Object.values(group).some((entry) => {
const distPercent = (0, fastest_levenshtein_1.distance)(val, entry) /
Math.max(val.length, entry.length);
return distPercent < 0.3;
}));
if (existingGroup) {
existingGroup[key] = val;
}
else {
groups.push({ [key]: val });
}
}
}
for (let i = groups.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[groups[i], groups[j]] = [groups[j], groups[i]];
}
flatInput = {};
for (const groupObj of groups) {
for (const [k, v] of Object.entries(groupObj)) {
flatInput[k] = v;
}
}
const allKeys = Object.keys(flatInput);
const batchSize = Number(options.batchSize ?? constants_1.DEFAULT_BATCH_SIZE);
const batchStartTime = Date.now();
for (let i = 0; i < Object.keys(flatInput).length; i += batchSize) {
if (i > 0 && options.verbose) {
console.log(`Completed ${((i / Object.keys(flatInput).length) * 100).toFixed(0)}%`);
const roundedEstimatedTimeLeftSeconds = Math.round((((Date.now() - batchStartTime) / (i + 1)) *
(Object.keys(flatInput).length - i)) /
1000);
console.log(`Estimated time left: ${roundedEstimatedTimeLeftSeconds} seconds`);
}
const keys = allKeys.slice(i, i + batchSize);
const input = keys.map((x) => `"${flatInput[x]}"`).join("\n");
// eslint-disable-next-line no-await-in-loop
const generatedTranslation = await (0, generate_1.default)({
chats,
ensureChangedTranslation: options.ensureChangedTranslation ?? false,
input,
inputLanguage: `[${options.inputLanguage}]`,
keys,
outputLanguage: `[${options.outputLanguage}]`,
overridePrompt: options.overridePrompt,
skipStylingVerification: options.skipStylingVerification ?? false,
skipTranslationVerification: options.skipTranslationVerification ?? false,
templatedStringPrefix,
templatedStringSuffix,
verboseLogging: options.verbose ?? false,
});
if (generatedTranslation === "") {
console.error(`Failed to generate translation for ${options.outputLanguage}`);
break;
}
for (let j = 0; j < keys.length; j++) {
output[keys[j]] = generatedTranslation.split("\n")[j].slice(1, -1);
if (options.verbose)
console.log(`${keys[j].replaceAll("*", ".")}:\n${flatInput[keys[j]]}\n=>\n${output[keys[j]]}\n`);
}
}
// sort the keys
const sortedOutput = {};
for (const key of Object.keys(flatInput).sort()) {
sortedOutput[key] = output[key];
}
for (const key in sortedOutput) {
if (Object.prototype.hasOwnProperty.call(sortedOutput, key)) {
sortedOutput[key] = sortedOutput[key].replaceAll(`${templatedStringPrefix}NEWLINE${templatedStringSuffix}`, "\n");
}
}
const unflattenedOutput = (0, flat_1.unflatten)(sortedOutput, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
if (options.verbose) {
const endTime = Date.now();
const roundedSeconds = Math.round((endTime - batchStartTime) / 1000);
console.log(`Actual execution time: ${roundedSeconds} seconds`);
}
return unflattenedOutput;
}
/**
* Translate the difference of an input JSON to the given languages
* @param options - The options for the translation
*/
async function translateDiff(options) {
const flatInputBefore = (0, flat_1.flatten)(options.inputJSONBefore, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
const flatInputAfter = (0, flat_1.flatten)(options.inputJSONAfter, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
const flatToUpdateJSONs = {};
for (const lang in options.toUpdateJSONs) {
if (Object.prototype.hasOwnProperty.call(options.toUpdateJSONs, lang)) {
const flatToUpdateJSON = (0, flat_1.flatten)(options.toUpdateJSONs[lang], {
delimiter: constants_1.FLATTEN_DELIMITER,
});
flatToUpdateJSONs[lang] = flatToUpdateJSON;
}
}
const addedKeys = [];
const modifiedKeys = [];
const deletedKeys = [];
for (const key in flatInputBefore) {
if (flatInputBefore[key] !== flatInputAfter[key]) {
if (flatInputAfter[key] === undefined) {
deletedKeys.push(key);
}
else {
modifiedKeys.push(key);
}
}
}
for (const key in flatInputAfter) {
if (flatInputBefore[key] === undefined) {
addedKeys.push(key);
}
}
if (options.verbose) {
console.log(`Added keys: ${addedKeys.join("\n")}\n`);
console.log(`Modified keys: ${modifiedKeys.join("\n")}\n`);
console.log(`Deleted keys: ${deletedKeys.join("\n")}\n`);
}
for (const key of deletedKeys) {
for (const lang in flatToUpdateJSONs) {
if (Object.prototype.hasOwnProperty.call(flatToUpdateJSONs, lang)) {
delete flatToUpdateJSONs[lang][key];
}
}
}
for (const languageCode in flatToUpdateJSONs) {
if (Object.prototype.hasOwnProperty.call(flatToUpdateJSONs, languageCode)) {
const addedAndModifiedTranslations = {};
for (const key of addedKeys) {
addedAndModifiedTranslations[key] = flatInputAfter[key];
}
for (const key of modifiedKeys) {
addedAndModifiedTranslations[key] = flatInputAfter[key];
}
// eslint-disable-next-line no-await-in-loop
const translated = await translate({
apiKey: options.apiKey,
batchSize: options.batchSize,
chatParams: options.chatParams,
engine: options.engine,
ensureChangedTranslation: options.ensureChangedTranslation,
host: options.host,
inputJSON: addedAndModifiedTranslations,
inputLanguage: options.inputLanguage,
model: options.model,
outputLanguage: languageCode,
overridePrompt: options.overridePrompt,
rateLimitMs: options.rateLimitMs,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification: options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
verbose: options.verbose,
});
const flatTranslated = (0, flat_1.flatten)(translated, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
for (const key in flatTranslated) {
if (Object.prototype.hasOwnProperty.call(flatTranslated, key)) {
flatToUpdateJSONs[languageCode][key] = flatTranslated[key];
}
}
// Sort the keys
flatToUpdateJSONs[languageCode] = Object.keys(flatToUpdateJSONs[languageCode])
.sort()
.reduce((obj, key) => {
obj[key] = flatToUpdateJSONs[languageCode][key];
return obj;
}, {});
}
}
const unflatToUpdateJSONs = {};
for (const lang in flatToUpdateJSONs) {
if (Object.prototype.hasOwnProperty.call(flatToUpdateJSONs, lang)) {
unflatToUpdateJSONs[lang] = (0, flat_1.unflatten)(flatToUpdateJSONs[lang], {
delimiter: constants_1.FLATTEN_DELIMITER,
});
}
}
return unflatToUpdateJSONs;
}
/**
* Wraps translate to take an input file and output its translation to another file
* @param options - The file translation's options
*/
async function translateFile(options) {
let inputJSON = {};
try {
const inputFile = fs_1.default.readFileSync(options.inputFilePath, "utf-8");
inputJSON = JSON.parse(inputFile);
}
catch (e) {
console.error(`Invalid input JSON: ${e}`);
return;
}
const inputLanguage = (0, utils_1.getLanguageCodeFromFilename)(options.inputFilePath);
let outputLanguage = "";
if (options.forceLanguageName) {
outputLanguage = options.forceLanguageName;
}
else {
outputLanguage = (0, utils_1.getLanguageCodeFromFilename)(options.outputFilePath);
}
try {
const outputJSON = await translate({
apiKey: options.apiKey,
batchSize: options.batchSize,
chatParams: options.chatParams,
engine: options.engine,
ensureChangedTranslation: options.ensureChangedTranslation,
host: options.host,
inputJSON,
inputLanguage,
model: options.model,
outputLanguage,
overridePrompt: options.overridePrompt,
rateLimitMs: options.rateLimitMs,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification: options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
verbose: options.verbose,
});
const outputText = JSON.stringify(outputJSON, null, 4);
fs_1.default.writeFileSync(options.outputFilePath, `${outputText}\n`);
}
catch (err) {
console.error(`Failed to translate file to ${outputLanguage}: ${err}`);
}
}
/**
* Wraps translateDiff to take two versions of a source file and update
* the target translation's file by only modifying keys that changed in the source
* @param options - The file diff translation's options
*/
async function translateFileDiff(options) {
// Get all the *json files from the same path as beforeInputPath
const outputFilesOrPaths = fs_1.default
.readdirSync(path_1.default.dirname(options.inputBeforeFileOrPath))
.filter((file) => file.endsWith(".json"))
.filter((file) => file !== path_1.default.basename(options.inputBeforeFileOrPath) &&
file !== path_1.default.basename(options.inputAfterFileOrPath))
.map((file) => path_1.default.resolve(path_1.default.dirname(options.inputBeforeFileOrPath), file));
const jsonFolder = path_1.default.resolve(process.cwd(), "jsons");
let inputBeforePath;
let inputAfterPath;
if (path_1.default.isAbsolute(options.inputBeforeFileOrPath)) {
inputBeforePath = path_1.default.resolve(options.inputBeforeFileOrPath);
}
else {
inputBeforePath = path_1.default.resolve(jsonFolder, options.inputBeforeFileOrPath);
if (!fs_1.default.existsSync(inputBeforePath)) {
inputBeforePath = path_1.default.resolve(process.cwd(), options.inputBeforeFileOrPath);
}
}
if (path_1.default.isAbsolute(options.inputAfterFileOrPath)) {
inputAfterPath = path_1.default.resolve(options.inputAfterFileOrPath);
}
else {
inputAfterPath = path_1.default.resolve(jsonFolder, options.inputAfterFileOrPath);
}
const outputPaths = [];
for (const outputFileOrPath of outputFilesOrPaths) {
let outputPath;
if (path_1.default.isAbsolute(outputFileOrPath)) {
outputPath = path_1.default.resolve(outputFileOrPath);
}
else {
outputPath = path_1.default.resolve(jsonFolder, outputFileOrPath);
if (!fs_1.default.existsSync(jsonFolder)) {
outputPath = path_1.default.resolve(process.cwd(), outputFileOrPath);
}
}
outputPaths.push(outputPath);
}
let inputBeforeJSON = {};
let inputAfterJSON = {};
try {
let inputFile = fs_1.default.readFileSync(inputBeforePath, "utf-8");
inputBeforeJSON = JSON.parse(inputFile);
inputFile = fs_1.default.readFileSync(inputAfterPath, "utf-8");
inputAfterJSON = JSON.parse(inputFile);
}
catch (e) {
console.error(`Invalid input JSON: ${e}`);
return;
}
const toUpdateJSONs = {};
const languageCodeToOutputPath = {};
for (const outputPath of outputPaths) {
const languageCode = (0, utils_1.getLanguageCodeFromFilename)(path_1.default.basename(outputPath));
if (!languageCode) {
throw new Error("Invalid output file name. Use a valid ISO 639-1 language code as the file name.");
}
try {
const outputFile = fs_1.default.readFileSync(outputPath, "utf-8");
toUpdateJSONs[languageCode] = JSON.parse(outputFile);
languageCodeToOutputPath[languageCode] = outputPath;
}
catch (e) {
console.error(`Invalid output JSON: ${e}`);
}
}
try {
const outputJSON = await translateDiff({
apiKey: options.apiKey,
batchSize: options.batchSize,
chatParams: options.chatParams,
engine: options.engine,
ensureChangedTranslation: options.ensureChangedTranslation,
host: options.host,
inputJSONAfter: inputAfterJSON,
inputJSONBefore: inputBeforeJSON,
inputLanguage: options.inputLanguageCode,
model: options.model,
overridePrompt: options.overridePrompt,
rateLimitMs: options.rateLimitMs,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification: options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
toUpdateJSONs,
verbose: options.verbose,
});
for (const language in outputJSON) {
if (Object.prototype.hasOwnProperty.call(outputJSON, language)) {
const outputText = JSON.stringify(outputJSON[language], null, 4);
fs_1.default.writeFileSync(languageCodeToOutputPath[language], `${outputText}\n`);
}
}
}
catch (err) {
console.error(`Failed to translate file diff: ${err}`);
}
}
/**
* Wraps translate to take all keys of all files in a directory and re-create the exact
* directory structure and translations for the target language
* @param options - The directory translation's options
*/
async function translateDirectory(options) {
const jsonFolder = path_1.default.resolve(process.cwd(), "jsons");
let fullBasePath;
if (path_1.default.isAbsolute(options.baseDirectory)) {
fullBasePath = path_1.default.resolve(options.baseDirectory);
}
else {
fullBasePath = path_1.default.resolve(jsonFolder, options.baseDirectory);
if (!fs_1.default.existsSync(fullBasePath)) {
fullBasePath = path_1.default.resolve(process.cwd(), options.baseDirectory);
}
}
const sourceLanguagePath = path_1.default.resolve(fullBasePath, options.inputLanguage);
if (!fs_1.default.existsSync(sourceLanguagePath)) {
throw new Error(`Source language path does not exist. sourceLanguagePath = ${sourceLanguagePath}`);
}
const sourceFilePaths = (0, utils_1.getAllFilesInPath)(sourceLanguagePath);
const inputJSON = {};
for (const sourceFilePath of sourceFilePaths) {
const fileContents = fs_1.default.readFileSync(sourceFilePath, "utf-8");
const fileJSON = JSON.parse(fileContents);
const flatJSON = (0, flat_1.flatten)(fileJSON, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
for (const key in flatJSON) {
if (Object.prototype.hasOwnProperty.call(flatJSON, key)) {
inputJSON[(0, utils_1.getTranslationDirectoryKey)(sourceFilePath, key, options.inputLanguage, options.outputLanguage)] = flatJSON[key];
}
}
}
const inputLanguage = (0, utils_1.getLanguageCodeFromFilename)(options.inputLanguage);
let outputLanguage = "";
if (options.forceLanguageName) {
outputLanguage = options.forceLanguageName;
}
else {
outputLanguage = (0, utils_1.getLanguageCodeFromFilename)(options.outputLanguage);
}
try {
const outputJSON = (await translate({
apiKey: options.apiKey,
batchSize: options.batchSize,
chatParams: options.chatParams,
engine: options.engine,
ensureChangedTranslation: options.ensureChangedTranslation,
host: options.host,
inputJSON,
inputLanguage,
model: options.model,
outputLanguage,
overridePrompt: options.overridePrompt,
rateLimitMs: options.rateLimitMs,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification: options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
verbose: options.verbose,
}));
const filesToJSON = {};
for (const pathWithKey in outputJSON) {
if (Object.prototype.hasOwnProperty.call(outputJSON, pathWithKey)) {
const filePath = pathWithKey.split(":").slice(0, -1).join(":");
if (!filesToJSON[filePath]) {
filesToJSON[filePath] = {};
}
const key = pathWithKey.split(":").pop();
filesToJSON[filePath][key] = outputJSON[pathWithKey];
}
}
for (const perFileJSON in filesToJSON) {
if (Object.prototype.hasOwnProperty.call(filesToJSON, perFileJSON)) {
const unflattenedOutput = (0, flat_1.unflatten)(filesToJSON[perFileJSON], {
delimiter: constants_1.FLATTEN_DELIMITER,
});
const outputText = JSON.stringify(unflattenedOutput, null, 4);
fs_1.default.mkdirSync((0, path_1.dirname)(perFileJSON), { recursive: true });
fs_1.default.writeFileSync(perFileJSON, `${outputText}\n`);
}
}
}
catch (err) {
console.error(`Failed to translate directory to ${outputLanguage}: ${err}`);
}
}
/**
* Wraps translateDiff to take the changed keys of all files in a directory
* and write the translation of those keys in the target translation
* @param options - The directory translation diff's options
*/
async function translateDirectoryDiff(options) {
const jsonFolder = path_1.default.resolve(process.cwd(), "jsons");
let fullBasePath;
if (path_1.default.isAbsolute(options.baseDirectory)) {
fullBasePath = path_1.default.resolve(options.baseDirectory);
}
else {
fullBasePath = path_1.default.resolve(jsonFolder, options.baseDirectory);
if (!fs_1.default.existsSync(fullBasePath)) {
fullBasePath = path_1.default.resolve(process.cwd(), options.baseDirectory);
}
}
const sourceLanguagePathBefore = path_1.default.resolve(fullBasePath, options.inputFolderNameBefore);
const sourceLanguagePathAfter = path_1.default.resolve(fullBasePath, options.inputFolderNameAfter);
if (!fs_1.default.existsSync(sourceLanguagePathBefore)) {
throw new Error(`Source language path before does not exist. sourceLanguagePathBefore = ${sourceLanguagePathBefore}`);
}
if (!fs_1.default.existsSync(sourceLanguagePathAfter)) {
throw new Error(`Source language path after does not exist. sourceLanguagePathAfter = ${sourceLanguagePathAfter}`);
}
// TODO: abstract to fn
const sourceFilePathsBefore = (0, utils_1.getAllFilesInPath)(sourceLanguagePathBefore);
const inputJSONBefore = {};
for (const sourceFilePath of sourceFilePathsBefore) {
const fileContents = fs_1.default.readFileSync(sourceFilePath, "utf-8");
const fileJSON = JSON.parse(fileContents);
const flatJSON = (0, flat_1.flatten)(fileJSON, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
for (const key in flatJSON) {
if (Object.prototype.hasOwnProperty.call(flatJSON, key)) {
inputJSONBefore[(0, utils_1.getTranslationDirectoryKey)(sourceFilePath, key, options.inputLanguageCode)] = flatJSON[key];
}
}
}
const sourceFilePathsAfter = (0, utils_1.getAllFilesInPath)(sourceLanguagePathAfter);
const inputJSONAfter = {};
for (const sourceFilePath of sourceFilePathsAfter) {
const fileContents = fs_1.default.readFileSync(sourceFilePath, "utf-8");
const fileJSON = JSON.parse(fileContents);
const flatJSON = (0, flat_1.flatten)(fileJSON, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
for (const key in flatJSON) {
if (Object.prototype.hasOwnProperty.call(flatJSON, key)) {
inputJSONAfter[(0, utils_1.getTranslationDirectoryKey)(sourceFilePath.replace(options.inputFolderNameAfter, options.inputFolderNameBefore), key, options.inputLanguageCode)] = flatJSON[key];
}
}
}
const outputLanguagePaths = fs_1.default
.readdirSync(options.baseDirectory)
.filter((folder) => folder !== path_1.default.basename(options.inputFolderNameBefore) &&
folder !== path_1.default.basename(options.inputFolderNameAfter))
.map((folder) => path_1.default.resolve(options.baseDirectory, folder));
const toUpdateJSONs = {};
for (const outputLanguagePath of outputLanguagePaths) {
const files = (0, utils_1.getAllFilesInPath)(outputLanguagePath);
for (const file of files) {
const fileContents = fs_1.default.readFileSync(file, "utf-8");
const fileJSON = JSON.parse(fileContents);
const flatJSON = (0, flat_1.flatten)(fileJSON, {
delimiter: constants_1.FLATTEN_DELIMITER,
});
const relative = path_1.default.relative(options.baseDirectory, outputLanguagePath);
const segments = relative.split(path_1.default.sep).filter(Boolean);
const language = segments[0];
if (!toUpdateJSONs[language]) {
toUpdateJSONs[language] = {};
}
for (const key in flatJSON) {
if (Object.prototype.hasOwnProperty.call(flatJSON, key)) {
toUpdateJSONs[language][(0, utils_1.getTranslationDirectoryKey)(file.replace(outputLanguagePath, options.inputFolderNameBefore), key, options.inputLanguageCode)] = flatJSON[key];
}
}
}
}
try {
const perLanguageOutputJSON = await translateDiff({
apiKey: options.apiKey,
batchSize: options.batchSize,
chatParams: options.chatParams,
engine: options.engine,
ensureChangedTranslation: options.ensureChangedTranslation,
host: options.host,
inputJSONAfter,
inputJSONBefore,
inputLanguage: options.inputLanguageCode,
model: options.model,
overridePrompt: options.overridePrompt,
rateLimitMs: options.rateLimitMs,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification: options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
toUpdateJSONs,
verbose: options.verbose,
});
const filesToJSON = {};
for (const outputLanguage in perLanguageOutputJSON) {
if (Object.prototype.hasOwnProperty.call(perLanguageOutputJSON, outputLanguage)) {
const outputJSON = perLanguageOutputJSON[outputLanguage];
for (const pathWithKey in outputJSON) {
if (Object.prototype.hasOwnProperty.call(outputJSON, pathWithKey)) {
const filePath = pathWithKey
.split(":")
.slice(0, -1)
.join(":")
.replace(options.inputFolderNameBefore, `${options.baseDirectory}/${outputLanguage}`);
if (!filesToJSON[filePath]) {
filesToJSON[filePath] = {};
}
const key = pathWithKey.split(":").pop();
filesToJSON[filePath][key] = outputJSON[pathWithKey];
}
}
for (const perFileJSON in filesToJSON) {
if (Object.prototype.hasOwnProperty.call(filesToJSON, perFileJSON)) {
const unflattenedOutput = (0, flat_1.unflatten)(filesToJSON[perFileJSON], {
delimiter: constants_1.FLATTEN_DELIMITER,
});
const outputText = JSON.stringify(unflattenedOutput, null, 4);
fs_1.default.mkdirSync((0, path_1.dirname)(perFileJSON), { recursive: true });
fs_1.default.writeFileSync(perFileJSON, `${outputText}\n`);
}
}
}
}
}
catch (err) {
console.error(`Failed to translate directory diff: ${err}`);
}
// Remove any files in before not in after
const fileNamesBefore = sourceFilePathsBefore.map((x) => x.slice(sourceLanguagePathBefore.length));
const fileNamesAfter = sourceFilePathsAfter.map((x) => x.slice(sourceLanguagePathAfter.length));
const removedFiles = fileNamesBefore.filter((x) => !fileNamesAfter.includes(x));
for (const languagePath of outputLanguagePaths) {
for (const removedFile of removedFiles) {
const removedFilePath = languagePath + removedFile;
fs_1.default.rmSync(removedFilePath);
// Recursively cleanup parent folders if they're also empty
let folder = path_1.default.dirname(removedFilePath);
while (fs_1.default.readdirSync(folder).length === 0) {
const parentFolder = path_1.default.resolve(folder, "..");
fs_1.default.rmdirSync(folder);
folder = parentFolder;
}
}
}
}
//# sourceMappingURL=translate.js.map