UNPKG

i18n-xy

Version:

自动提取React项目中的中文字符串并进行国际化的CLI工具

1,343 lines (1,326 loc) 182 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // main/cli/index.ts var import_commander = require("commander"); // package.json var version = "0.0.30"; // main/utils/fs.ts var fs = __toESM(require("fs-extra"), 1); var path = __toESM(require("path"), 1); var import_fast_glob = __toESM(require("fast-glob"), 1); function readFile2(filePath, encoding = "utf-8") { const absPath = path.resolve(process.cwd(), filePath); return fs.readFile(absPath, encoding); } function writeFile(filePath, data) { const absPath = path.resolve(process.cwd(), filePath); return fs.outputFile(absPath, data); } function writeFileWithTempDir(filePath, data, tempDir) { let targetPath; if (tempDir) { const relativePath = path.relative(process.cwd(), filePath); targetPath = path.resolve(process.cwd(), tempDir, relativePath); } else { targetPath = path.resolve(process.cwd(), filePath); } return fs.outputFile(targetPath, data); } function ensureDir2(dirPath) { const absPath = path.resolve(process.cwd(), dirPath); return fs.ensureDir(absPath); } function findTargetFiles(include, exclude = []) { return (0, import_fast_glob.default)(include, { ignore: exclude, dot: true, cwd: process.cwd() }); } function fileExists(filePath) { const absPath = path.resolve(process.cwd(), filePath); return fs.existsSync(absPath); } function readJsonSync2(filePath) { const absPath = path.resolve(process.cwd(), filePath); return fs.readJsonSync(absPath); } async function readJson(filePath, defaultValue) { try { const content = await readFile2(filePath, "utf-8"); return JSON.parse(content); } catch (error) { if (defaultValue !== void 0) { return defaultValue; } throw error; } } function writeJson(filePath, data, pretty = true) { const content = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data); return writeFile(filePath, content); } // main/config/index.ts var import_lodash = require("lodash"); // main/config/defaults.ts var defaultConfig = { // 基础语言配置 locale: "zh-CN", displayLanguage: "en-US", outputDir: "locales", // tempDir: undefined, // 可选,不设置则直接修改源文件 // 文件处理配置 include: [ "src/**/*.{js,jsx,ts,tsx}" ], exclude: [ "node_modules/**", "dist/**", "build/**", "**/*.test.{js,jsx,ts,tsx}", "**/*.spec.{js,jsx,ts,tsx}" ], // Key 生成配置 keyGeneration: { maxChineseLength: 10, // 最大汉字长度 hashLength: 6, // 哈希长度 maxRetryCount: 5, // 最大重试次数 reuseExistingKey: true, // 默认重复使用相同文案的key duplicateKeySuffix: "hash", // 重复key后缀模式,默认hash keyPrefix: "", // key前缀,默认为空 separator: "_", // 连接符,默认下划线 pinyinOptions: { // 拼音转换选项 toneType: "none", // 不带声调 type: "array" // 返回数组格式 } }, // 输出配置 output: { prettyJson: true, // 格式化 JSON 输出 localeFileName: "{locale}.json" // 语言文件名格式 }, // 日志配置 logging: { enabled: true, // 默认启用日志 level: "normal" // 默认正常级别 }, // 替换配置 replacement: { functionName: "$t", // 默认替换函数名 quoteType: "single", // 默认使用单引号 useOriginalTextAsKey: false, // 默认使用生成的key templateString: { enabled: true, // 默认启用模板字符串智能处理 preserveExpressions: true, // 默认保留原始表达式 splitStrategy: "smart" // 默认智能拆分策略 }, autoImport: { enabled: false, // 默认不启用自动引入 insertPosition: "afterImports", // 默认在import语句后插入 imports: {} } }, // 翻译配置 translation: { enabled: false, // 默认不启用翻译功能 provider: "baidu", // 默认使用百度翻译 defaultSourceLang: "zh", // 默认源语言:中文 defaultTargetLang: "en", // 默认目标语言:英文 concurrency: 5, // 默认并发数:5 (降低并发数避免API限制) retryTimes: 3, // 默认重试次数:3次(不包括第一次) retryDelay: 1e3, // 默认重试延迟:1秒 batchDelay: 500, // 默认批次延迟:0.5秒 baidu: { appid: "", // 需要用户配置 key: "" // 需要用户配置 } } }; // main/config/manager.ts var ConfigManagerClass = class { config = null; init(config) { this.config = config; } get() { if (!this.config) { throw new Error("ConfigManager: config not initialized!"); } return this.config; } reset() { this.config = null; } isInitialized() { return this.config !== null; } }; var ConfigManager = new ConfigManagerClass(); // main/config/validator.ts var ConfigValidator = class { /** * 检查配置的一致性和完整性 */ static validateConfigUsage() { var _a, _b, _c, _d, _e; const errors = []; const warnings = []; try { const config = ConfigManager.get(); if (!config.include || config.include.length === 0) { errors.push("\u914D\u7F6E\u9879 include \u4E0D\u80FD\u4E3A\u7A7A"); } if (!config.outputDir) { errors.push("\u914D\u7F6E\u9879 outputDir \u4E0D\u80FD\u4E3A\u7A7A"); } if (!config.locale) { errors.push("\u914D\u7F6E\u9879 locale \u4E0D\u80FD\u4E3A\u7A7A"); } if ((_a = config.translation) == null ? void 0 : _a.enabled) { if (!config.translation.provider) { errors.push("\u542F\u7528\u7FFB\u8BD1\u65F6\u5FC5\u987B\u6307\u5B9A provider"); } if (config.translation.provider === "baidu") { if (!((_b = config.translation.baidu) == null ? void 0 : _b.appid) || !((_c = config.translation.baidu) == null ? void 0 : _c.key)) { warnings.push("\u767E\u5EA6\u7FFB\u8BD1\u670D\u52A1\u7F3A\u5C11 appid \u6216 key \u914D\u7F6E"); } } } if (config.keyGeneration) { if (config.keyGeneration.maxChineseLength && config.keyGeneration.maxChineseLength <= 0) { warnings.push("maxChineseLength \u5E94\u8BE5\u5927\u4E8E0"); } if (config.keyGeneration.hashLength && config.keyGeneration.hashLength <= 0) { warnings.push("hashLength \u5E94\u8BE5\u5927\u4E8E0"); } } if (((_d = config.logging) == null ? void 0 : _d.level) && !["minimal", "normal", "verbose"].includes(config.logging.level)) { errors.push("logging.level \u5FC5\u987B\u662F minimal\u3001normal \u6216 verbose \u4E4B\u4E00"); } if (((_e = config.output) == null ? void 0 : _e.localeFileName) && !config.output.localeFileName.includes("{locale}")) { warnings.push("localeFileName \u5EFA\u8BAE\u5305\u542B {locale} \u5360\u4F4D\u7B26"); } } catch (error) { errors.push(`\u914D\u7F6E\u68C0\u67E5\u65F6\u53D1\u751F\u9519\u8BEF: ${error}`); } const isValid = errors.length === 0; if (!isValid) { Logger.error("\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25:", "minimal"); errors.forEach((error) => Logger.error(` - ${error}`, "minimal")); } if (warnings.length > 0) { Logger.warn("\u914D\u7F6E\u8B66\u544A:", "normal"); warnings.forEach((warning) => Logger.warn(` - ${warning}`, "normal")); } return { isValid, errors, warnings }; } /** * 检查是否存在配置与代码不一致的问题 */ static checkConfigConsistency() { var _a, _b; const config = ConfigManager.get(); Logger.verbose("\u68C0\u67E5\u914D\u7F6E\u4E00\u81F4\u6027..."); const expectedFileName = (((_a = config.output) == null ? void 0 : _a.localeFileName) ?? "{locale}.json").replace( "{locale}", config.locale ); Logger.verbose(`\u9884\u671F\u7684\u8BED\u8A00\u6587\u4EF6\u540D: ${expectedFileName}`); const functionName = ((_b = config.replacement) == null ? void 0 : _b.functionName) ?? "$t"; Logger.verbose(`\u9884\u671F\u7684\u66FF\u6362\u51FD\u6570\u540D: ${functionName}`); Logger.verbose(`\u8F93\u51FA\u76EE\u5F55: ${config.outputDir}`); if (config.tempDir) { Logger.verbose(`\u4E34\u65F6\u76EE\u5F55: ${config.tempDir}`); } Logger.verbose("\u914D\u7F6E\u4E00\u81F4\u6027\u68C0\u67E5\u5B8C\u6210"); } }; // main/config/index.ts function validateConfig(config) { var _a, _b, _c, _d; const errors = []; if (!config.include || !Array.isArray(config.include) || config.include.length === 0) { errors.push("include \u5B57\u6BB5\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4"); } if (!config.outputDir || typeof config.outputDir !== "string") { errors.push("outputDir \u5B57\u6BB5\u5FC5\u987B\u662F\u6709\u6548\u7684\u5B57\u7B26\u4E32\u8DEF\u5F84"); } if (!config.locale || typeof config.locale !== "string") { errors.push("locale \u5B57\u6BB5\u5FC5\u987B\u662F\u6709\u6548\u7684\u8BED\u8A00\u4EE3\u7801"); } if ((_a = config.translation) == null ? void 0 : _a.enabled) { if (!config.translation.provider) { errors.push("\u542F\u7528\u7FFB\u8BD1\u65F6\u5FC5\u987B\u6307\u5B9A translation.provider"); } if (config.translation.provider === "baidu") { if (!((_b = config.translation.baidu) == null ? void 0 : _b.appid) || !((_c = config.translation.baidu) == null ? void 0 : _c.key)) { errors.push("\u4F7F\u7528\u767E\u5EA6\u7FFB\u8BD1\u65F6\u5FC5\u987B\u914D\u7F6E translation.baidu.appid \u548C translation.baidu.key"); } } } if (((_d = config.logging) == null ? void 0 : _d.level) && !["minimal", "normal", "verbose"].includes(config.logging.level)) { errors.push("logging.level \u5FC5\u987B\u662F minimal\u3001normal \u6216 verbose \u4E4B\u4E00"); } if (errors.length > 0) { Logger.error("\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25:", "minimal"); errors.forEach((error) => Logger.error(` - ${error}`, "minimal")); throw new Error(`\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25: ${errors.join("; ")}`); } } function loadConfig(customConfigPath) { let userConfig = {}; if (customConfigPath) { if (fileExists(customConfigPath)) { Logger.verbose(`\u52A0\u8F7D\u914D\u7F6E\u6587\u4EF6: ${customConfigPath}`); try { userConfig = readJsonSync2(customConfigPath); Logger.verbose("\u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u6210\u529F"); } catch (error) { throw new Error(`\u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${error}`); } } else { Logger.warn(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${customConfigPath}\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E`); } } const finalConfig = (0, import_lodash.merge)({}, defaultConfig, userConfig); validateConfig(finalConfig); Logger.verbose("\u914D\u7F6E\u9A8C\u8BC1\u901A\u8FC7"); return finalConfig; } // main/utils/logger.ts var Logger = class { static temporaryLevel = null; // 临时日志级别 static getLogLevel() { var _a; try { if (this.temporaryLevel) { return this.temporaryLevel; } const config = ConfigManager.get(); return ((_a = config.logging) == null ? void 0 : _a.level) ?? "normal"; } catch { return "normal"; } } /** * 临时设置日志级别(用于进度显示期间) */ static setTemporaryLevel(level) { this.temporaryLevel = level; } static shouldLog(level) { const currentLevel = this.getLogLevel(); if (currentLevel === "minimal") { return level === "minimal"; } else if (currentLevel === "normal") { return level !== "verbose"; } else if (currentLevel === "verbose") { return true; } return false; } static success(message, level = "normal") { if (this.shouldLog(level)) { console.log(`\u2705 ${message}`); } } static info(message, level = "normal") { if (this.shouldLog(level)) { console.log(`\u2139\uFE0F ${message}`); } } static warn(message, level = "normal") { if (this.shouldLog(level)) { console.warn(`\u26A0\uFE0F ${message}`); } } static error(message, level = "normal") { if (this.shouldLog(level)) { console.error(`\u274C ${message}`); } } static verbose(message) { this.info(message, "verbose"); } }; // main/cli/commands/init.ts async function initHandler(options) { var _a; const outputDir = options.outputDir || ".test-output/locales"; const tempDir = options.tempDir || ".test-output/temp"; const configPath = options.configPath || "./i18n.config.json"; const locale = options.locale || defaultConfig.locale; const functionName = options.functionName || ((_a = defaultConfig.replacement) == null ? void 0 : _a.functionName); const config = { ...defaultConfig, outputDir, tempDir, locale, replacement: { ...defaultConfig.replacement, functionName } }; try { await writeJson(configPath, config, true); Logger.success(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u751F\u6210: ${configPath}`, "minimal"); Logger.info("\u4F60\u53EF\u4EE5\u6839\u636E\u9879\u76EE\u9700\u6C42\u4FEE\u6539\u914D\u7F6E\u6587\u4EF6\u3002", "normal"); } catch (error) { Logger.error(`\u914D\u7F6E\u6587\u4EF6\u751F\u6210\u5931\u8D25: ${error}`, "minimal"); process.exit(1); } } var initCommand = initHandler; // main/cli/wrapper.ts function withCLI(handler, wrapperOptions = {}) { return async (options) => { var _a; try { const processedOptions = ((_a = wrapperOptions.preProcess) == null ? void 0 : _a.call(wrapperOptions, options)) ?? options; let config = null; if (wrapperOptions.configRequired !== false) { const configPath = processedOptions.config ?? "./i18n.config.json"; config = loadConfig(configPath); ConfigManager.init(config); const validation = ConfigValidator.validateConfigUsage(); if (!validation.isValid) { Logger.error("\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25\uFF0C\u65E0\u6CD5\u7EE7\u7EED\u6267\u884C", "minimal"); process.exit(1); } ConfigValidator.checkConfigConsistency(); } if (config && wrapperOptions.validator) { wrapperOptions.validator(config); } await handler(processedOptions, config); } catch (error) { Logger.error(`\u547D\u4EE4\u6267\u884C\u5931\u8D25: ${error}`, "minimal"); process.exit(1); } }; } // main/core/ast/index.ts var import_parser2 = require("@babel/parser"); var t2 = __toESM(require("@babel/types"), 1); // main/core/key-generator/index.ts var import_pinyin_pro = require("pinyin-pro"); var path2 = __toESM(require("path"), 1); var import_string_hash = __toESM(require("string-hash"), 1); var keyValueCache = {}; var valueKeyCache = {}; var outputFilePath = ""; async function initI18nCache() { var _a; const config = ConfigManager.get(); const outputDir = config.outputDir ?? "locales"; await ensureDir2(outputDir); const localeFileName = ((_a = config.output) == null ? void 0 : _a.localeFileName) ?? "{locale}.json"; const fileName = localeFileName.replace("{locale}", config.locale ?? "zh-CN"); outputFilePath = path2.join(outputDir, fileName); keyValueCache = await readJson(outputFilePath, {}); valueKeyCache = {}; for (const [key, value] of Object.entries(keyValueCache)) { valueKeyCache[value] = key; } const existingKeyCount = Object.keys(keyValueCache).length; if (existingKeyCount > 0) { Logger.verbose(`\u52A0\u8F7D\u4E86 ${existingKeyCount} \u4E2A\u5DF2\u5B58\u5728\u7684\u7FFB\u8BD1\u952E`); } await writeJson(outputFilePath, keyValueCache); } function toShortHash(text) { var _a; const config = ConfigManager.get(); const hashLength = ((_a = config.keyGeneration) == null ? void 0 : _a.hashLength) ?? 6; return (0, import_string_hash.default)(text).toString(36).slice(0, hashLength); } function generateUniqueKey(baseKey, text) { var _a, _b; const config = ConfigManager.get(); const separator = ((_a = config.keyGeneration) == null ? void 0 : _a.separator) ?? "_"; const maxRetry = ((_b = config.keyGeneration) == null ? void 0 : _b.maxRetryCount) ?? 5; if (!keyValueCache[baseKey]) { return baseKey; } let finalKey = baseKey + separator + toShortHash(text); let tryCount = 0; while (keyValueCache[finalKey] && tryCount < maxRetry) { finalKey = baseKey + separator + toShortHash(text + Math.random().toString()); tryCount++; } if (tryCount >= maxRetry) { Logger.warn(`\u751F\u6210\u552F\u4E00key\u5931\u8D25\uFF0C\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570: ${baseKey}`); finalKey = baseKey + separator + Date.now().toString(36); } return finalKey; } function createI18nKey(text) { var _a, _b, _c, _d, _e, _f; const config = ConfigManager.get(); const reuseExistingKey = ((_a = config.keyGeneration) == null ? void 0 : _a.reuseExistingKey) ?? true; if (reuseExistingKey && valueKeyCache[text]) { Logger.verbose(`\u91CD\u590D\u4F7F\u7528\u5DF2\u5B58\u5728\u6587\u6848\u7684key: "${valueKeyCache[text]}" for "${text}"`); return valueKeyCache[text]; } const maxLength = ((_b = config.keyGeneration) == null ? void 0 : _b.maxChineseLength) ?? 10; const han = (((_c = text.match(/[\u4e00-\u9fa5]+/g)) == null ? void 0 : _c.join("")) ?? "").slice(0, maxLength); if (!han) { return ""; } const pinyinOptions = ((_d = config.keyGeneration) == null ? void 0 : _d.pinyinOptions) ?? { toneType: "none", type: "array" }; const separator = ((_e = config.keyGeneration) == null ? void 0 : _e.separator) ?? "_"; const keyPrefix = ((_f = config.keyGeneration) == null ? void 0 : _f.keyPrefix) ?? ""; const baseKey = ((0, import_pinyin_pro.pinyin)(han, { toneType: pinyinOptions.toneType ?? "none", type: "array" }) ?? []).join(separator); if (!baseKey) { return ""; } const keyWithPrefix = keyPrefix ? `${keyPrefix}${separator}${baseKey}` : baseKey; const finalKey = generateUniqueKey(keyWithPrefix, text); keyValueCache[finalKey] = text; valueKeyCache[text] = finalKey; return finalKey; } async function flushI18nCache() { var _a; if (outputFilePath) { const config = ConfigManager.get(); const prettyJson = ((_a = config.output) == null ? void 0 : _a.prettyJson) ?? true; await writeJson(outputFilePath, keyValueCache, prettyJson); const keyCount = Object.keys(keyValueCache).length; Logger.info(`\u5DF2\u4FDD\u5B58 ${keyCount} \u4E2A\u7FFB\u8BD1\u952E\u5230 ${outputFilePath}`, "normal"); } } // main/utils/import-manager.ts var import_parser = require("@babel/parser"); var t = __toESM(require("@babel/types"), 1); // main/utils/pattern.ts var import_micromatch = __toESM(require("micromatch"), 1); function matchPattern(pattern, filePath) { try { return import_micromatch.default.isMatch(filePath, pattern); } catch (error) { Logger.warn(`\u6A21\u5F0F\u5339\u914D\u51FA\u9519 - pattern: ${pattern}, filePath: ${filePath}, error: ${error}`); return false; } } function findMatchingImport(filePath, imports = {}) { var _a; Logger.verbose(`findMatchingImport: ${filePath}`); const patterns = Object.keys(imports).sort((a, b) => { const scoreA = getPatternSpecificity(a); const scoreB = getPatternSpecificity(b); return scoreB - scoreA; }); for (const pattern of patterns) { if (matchPattern(pattern, filePath)) { Logger.verbose(`\u5339\u914D\u5230\u6A21\u5F0F: ${pattern} -> ${filePath}`); return ((_a = imports[pattern]) == null ? void 0 : _a.importStatement) || null; } } Logger.verbose(`\u672A\u627E\u5230\u5339\u914D\u7684import\u914D\u7F6E: ${filePath}`); return null; } function getPatternSpecificity(pattern) { var _a, _b, _c; let score = 0; if (!pattern.includes("*")) { score += 100; } if (pattern.includes(".{") || pattern.match(/\.\w+$/)) { score += 50; } score += (((_a = pattern.match(/\//g)) == null ? void 0 : _a.length) || 0) * 10; score -= (((_b = pattern.match(/\*\*/g)) == null ? void 0 : _b.length) || 0) * 20; score -= (((_c = pattern.match(/(?<!\*)\*(?!\*)/g)) == null ? void 0 : _c.length) || 0) * 5; return score; } // main/utils/import-manager.ts var generate = require("@babel/generator").default; function hasExistingImport(ast, importStatement) { var _a; try { const importAst = (0, import_parser.parse)(importStatement, { sourceType: "module", plugins: ["jsx", "typescript"] }); if (!((_a = importAst.program) == null ? void 0 : _a.body) || importAst.program.body.length === 0) { return false; } const newStatements = importAst.program.body; const existingStatements = ast.program.body; for (const newStmt of newStatements) { let found = false; for (const existingStmt of existingStatements) { const newCode = generate(newStmt, { minified: true }).code; const existingCode = generate(existingStmt, { minified: true }).code; if (newCode === existingCode) { found = true; break; } } if (!found) { return false; } } return true; } catch (error) { Logger.verbose(`\u68C0\u67E5import\u91CD\u590D\u65F6\u51FA\u9519: ${error}`); return false; } } function addImportToAST(ast, importStatement, insertPosition = "afterImports") { var _a; try { if (hasExistingImport(ast, importStatement)) { Logger.verbose("Import\u8BED\u53E5\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u63D2\u5165"); return; } const importAst = (0, import_parser.parse)(importStatement, { sourceType: "module", plugins: ["jsx", "typescript"] }); if (((_a = importAst.program) == null ? void 0 : _a.body) && importAst.program.body.length > 0) { let insertIndex = 0; if (insertPosition === "afterImports") { for (let i = 0; i < ast.program.body.length; i++) { if (t.isImportDeclaration(ast.program.body[i])) { insertIndex = i + 1; } else { break; } } } else { insertIndex = 0; } ast.program.body.splice(insertIndex, 0, ...importAst.program.body); Logger.verbose(`Import\u8BED\u53E5\u5DF2\u63D2\u5165\u5230\u4F4D\u7F6E: ${insertPosition}`); } } catch (error) { Logger.warn(`\u65E0\u6CD5\u89E3\u6790import\u8BED\u53E5: ${importStatement}`); Logger.verbose(`\u9519\u8BEF\u8BE6\u60C5: ${error}`); } } function addEmptyLineAfterImport(code, importStatement) { const cleanImportStatement = importStatement.replace(/;\s*$/g, ";").trim(); const importIndex = code.indexOf(cleanImportStatement); if (importIndex === -1) { Logger.verbose("\u672A\u5728\u751F\u6210\u7684\u4EE3\u7801\u4E2D\u627E\u5230import\u8BED\u53E5\uFF0C\u8DF3\u8FC7\u7A7A\u884C\u6DFB\u52A0"); return code; } const importEndIndex = importIndex + cleanImportStatement.length; const charAfterImport = code.charAt(importEndIndex); if (charAfterImport && charAfterImport !== "\n" && charAfterImport !== "\r") { const restCode = code.substring(importEndIndex); const trimmedRestCode = restCode.replace(/^[ \t]*/, ""); return code.substring(0, importEndIndex) + "\n\n" + trimmedRestCode; } let lineEndIndex = importEndIndex; while (lineEndIndex < code.length && code.charAt(lineEndIndex) !== "\n") { lineEndIndex++; } if (lineEndIndex >= code.length) { return code + "\n"; } const nextLineStart = lineEndIndex + 1; if (nextLineStart >= code.length) { return code + "\n"; } const nextLineEnd = code.indexOf("\n", nextLineStart); const nextLine = nextLineEnd === -1 ? code.substring(nextLineStart) : code.substring(nextLineStart, nextLineEnd); if (nextLine.trim() !== "") { return code.substring(0, lineEndIndex + 1) + "\n" + code.substring(lineEndIndex + 1); } return code; } var ImportManager = class { hasImportInserted = false; insertedImportStatement = ""; /** * 重置状态(处理新文件时调用) */ reset() { this.hasImportInserted = false; this.insertedImportStatement = ""; } /** * 按需插入import语句 */ insertImportIfNeeded(ast, file, autoImportEnabled, hasReplacement, imports, insertPosition) { if (!this.hasImportInserted && autoImportEnabled && hasReplacement) { const importStatement = findMatchingImport(file, imports); if (importStatement) { Logger.verbose(`\u4E3A\u6587\u4EF6 ${file} \u6DFB\u52A0import\u8BED\u53E5: ${importStatement.trim()}`); addImportToAST(ast, importStatement, insertPosition); this.hasImportInserted = true; this.insertedImportStatement = importStatement; } else { Logger.warn(`\u6587\u4EF6 ${file} \u9700\u8981import\u8BED\u53E5\u4F46\u672A\u627E\u5230\u5339\u914D\u7684import\u914D\u7F6E`, "normal"); } } } /** * 为输出代码添加空行(如果插入了import语句) */ addEmptyLineToOutput(output) { if (this.hasImportInserted && this.insertedImportStatement) { const result = addEmptyLineAfterImport(output, this.insertedImportStatement); if (result !== output) { Logger.verbose(`\u5DF2\u4E3Aimport\u8BED\u53E5\u540E\u6DFB\u52A0\u7A7A\u884C`); } return result; } return output; } /** * 检查是否已插入import语句 */ get isImportInserted() { return this.hasImportInserted; } /** * 获取插入的import语句内容 */ get insertedStatement() { return this.insertedImportStatement; } }; // main/core/ast/index.ts var traverse = require("@babel/traverse").default; var generate2 = require("@babel/generator").default; var CHINESE_RE = /[\u4e00-\u9fa5]/; function isInComment(path8, codeLines) { const loc = path8.node.loc; if (!loc || !codeLines) return false; const lineIndex = loc.start.line - 1; if (lineIndex >= 0 && lineIndex < codeLines.length) { const line = codeLines[lineIndex]; const beforeText = line.substring(0, loc.start.column); const afterText = line.substring(loc.end.column); if (beforeText.includes("//")) { return true; } if (beforeText.includes("/*") && !beforeText.includes("*/")) { return true; } for (let i = lineIndex - 1; i >= 0; i--) { const prevLine = codeLines[i]; if (prevLine.includes("*/")) { break; } if (prevLine.includes("/*")) { return true; } } if (beforeText.includes("/**") || beforeText.includes("*")) { return true; } } return false; } function analyzeContext(path8, codeLines) { var _a; if (codeLines && isInComment(path8, codeLines)) { return { shouldProcess: false, contextType: "comment", reason: "\u6CE8\u91CA\u4E2D\u7684\u6587\u672C" }; } let current = path8; while (current) { const parent = current.parent; const parentPath = current.parentPath; if (!parent || !parentPath) break; if (t2.isObjectProperty(parent) && parent.key === current.node) { return { shouldProcess: false, contextType: "object-key", reason: "\u5BF9\u8C61\u5C5E\u6027\u952E\u540D" }; } if (t2.isObjectMethod(parent) && parent.key === current.node) { return { shouldProcess: false, contextType: "object-key", reason: "\u5BF9\u8C61\u65B9\u6CD5\u540D" }; } if (t2.isImportDeclaration(parent) || t2.isImportSpecifier(parent) || t2.isExportSpecifier(parent) || // 只有当字符串直接在export声明中时才忽略(如 export { "key" as alias }) // 而不是在export function/class内部 t2.isExportDeclaration(parent) && (t2.isExportAllDeclaration(parent) || t2.isExportDefaultDeclaration(parent) && current.node === parent.declaration || t2.isExportNamedDeclaration(parent) && ((_a = parent.specifiers) == null ? void 0 : _a.some((spec) => spec === current.node)))) { return { shouldProcess: false, contextType: "import-export", reason: "import/export\u8BED\u53E5" }; } if (t2.isTSTypeAnnotation(parent) || // : string t2.isTSLiteralType(parent) || // type T = "literal" t2.isTSIntersectionType(parent) || // A & B t2.isTSTypeReference(parent) || // SomeType<T> t2.isTSTypeLiteral(parent) || // { key: "value" } t2.isTSInterfaceDeclaration(parent) || // interface I {} t2.isTSTypeAliasDeclaration(parent) || // type T = ... t2.isTSEnumDeclaration(parent) || // enum E {} t2.isTSPropertySignature(parent) || // { prop: "type" } t2.isTSMethodSignature(parent) || // { method(): "return" } t2.isTSCallSignatureDeclaration(parent) || // { (): "return" } t2.isTSConstructSignatureDeclaration(parent) || // { new(): "type" } t2.isTSIndexSignature(parent) || // { [key: string]: "value" } t2.isTSMappedType(parent) || // { [K in keyof T]: "value" } t2.isTSConditionalType(parent) || // T extends "literal" ? A : B t2.isTSInferType(parent) || // infer R t2.isTSTypeParameter(parent) || // <T extends "literal"> t2.isTSTypeParameterDeclaration(parent)) { if (t2.isTSEnumMember(parent) && parent.initializer === current.node) { return { shouldProcess: true, contextType: "enum-value", reason: "\u679A\u4E3E\u503C" }; } return { shouldProcess: false, contextType: "ts-definition", reason: "TypeScript\u7C7B\u578B\u5B9A\u4E49" }; } if (t2.isTSUnionType(parent)) { if (parent.types && parent.types.includes(current.node)) { return { shouldProcess: false, contextType: "type-annotation", reason: "\u8054\u5408\u7C7B\u578B\u6210\u5458" }; } if (t2.isTSLiteralType(current.parent) && parent.types.includes(current.parent)) { return { shouldProcess: false, contextType: "type-annotation", reason: "\u8054\u5408\u7C7B\u578B\u5B57\u9762\u91CF" }; } } if (t2.isTSPropertySignature(parent)) { if (parent.typeAnnotation && parent.typeAnnotation.typeAnnotation === current.node) { return { shouldProcess: false, contextType: "type-annotation", reason: "\u63A5\u53E3\u5C5E\u6027\u7C7B\u578B\u6CE8\u89E3" }; } if (t2.isTSTypeAnnotation(current.parent) && parent.typeAnnotation === current.parent) { return { shouldProcess: false, contextType: "type-annotation", reason: "\u63A5\u53E3\u5C5E\u6027\u7C7B\u578B\u6CE8\u89E3" }; } } if (t2.isTSTypeAliasDeclaration(parent)) { if (parent.typeAnnotation === current.node) { return { shouldProcess: false, contextType: "ts-definition", reason: "\u7C7B\u578B\u522B\u540D\u5B9A\u4E49" }; } } if (t2.isTSTypeParameterInstantiation(parent)) { return { shouldProcess: false, contextType: "type-annotation", reason: "\u6CDB\u578B\u53C2\u6570" }; } if (t2.isClassProperty(parent) && parent.key === current.node) { return { shouldProcess: false, contextType: "object-key", reason: "\u7C7B\u5C5E\u6027\u540D" }; } if (t2.isMemberExpression(parent) && parent.property === current.node && !parent.computed) { return { shouldProcess: false, contextType: "object-key", reason: "\u6210\u5458\u8868\u8FBE\u5F0F\u5C5E\u6027\u540D" }; } if (t2.isFunctionDeclaration(parent) || t2.isFunctionExpression(parent) || t2.isArrowFunctionExpression(parent)) { if (parent.params) { for (const param of parent.params) { if (t2.isIdentifier(param) && param.typeAnnotation) { const typeNode = param.typeAnnotation.typeAnnotation; if (isNodeInTypeAnnotation(current.node, typeNode)) { return { shouldProcess: false, contextType: "type-annotation", reason: "\u51FD\u6570\u53C2\u6570\u7C7B\u578B\u6CE8\u89E3" }; } } } } } current = parentPath; } return { shouldProcess: true, contextType: "code", reason: "\u4EE3\u7801\u4E2D\u7684\u5B57\u7B26\u4E32" }; } function isInTypePosition(path8) { return !analyzeContext(path8).shouldProcess; } function isNodeInTypeAnnotation(targetNode, typeAnnotation) { if (!typeAnnotation) return false; if (typeAnnotation === targetNode) return true; if (t2.isTSUnionType(typeAnnotation)) { return typeAnnotation.types.some((type) => isNodeInTypeAnnotation(targetNode, type)); } if (t2.isTSLiteralType(typeAnnotation)) { return typeAnnotation.literal === targetNode; } return false; } function containsChinese(str) { return CHINESE_RE.test(str); } function createI18nCallExpression(functionName, key, quoteType) { const keyNode = t2.stringLiteral(key); if (quoteType === "double" && keyNode.extra) { keyNode.extra = { ...keyNode.extra, raw: `"${key}"`, rawValue: key }; } return t2.callExpression(t2.identifier(functionName), [keyNode]); } function isI18nFunctionCall(path8, functionName) { const node = path8.node; if (!t2.isCallExpression(node)) { return false; } if (!t2.isIdentifier(node.callee) || node.callee.name !== functionName) { return false; } if (node.arguments.length !== 1) { return false; } if (!t2.isStringLiteral(node.arguments[0])) { return false; } return true; } function handleStringLiteral(path8, functionName, quoteType) { if (isInTypePosition(path8)) { return false; } const parentPath = path8.parentPath; if (parentPath && isI18nFunctionCall(parentPath, functionName)) { Logger.verbose(`\u8DF3\u8FC7\u5DF2\u7ECF\u56FD\u9645\u5316\u7684\u51FD\u6570\u8C03\u7528: ${functionName}(${path8.node.value})`); return false; } const stringValue = path8.node.value; if (stringValue && typeof stringValue === "string" && containsChinese(stringValue)) { const key = createI18nKey(stringValue); const callExpression2 = createI18nCallExpression(functionName, key, quoteType); path8.replaceWith(callExpression2); return true; } return false; } async function checkUnwrappedChinese() { var _a; const config = ConfigManager.get(); const functionName = ((_a = config.replacement) == null ? void 0 : _a.functionName) ?? "$t"; Logger.info("\u5F00\u59CB\u68C0\u67E5\u672A\u56FD\u9645\u5316\u7684\u4E2D\u6587\u5B57\u7B26\u4E32...", "normal"); Logger.verbose(`\u914D\u7F6E\u4FE1\u606F: - include: ${JSON.stringify(config.include)} - exclude: ${JSON.stringify(config.exclude)} - tempDir: ${config.tempDir || "\u65E0"} - functionName: ${functionName}`); Logger.info("\u641C\u7D22\u76EE\u6807\u6587\u4EF6...", "verbose"); let files; let sourceDescription; if (config.tempDir) { try { const tempFiles = await findTargetFiles([`${config.tempDir}/**/*.{js,jsx,ts,tsx}`], config.exclude); if (tempFiles.length > 0) { files = tempFiles; sourceDescription = `\u4E34\u65F6\u76EE\u5F55 (${config.tempDir})`; Logger.info(`\u4F7F\u7528\u4E34\u65F6\u76EE\u5F55\u6587\u4EF6\u8FDB\u884C\u68C0\u67E5: ${config.tempDir}`, "verbose"); } else { files = await findTargetFiles(config.include, config.exclude); sourceDescription = "\u539F\u59CB\u6587\u4EF6"; Logger.info("\u4E34\u65F6\u76EE\u5F55\u65E0\u6587\u4EF6\uFF0C\u4F7F\u7528\u539F\u59CB\u6587\u4EF6\u8FDB\u884C\u68C0\u67E5", "verbose"); } } catch (error) { files = await findTargetFiles(config.include, config.exclude); sourceDescription = "\u539F\u59CB\u6587\u4EF6"; Logger.verbose(`\u4E34\u65F6\u76EE\u5F55\u8BBF\u95EE\u5931\u8D25: ${error}, \u4F7F\u7528\u539F\u59CB\u6587\u4EF6\u8FDB\u884C\u68C0\u67E5`); } } else { files = await findTargetFiles(config.include, config.exclude); sourceDescription = "\u539F\u59CB\u6587\u4EF6"; } if (files.length === 0) { Logger.warn(`\u5728${sourceDescription}\u4E2D\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6587\u4EF6`, "normal"); return []; } Logger.info(`\u627E\u5230 ${files.length} \u4E2A\u6587\u4EF6\u9700\u8981\u68C0\u67E5 (\u6765\u6E90: ${sourceDescription})`, "normal"); const results = []; let processedCount = 0; let totalIssues = 0; for (const file of files) { processedCount++; Logger.info(`[${processedCount}/${files.length}] \u68C0\u67E5\u6587\u4EF6: ${file}`, "verbose"); const code = await readFile2(file, "utf-8"); const fileResult = { file, issues: [] }; let ast; try { ast = (0, import_parser2.parse)(code, { sourceType: "unambiguous", plugins: ["jsx", "typescript", "classProperties", "decorators-legacy", "dynamicImport"] }); } catch (error) { Logger.warn(`\u89E3\u6790\u6587\u4EF6\u5931\u8D25: ${file}`, "minimal"); Logger.verbose(`\u9519\u8BEF\u8BE6\u60C5: ${error}`); continue; } const codeLines = code.split("\n"); traverse(ast, { // 检查字符串字面量 StringLiteral(path8) { const contextAnalysis = analyzeContext(path8, codeLines); const parentPath = path8.parentPath; if (parentPath && isI18nFunctionCall(parentPath, functionName)) return; const stringValue = path8.node.value; if (stringValue && typeof stringValue === "string" && containsChinese(stringValue)) { const loc = path8.node.loc; if (loc) { fileResult.issues.push({ line: loc.start.line, column: loc.start.column, text: stringValue, type: "string", context: getContextInfo(path8, codeLines), shouldProcess: contextAnalysis.shouldProcess, contextType: contextAnalysis.contextType, reason: contextAnalysis.reason }); } } }, // 检查模板字符串 TemplateLiteral(path8) { const contextAnalysis = analyzeContext(path8, codeLines); const parentPath = path8.parentPath; if (parentPath && isI18nFunctionCall(parentPath, functionName)) return; path8.node.quasis.forEach((quasi) => { var _a2; const raw = (_a2 = quasi.value) == null ? void 0 : _a2.raw; if (raw && typeof raw === "string" && containsChinese(raw)) { const loc = path8.node.loc; if (loc) { fileResult.issues.push({ line: loc.start.line, column: loc.start.column, text: raw, type: "template", context: getContextInfo(path8, codeLines), shouldProcess: contextAnalysis.shouldProcess, contextType: contextAnalysis.contextType, reason: contextAnalysis.reason }); } } }); path8.node.expressions.forEach((expr) => { if (t2.isStringLiteral(expr) && containsChinese(expr.value)) { const loc = expr.loc; if (loc) { fileResult.issues.push({ line: loc.start.line, column: loc.start.column, text: expr.value, type: "template", context: getContextInfo(path8, codeLines), shouldProcess: contextAnalysis.shouldProcess, contextType: contextAnalysis.contextType, reason: contextAnalysis.reason }); } } }); }, // 检查JSX文本 JSXText(path8) { const textValue = path8.node.value; if (textValue && typeof textValue === "string" && containsChinese(textValue)) { const contextAnalysis = analyzeContext(path8, codeLines); const parentPath = path8.parentPath; if ((parentPath == null ? void 0 : parentPath.isJSXExpressionContainer()) && parentPath.node.expression && isI18nFunctionCall({ node: parentPath.node.expression }, functionName)) { return; } const trimmedValue = textValue.trim(); if (trimmedValue) { const loc = path8.node.loc; if (loc) { fileResult.issues.push({ line: loc.start.line, column: loc.start.column, text: trimmedValue, type: "jsx-text", context: getContextInfo(path8, codeLines), shouldProcess: contextAnalysis.shouldProcess, contextType: contextAnalysis.contextType, reason: contextAnalysis.reason }); } } } }, // 检查JSX属性 JSXAttribute(path8) { if (t2.isStringLiteral(path8.node.value) && path8.node.value.value && typeof path8.node.value.value === "string" && containsChinese(path8.node.value.value)) { const contextAnalysis = analyzeContext(path8, codeLines); if (t2.isJSXExpressionContainer(path8.node.value) && path8.node.value.expression && isI18nFunctionCall({ node: path8.node.value.expression }, functionName)) { return; } const attributeValue = path8.node.value.value; const loc = path8.node.loc; if (loc) { fileResult.issues.push({ line: loc.start.line, column: loc.start.column, text: attributeValue, type: "jsx-attribute", context: getContextInfo(path8, codeLines), shouldProcess: contextAnalysis.shouldProcess, contextType: contextAnalysis.contextType, reason: contextAnalysis.reason }); } } } }); if (fileResult.issues.length > 0) { results.push(fileResult); totalIssues += fileResult.issues.length; Logger.info(`\u6587\u4EF6 ${file} \u53D1\u73B0 ${fileResult.issues.length} \u4E2A\u672A\u56FD\u9645\u5316\u7684\u4E2D\u6587\u5B57\u7B26\u4E32`, "normal"); } else { Logger.verbose(`\u6587\u4EF6 ${file} \u65E0\u672A\u56FD\u9645\u5316\u7684\u4E2D\u6587\u5B57\u7B26\u4E32`); } } Logger.success(`\u68C0\u67E5\u5B8C\u6210\uFF01`, "minimal"); Logger.info(`\u7EDF\u8BA1\u4FE1\u606F:`, "normal"); Logger.info(` - \u68C0\u67E5\u6587\u4EF6\u603B\u6570: ${processedCount}`, "normal"); Logger.info(` - \u6709\u95EE\u9898\u7684\u6587\u4EF6\u6570: ${results.length}`, "normal"); Logger.info(` - \u672A\u56FD\u9645\u5316\u5B57\u7B26\u4E32\u603B\u6570: ${totalIssues}`, "normal"); return results; } function getContextInfo(path8, codeLines) { var _a; const loc = path8.node.loc; if (!loc || !codeLines) return ""; const lineIndex = loc.start.line - 1; if (lineIndex >= 0 && lineIndex < codeLines.length) { return ((_a = codeLines[lineIndex]) == null ? void 0 : _a.trim()) ?? ""; } return ""; } async function scanAndReplaceAll() { var _a, _b, _c, _d, _e, _f, _g, _h, _i; const config = ConfigManager.get(); Logger.info("\u5F00\u59CB\u626B\u63CF\u548C\u66FF\u6362\u4E2D\u6587\u5B57\u7B26\u4E32...", "normal"); Logger.verbose(`\u914D\u7F6E\u4FE1\u606F: - include: ${JSON.stringify(config.include)} - exclude: ${JSON.stringify(config.exclude)} - outputDir: ${config.outputDir} - locale: ${config.locale} - tempDir: ${config.tempDir || "\u65E0"}`); Logger.info("\u521D\u59CB\u5316i18n\u7F13\u5B58...", "verbose"); await initI18nCache(); Logger.info("\u641C\u7D22\u76EE\u6807\u6587\u4EF6...", "verbose"); const files = await findTargetFiles(config.include, config.exclude); if (files.length === 0) { Logger.warn("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6587\u4EF6", "normal"); return; } Logger.info(`\u627E\u5230 ${files.length} \u4E2A\u6587\u4EF6\u9700\u8981\u5904\u7406`, "normal"); Logger.verbose(`\u6587\u4EF6\u5217\u8868: ${files.join(", ")}`); const functionName = ((_a = config.replacement) == null ? void 0 : _a.functionName) ?? "$t"; const quoteType = ((_b = config.replacement) == null ? void 0 : _b.quoteType) ?? "single"; const autoImportEnabled = ((_d = (_c = config.replacement) == null ? void 0 : _c.autoImport) == null ? void 0 : _d.enabled) ?? false; const insertPosition = ((_f = (_e = config.replacement) == null ? void 0 : _e.autoImport) == null ? void 0 : _f.insertPosition) ?? "afterImports"; const imports = ((_h = (_g = config.replacement) == null ? void 0 : _g.autoImport) == null ? void 0 : _h.imports) ?? {}; const templateConfig = ((_i = config.replacement) == null ? void 0 : _i.templateString) ?? { enabled: true, preserveExpressions: true, splitStrategy: "smart" }; Logger.verbose(`\u66FF\u6362\u914D\u7F6E: - \u51FD\u6570\u540D: ${functionName} - \u5F15\u53F7\u7C7B\u578B: ${quoteType} - \u81EA\u52A8\u5BFC\u5165: ${autoImportEnabled ? "\u542F\u7528" : "\u7981\u7528"} - \u63D2\u5165\u4F4D\u7F6E: ${insertPosition} - \u6A21\u677F\u5B57\u7B26\u4E32\u5904\u7406: ${templateConfig.enabled ? "\u542F\u7528" : "\u7981\u7528"} - \u62C6\u5206\u7B56\u7565: ${templateConfig.splitStrategy}`); let processedCount = 0; let modifiedCount = 0; let totalReplacements = 0; const importManager = new ImportManager(); for (const file of files) { processedCount++; Logger.info(`[${processedCount}/${files.length}] \u5904\u7406\u6587\u4EF6: ${file}`, "verbose"); Logger.verbose(`\u6B63\u5728\u5904\u7406\u6587\u4EF6: ${file}`); const code = await readFile2(file, "utf-8"); let ast; try { ast = (0, import_parser2.parse)(code, { sourceType: "unambiguous", plugins: ["jsx", "typescript", "classProperties", "decorators-legacy", "dynamicImport"] }); } catch (error) { Logger.warn(`\u89E3\u6790\u6587\u4EF6\u5931\u8D25: ${file}`, "minimal"); Logger.verbose(`\u9519\u8BEF\u8BE6\u60C5: ${error}`); continue; } importManager.reset(); let hasReplacement = false; let fileReplacements = 0; traverse(ast, { // 处理普通字符串字面量 StringLiteral(path8) { if (handleStringLiteral(path8, functionName, quoteType)) { hasReplacement = true; fileReplacements++; const stringValue = path8.node.value; Logger.verbose( `\u66FF\u6362\u5B57\u7B26\u4E32: "${stringValue}" -> ${functionName}(${quoteType === "single" ? "'" : '"'}${stringValue}${quoteType === "single" ? "'" : '"'})` ); } }, // 处理模板字符串元素 TemplateElement(path8) { var _a2; if (!templateConfig.enabled) { return; } if (isInTypePosition(path8)) { return; } const parentPath = path8.parentPath; if ((parentPath == null ? void 0 : parentPath.parentPath) && isI18nFunctionCall(parentPath.parentPath, functionName)) { Logger.verbose(`\u8DF3\u8FC7\u5DF2\u7ECF\u56FD\u9645\u5316\u7684\u6A21\u677F\u5B57\u7B26\u4E32\u5143\u7D20`); return; } const raw = (_a2 = path8.node.value) == null ? void 0 : _a2.raw; if (raw && typeof raw === "string" && containsChinese(raw)) { const chineseMatches = raw.match(/[\u4e00-\u9fa5]+/g); if (chineseMatches && chineseMatches.length > 0) { if (templateConfig.splitStrategy === "conservative") { const result = raw.replace(/[\u4e00-\u9fa5]+/g, (match) => { const key = createI18nKey(match); const quoteChar = quoteType === "single" ? "'" : '"'; return "${" + functionName + "(" + quoteChar + key + quoteChar + ")}"; }); path8.node.value.raw = result; path8.node.value.cooked = result; hasReplacement = true; fileReplacements++; Logger.verbose(`\u4FDD\u5B88\u7B56\u7565\u66FF\u6362\u6A21\u677F\u5B57\u7B26\u4E32\u4E2D\u7684\u4E2D\u6587\u7247\u6BB5`); } else { const SEPARATOR = "\u{1F4D6}"; const replaced = raw.replace( /[\u4e00-\u9fa5]+/g, (match) => `${SEPARATOR}${match}${SEPARATOR}` ); const parts = replaced.split(new RegExp(`${SEPARATOR}([\\u4e00-\\u9fa5]+)${SEPARATOR}`, "g")).filter(Boolean); let result = ""; const quoteChar = quoteType === "single" ? "'" : '"'; for (const part of parts) { if (/^[\u4e00-\u9fa5]+$/.test(part)) { const key = createI18nKey(part); result += "${" + functionName + "(" + quoteChar + key + quoteChar + ")}"; } else { result += part; } } path8.node.value.raw = result; path8.node.value.cooked = result; hasReplacement = true; fileReplacements++; Logger.verbose(`${templateConfig.splitStrategy}\u7B56\u7565\u66FF\u6362\u6A21\u677F\u5B57\u7B26\u4E32\u4E2D\u7684\u4E2D\u6587\u7247\u6BB5`); } } } }, // 处理模板字符串 TemplateLiteral(path8) { const parentPath = path8.parentPath; if (parentPath && isI18nFunctionCall(parentPath, functionName)) { Logger.verbose(`\u8DF3\u8FC7\u5DF2\u7ECF\u56FD\u9645\u5316\u7684\u6A21\u677F\u5B57\u7B26\u4E32`); return; } if (isInTypePosition(path8)) { return; } if (!templateConfig.enabled) { return; } let hasModified = false