UNPKG

i18n-ai-translate

Version:

Use LLMs to translate your i18n JSON to any language.

667 lines 30.5 kB
"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