import { fs, argv, path, $, usePowerShell, os, } from "zx"; import { fileURLToPath } from "url"; import "dotenv/config"; import { z } from "zod"; import { generateObject, streamObject } from 'ai'; import { createOpenAI } from '@ai-sdk/openai'; import { createAnthropic } from '@ai-sdk/anthropic'; import { createGoogleGenerativeAI } from '@ai-sdk/google'; if (os.platform() === "win32") { usePowerShell(); } const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const rootDir = path.resolve(__dirname, "../"); const i18nPath = path.resolve(rootDir, "./packages/shared/src/i18n/i18n.json"); // 🎯 HyperChat 多语言专业术语词典 const TERMINOLOGY_DICT = { zh: { "Agent": "代理", "Agents": "代理", "MCP": "MCP", "MCP Server": "MCP 服务器", "MCP Client": "MCP 客户端", "Workspace": "工作区", "Workspaces": "工作区", "Chat": "聊天", "Model": "模型", "Provider": "提供商", "API Key": "API 密钥", "Task": "任务", "Tasks": "任务", "Schedule": "调度", "Scheduler": "调度器", "CLI": "命令行工具", "Command": "命令", "Template": "模板", "Gateway": "网关", "Token": "令牌", "Prompt": "提示词", "Temperature": "温度", "Tool": "工具", "Tools": "工具", "Resource": "资源", "System Prompt": "系统提示词", "Chat Log": "聊天记录", "History": "历史记录", "Session": "会话", "Configuration": "配置", "Settings": "设置" }, ja: { "Agent": "エージェント", "Agents": "エージェント", "MCP": "MCP", "MCP Server": "MCPサーバー", "MCP Client": "MCPクライアント", "Workspace": "ワークスペース", "Workspaces": "ワークスペース", "Chat": "チャット", "Model": "モデル", "Provider": "プロバイダー", "API Key": "APIキー", "Task": "タスク", "Tasks": "タスク", "Schedule": "スケジュール", "Scheduler": "スケジューラー", "CLI": "コマンドラインツール", "Command": "コマンド", "Template": "テンプレート", "Gateway": "ゲートウェイ", "Token": "トークン", "Prompt": "プロンプト", "Temperature": "温度", "Tool": "ツール", "Tools": "ツール", "Resource": "リソース", "System Prompt": "システムプロンプト", "Chat Log": "チャット履歴", "History": "履歴", "Session": "セッション", "Configuration": "設定", "Settings": "設定" }, ko: { "Agent": "에이전트", "Agents": "에이전트", "MCP": "MCP", "MCP Server": "MCP 서버", "MCP Client": "MCP 클라이언트", "Workspace": "워크스페이스", "Workspaces": "워크스페이스", "Chat": "채팅", "Model": "모델", "Provider": "프로바이더", "API Key": "API 키", "Task": "작업", "Tasks": "작업", "Schedule": "스케줄", "Scheduler": "스케줄러", "CLI": "명령줄 도구", "Command": "명령", "Template": "템플릿", "Gateway": "게이트웨이", "Token": "토큰", "Prompt": "프롬프트", "Temperature": "온도", "Tool": "도구", "Tools": "도구", "Resource": "리소스", "System Prompt": "시스템 프롬프트", "Chat Log": "채팅 기록", "History": "히스토리", "Session": "세션", "Configuration": "구성", "Settings": "설정" }, fr: { "Agent": "Agent", "Agents": "Agents", "MCP": "MCP", "MCP Server": "Serveur MCP", "MCP Client": "Client MCP", "Workspace": "Espace de travail", "Workspaces": "Espaces de travail", "Chat": "Chat", "Model": "Modèle", "Provider": "Fournisseur", "API Key": "Clé API", "Task": "Tâche", "Tasks": "Tâches", "Schedule": "Planification", "Scheduler": "Planificateur", "CLI": "Outil en ligne de commande", "Command": "Commande", "Template": "Modèle", "Gateway": "Passerelle", "Token": "Jeton", "Prompt": "Invite", "Temperature": "Température", "Tool": "Outil", "Tools": "Outils", "Resource": "Ressource", "System Prompt": "Invite système", "Chat Log": "Journal de chat", "History": "Historique", "Session": "Session", "Configuration": "Configuration", "Settings": "Paramètres" }, de: { "Agent": "Agent", "Agents": "Agenten", "MCP": "MCP", "MCP Server": "MCP-Server", "MCP Client": "MCP-Client", "Workspace": "Arbeitsbereich", "Workspaces": "Arbeitsbereiche", "Chat": "Chat", "Model": "Modell", "Provider": "Anbieter", "API Key": "API-Schlüssel", "Task": "Aufgabe", "Tasks": "Aufgaben", "Schedule": "Zeitplan", "Scheduler": "Planer", "CLI": "Kommandozeilen-Tool", "Command": "Befehl", "Template": "Vorlage", "Gateway": "Gateway", "Token": "Token", "Prompt": "Eingabeaufforderung", "Temperature": "Temperatur", "Tool": "Werkzeug", "Tools": "Werkzeuge", "Resource": "Ressource", "System Prompt": "System-Eingabeaufforderung", "Chat Log": "Chat-Protokoll", "History": "Verlauf", "Session": "Sitzung", "Configuration": "Konfiguration", "Settings": "Einstellungen" } }; // 🎯 语言配置映射 const LANGUAGE_CONFIG = { zh: { name: '中文(简体)', nativeName: '中文', region: '中国大陆' }, ja: { name: '日语', nativeName: '日本語', region: '日本' }, ko: { name: '韩语', nativeName: '한국어', region: '韩国' }, fr: { name: '法语', nativeName: 'Français', region: '法国' }, de: { name: '德语', nativeName: 'Deutsch', region: '德国' } }; // 🎯 软件背景信息和翻译上下文 const HYPERCHAT_CONTEXT = ` HyperChat 是一个多平台的 AI 聊天应用,具有以下特点: - 支持多种 AI 模型(OpenAI、Claude、Gemini、Qwen、Deepseek 等) - 完整的 MCP(模型上下文协议)支持 - 工作区概念:每个项目可以有独立的配置和代理 - AI 代理系统:可创建专门用途的 AI 助手 - 任务调度系统:支持定时任务执行 - 多平台支持:Web、桌面(Electron)、命令行(CLI) - 支持多语言界面(中文/英文) UI 组件包括:表单、按钮、菜单、设置面板、聊天界面、文件管理等。 用户群体:开发者、AI 研究人员、需要 AI 辅助工作的专业人士。 `; // 🎯 翻译结果 Schema - 批量翻译 const BatchTranslationSchema = z.object({ translations: z.array(z.object({ index: z.number(), original: z.string(), translation: z.string(), confidence: z.number().min(0).max(1).optional().describe("翻译置信度,0-1之间") })).describe("按原始顺序排列的翻译结果数组") }); // 🎯 翻译结果 Schema - 单个翻译 const SingleTranslationSchema = z.object({ translation: z.string().describe("翻译后的中文文本"), confidence: z.number().min(0).max(1).optional().describe("翻译置信度,0-1之间"), reasoning: z.string().optional().describe("翻译推理过程") }); // 🎯 AI 提供商配置 interface AIConfig { provider: 'openai' | 'anthropic' | 'google'; model: string; apiKey: string; baseURL?: string; } // 🎯 获取 AI 模型 function getAIModel(config: AIConfig) { // 如果使用 OpenRouter,统一使用 OpenAI 兼容接口 if (config.baseURL && config.baseURL.includes('openrouter.ai')) { const openai = createOpenAI({ apiKey: config.apiKey, baseURL: config.baseURL }); return openai(config.model); } switch (config.provider) { case 'openai': const openai = createOpenAI({ apiKey: config.apiKey, baseURL: config.baseURL }); return openai(config.model); case 'anthropic': const anthropic = createAnthropic({ apiKey: config.apiKey, baseURL: config.baseURL }); return anthropic(config.model); case 'google': const google = createGoogleGenerativeAI({ apiKey: config.apiKey, baseURL: config.baseURL }); return google(config.model); default: throw new Error(`不支持的 AI 提供商: ${config.provider}`); } } // 🎯 创建翻译提示词 - 批量 function createBatchTranslationPrompt(items: string[], targetLang: string): string { const langConfig = LANGUAGE_CONFIG[targetLang]; const terminologyDict = TERMINOLOGY_DICT[targetLang] || {}; const terminologyList = Object.entries(terminologyDict) .map(([en, translated]) => `- ${en} → ${translated}`) .join('\n'); const itemsList = items.map((text, index) => `${index + 1}. "${text}"`).join('\n'); return `作为专业的软件本地化翻译专家,请将以下 HyperChat 软件界面文本从英文翻译成${langConfig.name}。 【软件背景】${HYPERCHAT_CONTEXT} 【专业术语词典】 ${terminologyList} 【翻译原则】 1. 保持用户界面的简洁性和一致性 2. 使用${langConfig.region}用户习惯的表达方式 3. 技术术语保持准确性,优先使用词典中的对应翻译 4. 保持原文的格式、标点符号和特殊字符 5. 错误信息要清晰易懂,帮助用户快速定位问题 6. 按钮和菜单项要简洁有力 7. 如果原文是空字符串,返回空字符串 8. 确保翻译质量高且专业,符合软件界面标准 【待翻译文本列表】 ${itemsList} 请按照相同的顺序和索引返回结构化的翻译结果。每个翻译都应该准确、自然且符合${langConfig.nativeName}表达习惯。`; } // 🎯 创建翻译提示词 - 单个 function createSingleTranslationPrompt(text: string, targetLang: string): string { const langConfig = LANGUAGE_CONFIG[targetLang]; const terminologyDict = TERMINOLOGY_DICT[targetLang] || {}; const terminologyList = Object.entries(terminologyDict) .map(([en, translated]) => `- ${en} → ${translated}`) .join('\n'); return `作为专业的软件本地化翻译专家,请将以下 HyperChat 软件界面文本从英文翻译成${langConfig.name}。 【软件背景】${HYPERCHAT_CONTEXT} 【专业术语词典】 ${terminologyList} 【翻译原则】 1. 保持用户界面的简洁性和一致性 2. 使用中国大陆用户习惯的表达方式 3. 技术术语保持准确性,优先使用词典中的对应翻译 4. 保持原文的格式、标点符号和特殊字符 5. 错误信息要清晰易懂,帮助用户快速定位问题 6. 按钮和菜单项要简洁有力 7. 如果原文是空字符串,返回空字符串 【待翻译文本】"${text}" 请提供高质量的${langConfig.nativeName}翻译,确保准确性和专业性。`; } // 🎯 批量翻译函数 export async function translateBatch(items: string[], config: AIConfig, targetLang: string = 'zh'): Promise { if (items.length === 0) return []; try { const model = getAIModel(config); const prompt = createBatchTranslationPrompt(items, targetLang); console.log(`🤖 使用 ${config.provider}/${config.model} 进行批量翻译...`); const result = await generateObject({ model, schema: BatchTranslationSchema, prompt, temperature: 0.1, // 低温度确保一致性 } as any) as any; // 按索引排序并提取翻译结果 const sortedTranslations = result.object.translations .sort((a, b) => a.index - b.index) .map(t => t.translation); if (sortedTranslations.length !== items.length) { console.warn(`⚠️ 翻译数量不匹配,期望 ${items.length},实际 ${sortedTranslations.length}`); return await translateFallback(items, config, targetLang); } // 质量检查 let lowConfidenceCount = 0; result.object.translations.forEach(t => { if (t.confidence && t.confidence < 0.8) { lowConfidenceCount++; console.warn(`⚠️ 低置信度翻译: "${t.original}" -> "${t.translation}" (${t.confidence})`); } }); if (lowConfidenceCount > 0) { console.warn(`⚠️ 发现 ${lowConfidenceCount} 个低置信度翻译`); } return sortedTranslations; } catch (error) { console.warn('批量翻译失败,回退到单个翻译模式:', error); return await translateFallback(items, config, targetLang); } } // 🎯 单个翻译函数 export async function translateSingle(text: string, config: AIConfig, targetLang: string = 'zh'): Promise { try { const model = getAIModel(config); const prompt = createSingleTranslationPrompt(text, targetLang); const result = await generateObject({ model, schema: SingleTranslationSchema, prompt, temperature: 0.1, } as any) as any; // 质量检查 if (result.object.confidence && result.object.confidence < 0.8) { console.warn(`⚠️ 低置信度翻译: "${text}" -> "${result.object.translation}" (${result.object.confidence})`); if (result.object.reasoning) { console.warn(` 推理: ${result.object.reasoning}`); } } return result.object.translation; } catch (error) { console.error(`单个翻译失败: ${text}`, error); throw error; } } // 🎯 回退翻译方法 async function translateFallback(items: string[], config: AIConfig, targetLang: string = 'zh'): Promise { console.log('使用单个翻译回退模式...'); const results: string[] = []; for (const item of items) { try { const translation = await translateSingle(item, config, targetLang); results.push(translation); await new Promise(resolve => setTimeout(resolve, 200)); // 增加延迟避免限速 } catch (error) { console.error(`翻译失败: ${item}`, error); results.push(item); // 翻译失败时保持原文 } } return results; } // 🎯 翻译质量验证 function validateTranslation(original: string, translation: string, targetLang: string = 'zh'): { isValid: boolean; issues: string[] } { const issues: string[] = []; const terminologyDict = TERMINOLOGY_DICT[targetLang] || {}; // 检查是否为空 if (!translation.trim()) { if (original.trim()) { issues.push("翻译结果为空但原文不为空"); } } // 检查是否包含英文(可能翻译不完整) if (/[a-zA-Z]{3,}/.test(translation) && translation !== original) { // 排除专业术语 const hasValidTerminology = Object.values(terminologyDict).some(term => translation.includes(term as string) ); if (!hasValidTerminology) { issues.push("翻译中包含较长英文单词"); } } // 检查长度是否合理 if (translation.length > original.length * 4) { issues.push("翻译长度异常过长"); } // 检查专业术语使用 for (const [en, translated] of Object.entries(terminologyDict)) { if (original.includes(en) && !translation.includes(translated as string) && !translation.includes(en)) { issues.push(`专业术语 "${en}" 可能翻译不准确`); } } return { isValid: issues.length === 0, issues }; } // 🎯 处理单个语言的翻译 async function processLanguage(json: any, aiConfig: AIConfig, targetLang: string): Promise { const langConfig = LANGUAGE_CONFIG[targetLang]; let hasChanges = false; // 仅验证模式 if (argv.validate) { console.log(`🔍 验证 ${langConfig.name} 翻译质量...`); let issues = 0; for (const [key, value] of Object.entries(json)) { const entry = value as { [key: string]: string }; if (entry[targetLang]) { const validation = validateTranslation(key, entry[targetLang], targetLang); if (!validation.isValid) { console.warn(`⚠️ [${key}]: ${validation.issues.join(', ')}`); issues++; } } } console.log(issues === 0 ? `✅ ${langConfig.name} 所有翻译质量良好` : `⚠️ ${langConfig.name} 发现 ${issues} 个质量问题`); return false; } // 统计需要翻译的条目 const needTranslation = Object.keys(json).filter(key => { if (argv.force) { return true; } const entry = json[key] as { [key: string]: string | null }; const translation = entry[targetLang]; return translation == null || translation === ""; }); if (needTranslation.length === 0) { console.log(`✅ ${langConfig.name} 所有条目都已翻译,无需处理`); return false; } console.log(`📊 ${langConfig.name} 找到 ${needTranslation.length} 个需要翻译的条目`); // 如果是 dry-run 模式,只显示需要翻译的条目 if (argv['dry-run']) { console.log(`\n📋 ${langConfig.name}翻译预览:`); needTranslation.forEach((key, index) => { const entry = json[key] as { [key: string]: string | null }; const translation = entry[targetLang]; const status = translation ? '(重新翻译)' : '(新翻译)'; console.log(`${index + 1}. ${key} ${status}`); }); console.log(`\n📈 ${langConfig.name} 总计: ${needTranslation.length} 个条目`); return false; } // 🚀 批量翻译处理 const batchSize = parseInt(argv['batch-size']) || 8; const batches = []; for (let i = 0; i < needTranslation.length; i += batchSize) { batches.push(needTranslation.slice(i, i + batchSize)); } console.log(`⚡ ${langConfig.name} 使用批量翻译模式,${batches.length} 个批次,每批 ${batchSize} 个条目`); for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) { const batch = batches[batchIndex]; console.log(`\n🔄 ${langConfig.name} 处理批次 ${batchIndex + 1}/${batches.length} (${batch.length} 个条目)`); try { const translations = await translateBatch(batch, aiConfig, targetLang); // 应用翻译结果并验证质量 for (let i = 0; i < batch.length; i++) { const key = batch[i]; if (translations[i]) { const entry = json[key] as { [key: string]: string | null }; entry[targetLang] = translations[i]; hasChanges = true; // 翻译质量验证 const validation = validateTranslation(key, translations[i], targetLang); if (validation.isValid) { console.log(`✅ ${key}`); } else { console.warn(`⚠️ ${key} (质量警告: ${validation.issues.join(', ')})`); } } } // 批次间延迟,避免 API 限速 if (batchIndex < batches.length - 1) { console.log('⏳ 等待 1 秒避免 API 限速...'); await new Promise(resolve => setTimeout(resolve, 1000)); } } catch (error) { console.error(`❌ ${langConfig.name} 批次 ${batchIndex + 1} 翻译失败:`, error); console.log('继续处理下一批次...'); } } return hasChanges; } // 🎯 解析 AI 配置 function parseAIConfig(): { aiConfig: AIConfig; targetLang: string } { const baseURL = process.env.baseURL; // 如果使用 OpenRouter,默认使用 OpenAI 兼容模式 let provider: AIConfig['provider']; let model: string; if (baseURL && baseURL.includes('openrouter.ai')) { provider = 'openai'; model = argv.model || "google/gemini-2.5-flash"; } else { provider = (argv.provider || 'google') as AIConfig['provider']; model = argv.model || "google/gemini-2.5-flash"; } const apiKey = process.env.apiKey || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.GOOGLE_API_KEY; const targetLang = argv.lang || argv.language || 'all'; if (!apiKey) { console.error("❌ 错误: 缺少 API 密钥"); console.error("请设置环境变量: apiKey 或对应的提供商 API 密钥"); process.exit(1); } if (targetLang !== 'all' && !LANGUAGE_CONFIG[targetLang]) { console.error(`❌ 错误: 不支持的语言: ${targetLang}`); console.error(`支持的语言: ${Object.keys(LANGUAGE_CONFIG).join(', ')}, all`); process.exit(1); } return { aiConfig: { provider, model, apiKey, baseURL }, targetLang }; } // 🎯 主函数 async function main() { if (argv.help || argv.h) { console.log(` 🌐 HyperChat 智能翻译工具 (基于 AI SDK) 用法: npx tsx scripts/translate.mts [选项] 选项: --help, -h 显示帮助信息 --dry-run 仅显示需要翻译的条目,不执行翻译 --force 强制重新翻译所有条目(包括已有翻译的) --batch-size N 批量翻译大小(默认: 8) --validate 仅验证现有翻译质量 --provider NAME AI 提供商 (openai|anthropic|google, 默认: google) --model MODEL 指定模型 (默认: google/gemini-2.0-flash-exp) --lang, --language 目标语言 (zh|ja|ko|fr|de|all, 默认: all) 环境变量: apiKey API 密钥(通用) OPENAI_API_KEY OpenAI API 密钥 ANTHROPIC_API_KEY Anthropic API 密钥 GOOGLE_API_KEY Google API 密钥 baseURL API 基础 URL(可选) 特性: ✅ 基于独立 AI SDK,轻量高效 ✅ 支持多个 AI 提供商 (OpenAI/Anthropic/Google) ✅ JSON Schema 确保翻译结构化和一致性 ✅ 针对 HyperChat 软件的专业术语词典 ✅ 智能批量翻译减少 API 调用 ✅ 置信度评估和质量验证 ✅ 智能回退机制 示例: npx tsx scripts/translate.mts # 翻译所有语言 npx tsx scripts/translate.mts --lang zh # 仅翻译中文 npx tsx scripts/translate.mts --lang ja # 翻译为日语 npx tsx scripts/translate.mts --lang ko # 翻译为韩语 npx tsx scripts/translate.mts --lang fr # 翻译为法语 npx tsx scripts/translate.mts --lang de # 翻译为德语 npx tsx scripts/translate.mts --provider anthropic --lang ja # 使用 Claude 翻译日语 npx tsx scripts/translate.mts --dry-run --lang ko # 预览韩语翻译计划 npx tsx scripts/translate.mts --validate --lang fr # 验证法语翻译质量 npx tsx scripts/translate.mts --dry-run # 预览所有语言翻译计划 `); process.exit(0); } try { const { aiConfig, targetLang } = parseAIConfig(); // 检查 i18n.json 文件是否存在 if (!fs.existsSync(i18nPath)) { console.error(`❌ 错误: i18n.json 文件不存在: ${i18nPath}`); console.error(`请先运行 'node scripts/extract-i18n.mjs' 生成 i18n.json 文件`); process.exit(1); } console.log("🌐 开始 HyperChat 智能翻译 (基于 AI SDK)..."); console.log(`🤖 使用: ${aiConfig.provider}/${aiConfig.model}`); const json = JSON.parse(fs.readFileSync(i18nPath).toString()); let hasChanges = false; // 如果选择翻译所有语言 if (targetLang === 'all') { console.log("🌍 翻译所有支持的语言..."); for (const lang of Object.keys(LANGUAGE_CONFIG)) { console.log(`\n📍 开始翻译 ${LANGUAGE_CONFIG[lang].name} (${lang})...`); const result = await processLanguage(json, aiConfig, lang); if (result) hasChanges = true; } } else { const langConfig = LANGUAGE_CONFIG[targetLang]; console.log(`📍 翻译目标语言: ${langConfig.name}`); const result = await processLanguage(json, aiConfig, targetLang); if (result) hasChanges = true; } // 如果是 dry-run 或 validate 模式,提前返回 if (argv['dry-run'] || argv.validate) { if (argv['dry-run'] && targetLang === 'all') { console.log(`⚡ 预计使用批量翻译,减少 API 调用`); console.log(`🤖 使用模型: ${aiConfig.provider}/${aiConfig.model}`); } return; } if (hasChanges) { // 备份原文件 const backupPath = i18nPath + '.backup-' + Date.now(); fs.copyFileSync(i18nPath, backupPath); console.log(`\n💾 已备份原文件到: ${backupPath}`); fs.writeFileSync(i18nPath, JSON.stringify(json, null, 2)); console.log(`✅ 已更新 i18n.json 文件`); // 重新构建 shared 包 console.log('\n🔧 重新构建 shared 包...'); try { await $`npm run build --workspace=@dadigua/hyperchat-shared`; console.log('✅ shared 包构建完成,i18n.json 已同步到 dist 目录'); } catch (error) { console.warn('⚠️ shared 包构建失败,可能需要手动运行: npm run build --workspace=@dadigua/hyperchat-shared'); } } console.log(`\n🎉 翻译完成!使用 ${aiConfig.provider}/${aiConfig.model} 完成智能翻译`); } catch (error) { console.error("❌ 脚本执行失败:", error); process.exit(1); } } // 运行主函数 await main();