UNPKG

i18n-translate-agent

Version:

An intelligent i18n translation agent powered by OpenAI, supporting automatic translation of JSON files with caching and progress tracking

214 lines (190 loc) 5.89 kB
import path from "path"; import fs from "fs"; import { intersection, notExistsToCreateFile, readJsonFileSync, } from "../utils.js"; import { ICacheFile, IDeleteSingleCacheParams, IGenerateCacheParams, IJson, IRegisterLanguageCacheFile, } from "../../types"; /** * 和翻译缓存json文件对比 返回存在更改的json文件 * @param {object} cacheObject 已经缓存的对象 (包含译文) * @param {object} translateObject 需要翻译的对象 (源语言) * @returns {object} 存在修改的对象 */ export const translateJSONDiffToJson = ( cacheObject: IJson, translateObject: IJson ) => { if (Object.values(cacheObject).length === 0) { console.log( `💾 [Cache] No cache found, translating all ${ Object.keys(translateObject).length } items` ); return translateObject; } // json文件内容diff const pendingTranslateMap: IJson = {}; const totalItems = Object.keys(translateObject).length; let cacheHits = 0; Object.entries(translateObject).forEach(([key, value]) => { //不存在该key 是新增的key,需要翻译 if (!cacheObject.hasOwnProperty(key)) { pendingTranslateMap[key] = value; } else { // 存在缓存key就算命中缓存,不需要重新翻译 cacheHits++; } }); const missedItems = Object.keys(pendingTranslateMap).length; console.log( `💾 [Cache] Cache hits: ${cacheHits}/${totalItems}, translating ${missedItems} items` ); return pendingTranslateMap; }; /** * 获取缓存文件 * @param {string} filePath 缓存文件路径 * @returns {Promise<{key:value}>} */ export const getCacheFileSync = async (filePath: string): Promise<IJson> => { if (fs.existsSync(filePath)) { return await readJsonFileSync(filePath); } else { return {}; } }; /** * 注册语言缓存文件 * @param {string} language */ export const registerLanguageCacheFile = async ( params: IRegisterLanguageCacheFile ) => { const { jsonMap, sourceFilePath, fileName, language, folderName } = params; const cacheFilePath = path.join(folderName, language, fileName); const sourceObject = await readJsonFileSync(sourceFilePath); const cacheObject = await readJsonFileSync(cacheFilePath); Object.entries(jsonMap).forEach(([key, value]) => { cacheObject[key] = value; // 存储翻译后的内容,而不是源语言内容 }); if (Object.values(jsonMap).length === 0) return; notExistsToCreateFile(folderName); notExistsToCreateFile(`${folderName}/${language}`); await fs.writeFileSync( cacheFilePath, JSON.stringify(cacheObject, null, 2), "utf8" ); }; /** * 根据已翻译文件生成缓存 */ export const generateCache = async (params: IGenerateCacheParams) => { const { sourceFolderPath, languages, exportFolderPath, sourceLanguage } = params; if (!languages || !Array.isArray(languages) || languages.length === 0) return; // 读取源文件夹下的所有文件 const sourceFileData = readAllFilesOfFolder( path.resolve(sourceFolderPath, sourceLanguage) ); // 源文件数据map const sourceFileMap: Record<string, ICacheFile> = {}; sourceFileData.forEach((item) => { sourceFileMap[item.fileName] = item; }); languages.forEach((language) => { // 获取当前语言的文件数据 const currentFileData = readAllFilesOfFolder( path.join(sourceFolderPath, language) ); currentFileData.forEach(({ data, fileName }) => { const translated: Record<string, string> = {}; // 遍历文件中的已经翻译的内容 Object.entries(data).forEach(([key, value]) => { if (value) { translated[key] = sourceFileMap[fileName].data[key]; } }); notExistsToCreateFile(path.resolve(exportFolderPath, language)); fs.writeFileSync( path.resolve(exportFolderPath, language, fileName), JSON.stringify(translated, null, 2), "utf-8" ); }); }); }; /** * 读取文件夹下的所有文件 并返回string类型的内容 * @param sourceFolderPath * @returns */ const readAllFilesOfFolder = (sourceFolderPath: string) => { try { if (!fs.existsSync(sourceFolderPath)) return []; const sourceFiles = fs.readdirSync(sourceFolderPath); return sourceFiles.map((fileName) => { return readFile(sourceFolderPath, fileName); }); } catch (error) { console.error("read folder error:", error); return []; } }; const readFile = (sourceFolderPath: string, fileName: string): ICacheFile => { const filePath = path.resolve(sourceFolderPath, fileName); try { const jsonData = fs.readFileSync(filePath, "utf-8"); const data = JSON.parse(jsonData) as Record<string, string>; return { fileName, filePath, data, }; } catch (error) { console.error("read file error:", error); return { fileName, filePath, data: {}, }; } }; /** * 批量删除指定语言的缓存信息 * @param params */ export const deleteBatchCache = (params: IDeleteSingleCacheParams) => { const { keys, cacheFolderPath, languages, cacheFileName } = params; let deleteLanguages: string[] = []; const localLanguages = fs.readdirSync(cacheFolderPath); if (!languages || !Array.isArray(languages) || languages.length === 0) { deleteLanguages = localLanguages; } else { deleteLanguages = intersection(localLanguages, languages); } if (deleteLanguages.length === 0) return; deleteLanguages.forEach((language) => { const jsonData = fs.readFileSync( path.resolve(cacheFolderPath, language, cacheFileName), "utf-8" ); const data = JSON.parse(jsonData); keys.forEach((key) => { delete data[key]; }); fs.writeFileSync( path.resolve(cacheFolderPath, language, cacheFileName), JSON.stringify(data, null, 2), "utf-8" ); }); };