repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | #!/usr/bin/env bun |
| #2 | /** |
| #3 | * 语言验证命令 |
| #4 | * 用于验证翻译文件的语言是否匹配预期的语言代码 |
| #5 | */ |
| #6 | import { existsSync, readdirSync, rmSync } from 'node:fs'; |
| #7 | import { resolve } from 'node:path'; |
| #8 | import pMap from 'p-map'; |
| #9 | |
| #10 | import { Logger } from '../utils/logger'; |
| #11 | import { |
| #12 | LanguageValidationResult, |
| #13 | ValidationStats, |
| #14 | ensureELDInitialized, |
| #15 | fixLanguageIssues, |
| #16 | fixLanguageWithFallback, |
| #17 | validateTranslationLanguage, |
| #18 | } from '../validators/language-validator'; |
| #19 | |
| #20 | /** |
| #21 | * 获取所有翻译文件路径 |
| #22 | * @param targetFile - 指定验证的文件路径 |
| #23 | * @returns 翻译文件路径数组 |
| #24 | */ |
| #25 | function getAllTranslationFiles(targetFile?: string): string[] { |
| #26 | if (targetFile) { |
| #27 | return existsSync(targetFile) ? [targetFile] : []; |
| #28 | } |
| #29 | |
| #30 | const files: string[] = []; |
| #31 | const localesDir = resolve(process.cwd(), 'locales'); |
| #32 | |
| #33 | if (!existsSync(localesDir)) { |
| #34 | return files; |
| #35 | } |
| #36 | |
| #37 | const dirs = readdirSync(localesDir, { withFileTypes: true }); |
| #38 | |
| #39 | for (const dir of dirs) { |
| #40 | if (dir.isDirectory()) { |
| #41 | const agentDir = resolve(localesDir, dir.name); |
| #42 | const agentFiles = readdirSync(agentDir).filter( |
| #43 | (file) => file.endsWith('.json') && !file.endsWith('index.json'), |
| #44 | ); |
| #45 | |
| #46 | for (const file of agentFiles) { |
| #47 | files.push(resolve(agentDir, file)); |
| #48 | } |
| #49 | } |
| #50 | } |
| #51 | |
| #52 | return files; |
| #53 | } |
| #54 | |
| #55 | /** |
| #56 | * 将绝对路径转换为可点击的相对路径格式 |
| #57 | * @param filePath - 绝对文件路径 |
| #58 | * @returns 相对路径格式,便于在终端中点击打开 |
| #59 | */ |
| #60 | function formatClickablePath(filePath: string): string { |
| #61 | return filePath.replace(process.cwd() + '/', './'); |
| #62 | } |
| #63 | |
| #64 | /** |
| #65 | * 验证所有翻译文件的语言 |
| #66 | * @param shouldDelete - 是否删除验证失败的文件 |
| #67 | * @param shouldFix - 是否修复部分匹配的文件 |
| #68 | * @param targetFile - 指定验证的文件路径 |
| #69 | */ |
| #70 | async function validateAllLanguages( |
| #71 | shouldDelete = false, |
| #72 | shouldFix = false, |
| #73 | targetFile?: string, |
| #74 | ): Promise<void> { |
| #75 | Logger.split('⚡ 开始验证所有翻译文件的语言'); |
| #76 | |
| #77 | // 获取所有翻译文件 |
| #78 | const files = getAllTranslationFiles(targetFile); |
| #79 | |
| #80 | Logger.info(`找到翻译文件 ${files.length}`); |
| #81 | |
| #82 | if (files.length === 0) { |
| #83 | Logger.warn('没有找到翻译文件'); |
| #84 | return; |
| #85 | } |
| #86 | |
| #87 | // 预先初始化 ELD 语言检测器,避免并发初始化问题 |
| #88 | await ensureELDInitialized(); |
| #89 | |
| #90 | const stats: ValidationStats = { |
| #91 | total: files.length, |
| #92 | passed: 0, |
| #93 | failed: 0, |
| #94 | ignored: 0, |
| #95 | lowConfidence: 0, |
| #96 | fixed: 0, |
| #97 | }; |
| #98 | |
| #99 | const failedFiles: string[] = []; |
| #100 | const fixableFiles: LanguageValidationResult[] = []; |
| #101 | const detailedResults: LanguageValidationResult[] = []; |
| #102 | |
| #103 | // 并发验证所有文件 |
| #104 | await pMap( |
| #105 | files, |
| #106 | async (file) => { |
| #107 | const result = await validateTranslationLanguage(file); |
| #108 | detailedResults.push(result); |
| #109 | |
| #110 | // 生成可点击的文件路径 (相对路径格式,VSCode 终端可识别) |
| #111 | const clickablePath = formatClickablePath(file); |
| #112 | |
| #113 | // 检查是否被忽略 |
| #114 | if (result.expectedLanguage === 'ignored') { |
| #115 | stats.ignored++; |
| #116 | return result; |
| #117 | } |
| #118 | |
| #119 | // 检查是否可以修复 |
| #120 | if (result.fixable) { |
| #121 | fixableFiles.push(result); |
| #122 | } |
| #123 | |
| #124 | if (!result.valid) { |
| #125 | stats.failed++; |
| #126 | failedFiles.push(file); |
| #127 | |
| #128 | // 构建问题摘要 |
| #129 | const issuesSummary = |
| #130 | result.issues && result.issues.length > 0 ? ` (${result.issues.length} 个字段问题)` : ''; |
| #131 | |
| #132 | // 如果可以修复,显示不同的提示 |
| #133 | if (result.fixable) { |
| #134 | Logger.warn( |
| #135 | `⚠️ ${clickablePath} - 期望 ${result.expectedLanguage}, 检测到 ${result.detectedLanguage} (${result.confidence.toFixed(3)})${issuesSummary} [可修复]`, |
| #136 | ); |
| #137 | } else { |
| #138 | Logger.error( |
| #139 | `❌ ${clickablePath} - 期望 ${result.expectedLanguage}, 检测到 ${result.detectedLanguage} (${result.confidence.toFixed(3)})${issuesSummary}`, |
| #140 | ); |
| #141 | } |
| #142 | } else { |
| #143 | stats.passed++; |
| #144 | |
| #145 | // 检查是否有字段级问题需要修复 |
| #146 | if (result.fixable && result.issues && result.issues.length > 0) { |
| #147 | Logger.warn(`⚠️ ${clickablePath} - ${result.issues.length} 个字段语言问题 [可修复]`); |
| #148 | } |
| #149 | // 低置信度警告 (只对很低置信度的警告) |
| #150 | else if (result.confidence < 0.4 && result.confidence >= 0.2) { |
| #151 | stats.lowConfidence++; |
| #152 | Logger.warn(`⚠️ ${clickablePath} - 置信度很低 (${result.confidence.toFixed(3)})`); |
| #153 | } |
| #154 | // 正常通过的文件(只在较少文件时显示) |
| #155 | else if (files.length <= 20) { |
| #156 | Logger.info(`✅ ${clickablePath} - 通过验证 (${result.confidence.toFixed(3)})`); |
| #157 | } |
| #158 | } |
| #159 | |
| #160 | return result; |
| #161 | }, |
| #162 | { concurrency: 10 }, |
| #163 | ); |
| #164 | |
| #165 | // 如果需要修复,处理可修复的文件 |
| #166 | if (shouldFix && fixableFiles.length > 0) { |
| #167 | Logger.split('🔧 开始修复语言不匹配的文件'); |
| #168 | Logger.info(`发现 ${fixableFiles.length} 个文件需要修复`); |
| #169 | |
| #170 | for (const result of fixableFiles) { |
| #171 | const clickablePath = formatClickablePath(result.filePath); |
| #172 | Logger.info(`修复 ${clickablePath}`); |
| #173 | |
| #174 | let fixed = false; |
| #175 | |
| #176 | // 判断使用哪种修复方式 |
| #177 | const shouldUseFallback = |
| #178 | !result.valid && // 验证失败 |
| #179 | (!result.detectedLanguage || // 无法检测语言 |
| #180 | result.detectedLanguage !== result.expectedLanguage || // 语言不匹配 |
| #181 | result.confidence < 0.4); // 置信度很低 |
| #182 | |
| #183 | if (shouldUseFallback) { |
| #184 | // 使用 en-US 兜底修复整个文件 |
| #185 | fixed = await fixLanguageWithFallback(result.filePath); |
| #186 | } else if (result.issues && result.issues.length > 0) { |
| #187 | // 有具体字段问题,使用字段级修复 |
| #188 | fixed = await fixLanguageIssues(result.filePath, result.issues); |
| #189 | } else { |
| #190 | // 兜底情况:其他可修复问题也使用兜底修复 |
| #191 | fixed = await fixLanguageWithFallback(result.filePath); |
| #192 | } |
| #193 | |
| #194 | if (fixed) { |
| #195 | stats.fixed++; |
| #196 | } |
| #197 | } |
| #198 | |
| #199 | Logger.success(`修复完成,共修复 ${stats.fixed} 个文件`); |
| #200 | } |
| #201 | |
| #202 | // 如果需要删除,删除验证失败的文件 |
| #203 | if (shouldDelete && failedFiles.length > 0) { |
| #204 | Logger.split('🗑️ 开始删除验证失败的文件'); |
| #205 | Logger.warn(`将删除 ${failedFiles.length} 个验证失败的文件`); |
| #206 | |
| #207 | let deletedCount = 0; |
| #208 | for (const file of failedFiles) { |
| #209 | try { |
| #210 | rmSync(file); |
| #211 | const clickablePath = formatClickablePath(file); |
| #212 | Logger.info(`已删除: ${clickablePath}`); |
| #213 | deletedCount++; |
| #214 | } catch (error) { |
| #215 | const clickablePath = formatClickablePath(file); |
| #216 | Logger.error(`删除失败: ${clickablePath} - ${error}`); |
| #217 | } |
| #218 | } |
| #219 | |
| #220 | Logger.warn(`删除完成,共删除 ${deletedCount} 个文件`); |
| #221 | } |
| #222 | |
| #223 | // 显示汇总统计 |
| #224 | Logger.split('📊 验证汇总统计'); |
| #225 | Logger.info(`总文件数: ${stats.total}`); |
| #226 | |
| #227 | // 计算实际验证的文件数(排除忽略的文件) |
| #228 | const actualTotal = stats.total - stats.ignored; |
| #229 | const successRate = actualTotal > 0 ? ((stats.passed / actualTotal) * 100).toFixed(1) : '100.0'; |
| #230 | |
| #231 | Logger.info(`实际验证: ${actualTotal} 个文件`); |
| #232 | Logger.info(`成功率: ${successRate}%`); |
| #233 | Logger.info(`验证通过: ${stats.passed}`); |
| #234 | |
| #235 | if (stats.ignored > 0) { |
| #236 | Logger.info(`已忽略: ${stats.ignored}`); |
| #237 | } |
| #238 | |
| #239 | if (stats.failed > 0) { |
| #240 | Logger.error(`验证失败: ${stats.failed}`); |
| #241 | } |
| #242 | |
| #243 | if (stats.lowConfidence > 0) { |
| #244 | Logger.warn(`低置信度: ${stats.lowConfidence}`); |
| #245 | } |
| #246 | |
| #247 | if (fixableFiles.length > 0 && !shouldFix) { |
| #248 | Logger.warn(`可修复问题: ${fixableFiles.length} 个文件`); |
| #249 | Logger.info('使用 --fix 参数来修复这些问题'); |
| #250 | } |
| #251 | |
| #252 | if (shouldFix && stats.fixed > 0) { |
| #253 | Logger.success(`已修复: ${stats.fixed} 个文件`); |
| #254 | } |
| #255 | |
| #256 | if (shouldDelete && failedFiles.length > 0) { |
| #257 | Logger.warn(`已删除: ${failedFiles.length} 个文件`); |
| #258 | } |
| #259 | |
| #260 | if (stats.failed === 0) { |
| #261 | Logger.success(`✅ 验证完成,所有文件通过验证`); |
| #262 | } else { |
| #263 | Logger.error(`❌ 验证完成,${stats.failed} 个文件失败`); |
| #264 | |
| #265 | if (!shouldDelete) { |
| #266 | Logger.info('使用 --delete 参数来删除验证失败的文件'); |
| #267 | } |
| #268 | } |
| #269 | } |
| #270 | |
| #271 | // 解析命令行参数 |
| #272 | const args = process.argv.slice(2); |
| #273 | const shouldDelete = args.includes('--delete'); |
| #274 | const shouldFix = args.includes('--fix'); |
| #275 | const targetFile = args.find((arg) => !arg.startsWith('--')); |
| #276 | |
| #277 | // 运行验证 - 使用顶级await |
| #278 | try { |
| #279 | await validateAllLanguages(shouldDelete, shouldFix, targetFile); |
| #280 | } catch (error) { |
| #281 | Logger.error(`验证过程中发生错误: ${error}`); |
| #282 | process.exit(1); |
| #283 | } |
| #284 | |
| #285 | |
| #286 |