From b616e10ceb51c94de39316f872a011f725ed00c9 Mon Sep 17 00:00:00 2001 From: WrBug Date: Sun, 8 Feb 2026 23:17:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(skills):=20=E6=B7=BB=E5=8A=A0=20check-i18n?= =?UTF-8?q?-keys=20=E6=8A=80=E8=83=BD=E5=B9=B6=E8=A1=A5=E5=85=A8=E5=A4=9A?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 按 Cursor Agent Skills 规范:技能目录含 SKILL.md 与 scripts/ - 多语言检查脚本:扫描 t() 引用,对比 zh-CN/zh-TW/en common.json - 补全缺失 key:common.create/copy/copySuccess/copyFailed、 leaderList.openDetailFailed、leaderDetail.market、 filteredOrdersList.orderDetails、filteredOrdersList.filterTypes.keywordFilter Co-authored-by: Cursor --- .cursor/skills/README.md | 39 + .../skills/frontend/check-i18n-keys/SKILL.md | 36 + .../check-i18n-keys/scripts/.gitignore | 3 + .../scripts/check-i18n-keys.ts | 302 ++++++++ .../check-i18n-keys/scripts/package-lock.json | 680 ++++++++++++++++++ .../check-i18n-keys/scripts/package.json | 16 + .../check-i18n-keys/scripts/tsconfig.json | 20 + frontend/src/locales/en/common.json | 10 +- frontend/src/locales/zh-CN/common.json | 10 +- frontend/src/locales/zh-TW/common.json | 10 +- 10 files changed, 1123 insertions(+), 3 deletions(-) create mode 100644 .cursor/skills/README.md create mode 100644 .cursor/skills/frontend/check-i18n-keys/SKILL.md create mode 100644 .cursor/skills/frontend/check-i18n-keys/scripts/.gitignore create mode 100644 .cursor/skills/frontend/check-i18n-keys/scripts/check-i18n-keys.ts create mode 100644 .cursor/skills/frontend/check-i18n-keys/scripts/package-lock.json create mode 100644 .cursor/skills/frontend/check-i18n-keys/scripts/package.json create mode 100644 .cursor/skills/frontend/check-i18n-keys/scripts/tsconfig.json diff --git a/.cursor/skills/README.md b/.cursor/skills/README.md new file mode 100644 index 0000000..9830a72 --- /dev/null +++ b/.cursor/skills/README.md @@ -0,0 +1,39 @@ +# Cursor Skills 目录结构 + +本目录遵循 [Cursor Agent Skills 规范](https://cursor.com/cn/docs/context/skills):每个技能为一个**文件夹**,内含 `SKILL.md` 及可选的 `scripts/`、`references/`、`assets/`。 + +## 目录结构 + +``` +.cursor/skills/ +├── frontend/ # 前端相关 skill 分组 +│ └── check-i18n-keys/ # 单个 skill(文件夹名 = name) +│ ├── SKILL.md # 必填,技能定义与指令 +│ └── scripts/ +│ ├── check-i18n-keys.ts +│ └── package.json +├── backend/ # 后端相关 skill +└── common/ # 通用 skill +``` + +- **SKILL.md**:YAML frontmatter(`name`、`description` 必填,`name` 须与父文件夹名一致、小写连字符)+ 给 Agent 的详细指令。 +- **scripts/**:Agent 可执行的脚本,在 SKILL.md 中用相对路径引用。 + +## 添加新 Skill + +1. **确定分组**:在 `frontend/`、`backend/` 或 `common/` 下新建**以技能名命名的文件夹**(仅小写、数字、连字符,如 `check-i18n-keys`)。 +2. **创建 SKILL.md**:在该文件夹内创建 `SKILL.md`(大写),frontmatter 中 `name` 必须与文件夹名一致。 +3. **可选 scripts/**:在技能文件夹内建 `scripts/`,放入可执行脚本;在 SKILL.md 正文中写明运行命令(如 `cd .cursor/skills/.../scripts && npm run xxx`)。 + +## 示例 + +- `frontend/check-i18n-keys/SKILL.md` + `frontend/check-i18n-keys/scripts/` — 检查前端多语言 key。 + +## 运行 check-i18n-keys + +```bash +cd .cursor/skills/frontend/check-i18n-keys/scripts +npm install +npm run check-i18n +``` + diff --git a/.cursor/skills/frontend/check-i18n-keys/SKILL.md b/.cursor/skills/frontend/check-i18n-keys/SKILL.md new file mode 100644 index 0000000..3e71bc2 --- /dev/null +++ b/.cursor/skills/frontend/check-i18n-keys/SKILL.md @@ -0,0 +1,36 @@ +--- +name: check-i18n-keys +description: 检查前端多语言 key 完整性。当用户要求检查 i18n、多语言缺失、翻译 key 或运行 check-i18n 时使用。 +--- + +# Check I18n Keys + +检查前端代码中使用的 i18n key 是否在所有语言文件(zh-CN、zh-TW、en)中存在,并报告缺失或语言间不一致的 key。 + +## 使用时机 + +- 用户要求「检查多语言」「检查 i18n」「扫一下 key」「多语言缺失」时 +- 用户要求运行多语言检查或执行 check-i18n 时 +- 在修改或新增前端文案后,需要确认三语言 key 一致时 + +## 指令 + +1. **运行检查脚本**(在项目仓库根目录下执行): + ```bash + cd .cursor/skills/frontend/check-i18n-keys/scripts && npm install && npm run check-i18n + ``` + 首次运行需先 `npm install`,之后可直接 `npm run check-i18n`。 + +2. **脚本行为**: + - 扫描 `frontend/` 下所有 `.ts`、`.tsx`、`.js`、`.jsx`(排除 node_modules、dist、build、*.d.ts) + - 提取代码中 `t('key')` / `t("key")` 的 key + - 与 `frontend/src/locales/{zh-CN,zh-TW,en}/common.json` 对比 + - 报告:代码中使用但某语言 JSON 缺失的 key;某语言有而另一语言没有的 key(不一致) + +3. **结果处理**: + - 若有缺失或不一致,退出码为 1;可根据报告在对应 `common.json` 中补全 key + - 脚本路径:`scripts/check-i18n-keys.ts`(相对本 skill 根目录) + +## 可选目录说明 + +- `scripts/`:可执行检查脚本,Agent 按上述命令调用。 diff --git a/.cursor/skills/frontend/check-i18n-keys/scripts/.gitignore b/.cursor/skills/frontend/check-i18n-keys/scripts/.gitignore new file mode 100644 index 0000000..3c45938 --- /dev/null +++ b/.cursor/skills/frontend/check-i18n-keys/scripts/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +*.log +.DS_Store diff --git a/.cursor/skills/frontend/check-i18n-keys/scripts/check-i18n-keys.ts b/.cursor/skills/frontend/check-i18n-keys/scripts/check-i18n-keys.ts new file mode 100644 index 0000000..0c11e3e --- /dev/null +++ b/.cursor/skills/frontend/check-i18n-keys/scripts/check-i18n-keys.ts @@ -0,0 +1,302 @@ +#!/usr/bin/env node + +/** + * 检查前端多语言 key 完整性 + * + * 扫描范围:frontend 下所有 .ts / .tsx / .js / .jsx(排除 node_modules、dist、build、*.d.ts) + * + * 支持的检查: + * 1. 代码引用但 JSON 没有:某 ts/js 引用了 t("common.test"),但任意语言 common.json 里没有该 key → 报缺失 + * 2. 语言间不一致:zh-CN/zh-TW 的 JSON 有 common.xxx,但 en 的没有 → 报不一致(某些语言有、某些没有) + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; +import { glob } from 'glob'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +interface CheckResult { + usedKeys: Set; + localeKeys: { + 'zh-CN': Set; + 'zh-TW': Set; + 'en': Set; + }; + missingKeys: { + 'zh-CN': string[]; + 'zh-TW': string[]; + 'en': string[]; + }; + inconsistentKeys: Array<{ + key: string; + existsIn: string[]; + missingIn: string[]; + }>; +} + +const LOCALES = ['zh-CN', 'zh-TW', 'en'] as const; +type Locale = typeof LOCALES[number]; + +// 颜色输出(如果支持) +const colors = { + reset: '\x1b[0m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', +}; + +function log(message: string, color: keyof typeof colors = 'reset') { + console.log(`${colors[color]}${message}${colors.reset}`); +} + +/** + * 从代码文件中提取所有 t() 调用中的 key + */ +function extractKeysFromCode(frontendDir: string): Set { + const keys = new Set(); + + // 匹配 t('key') 或 t("key") 或 t(`key`) + const tPattern = /t\(['"`]([^'"`]+)['"`]\)/g; + + // 扫描所有 TS/JS 文件(含 .ts .tsx .js .jsx),排除 node_modules、dist、build、*.d.ts + const files = glob.sync('**/*.{ts,tsx,js,jsx}', { + cwd: frontendDir, + ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/*.d.ts'], + absolute: true, + }); + + for (const file of files) { + try { + const content = fs.readFileSync(file, 'utf-8'); + let match; + + while ((match = tPattern.exec(content)) !== null) { + const key = match[1]; + // 过滤掉模板字符串中的变量(如 t(`key.${variable}`)) + if (!key.includes('${') && !key.includes('${')) { + keys.add(key); + } + } + } catch (error) { + console.error(`Error reading file ${file}:`, error); + } + } + + return keys; +} + +/** + * 加载语言文件 + */ +function loadLocaleFile(localeDir: string, locale: Locale): Record { + const filePath = path.join(localeDir, locale, 'common.json'); + + if (!fs.existsSync(filePath)) { + log(`⚠️ 语言文件不存在: ${filePath}`, 'yellow'); + return {}; + } + + try { + const content = fs.readFileSync(filePath, 'utf-8'); + return JSON.parse(content); + } catch (error) { + log(`❌ 解析语言文件失败 ${filePath}: ${error}`, 'red'); + return {}; + } +} + +/** + * 展平嵌套对象为点分隔的 key 路径 + */ +function flattenKeys(obj: Record, prefix = ''): Set { + const keys = new Set(); + + for (const [key, value] of Object.entries(obj)) { + const fullKey = prefix ? `${prefix}.${key}` : key; + + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + // 递归处理嵌套对象 + const nestedKeys = flattenKeys(value, fullKey); + nestedKeys.forEach(k => keys.add(k)); + } else { + // 叶子节点 + keys.add(fullKey); + } + } + + return keys; +} + +/** + * 检查 key 完整性 + */ +function checkKeys(usedKeys: Set, localeKeys: Record>): CheckResult { + const result: CheckResult = { + usedKeys, + localeKeys, + missingKeys: { + 'zh-CN': [], + 'zh-TW': [], + 'en': [], + }, + inconsistentKeys: [], + }; + + // 检查每个语言文件中缺失的 key + for (const locale of LOCALES) { + for (const key of usedKeys) { + if (!localeKeys[locale].has(key)) { + result.missingKeys[locale].push(key); + } + } + } + + // 检查不一致的 key(某些语言有,某些没有) + const allLocaleKeys = new Set(); + LOCALES.forEach(locale => { + localeKeys[locale].forEach(key => allLocaleKeys.add(key)); + }); + + for (const key of allLocaleKeys) { + const existsIn: Locale[] = []; + const missingIn: Locale[] = []; + + for (const locale of LOCALES) { + if (localeKeys[locale].has(key)) { + existsIn.push(locale); + } else { + missingIn.push(locale); + } + } + + // 如果某些语言有,某些没有,则不一致 + if (existsIn.length > 0 && existsIn.length < LOCALES.length) { + result.inconsistentKeys.push({ + key, + existsIn, + missingIn, + }); + } + } + + return result; +} + +/** + * 打印检查报告 + */ +function printReport(result: CheckResult): void { + log('\n=== 多语言 Key 检查报告 ===\n', 'cyan'); + + // 统计信息 + log('📊 统计信息:', 'blue'); + log(` - 代码中使用的 key 数量: ${result.usedKeys.size}`); + log(` - zh-CN 语言文件 key 数量: ${result.localeKeys['zh-CN'].size}`); + log(` - zh-TW 语言文件 key 数量: ${result.localeKeys['zh-TW'].size}`); + log(` - en 语言文件 key 数量: ${result.localeKeys['en'].size}`); + log(''); + + // 缺失的 key + let hasMissing = false; + for (const locale of LOCALES) { + if (result.missingKeys[locale].length > 0) { + hasMissing = true; + log(`❌ 缺失的 Key (代码中使用但 ${locale} 语言文件中不存在):`, 'red'); + result.missingKeys[locale].forEach(key => { + log(` - ${key}`, 'red'); + }); + log(''); + } + } + + // 不一致的 key + if (result.inconsistentKeys.length > 0) { + log('⚠️ 不一致的 Key (某些语言文件有,某些没有):', 'yellow'); + result.inconsistentKeys.forEach(({ key, existsIn, missingIn }) => { + log(` - ${key}`, 'yellow'); + log(` 存在于: ${existsIn.join(', ')}`, 'yellow'); + log(` 缺失于: ${missingIn.join(', ')}`, 'yellow'); + }); + log(''); + } + + // 总结 + const hasErrors = hasMissing || result.inconsistentKeys.length > 0; + + if (hasErrors) { + log('❌ 检查失败:发现缺失或不一致的 key', 'red'); + } else { + log('✅ 检查通过:所有 key 都完整且一致', 'green'); + } +} + +/** + * 主函数 + */ +function main(): void { + // 脚本位于 .cursor/skills/frontend/check-i18n-keys/scripts/,向上到项目根 + const scriptDir = __dirname; + const skillDir = path.resolve(scriptDir, '..'); + const frontendSkillDir = path.resolve(skillDir, '..'); + const repoRoot = path.resolve(frontendSkillDir, '../../..'); + const frontendDir = path.join(repoRoot, 'frontend'); + const localesDir = path.join(frontendDir, 'src', 'locales'); + + // 检查目录是否存在 + if (!fs.existsSync(frontendDir)) { + log(`❌ 前端目录不存在: ${frontendDir}`, 'red'); + process.exit(1); + } + + if (!fs.existsSync(localesDir)) { + log(`❌ 语言文件目录不存在: ${localesDir}`, 'red'); + process.exit(1); + } + + log('🔍 开始检查多语言 key...\n', 'cyan'); + + // 1. 提取代码中使用的 key + log('📝 扫描代码文件...', 'blue'); + const usedKeys = extractKeysFromCode(frontendDir); + log(` 找到 ${usedKeys.size} 个使用的 key\n`, 'green'); + + // 2. 加载语言文件 + log('📚 加载语言文件...', 'blue'); + const localeKeys: Record> = { + 'zh-CN': new Set(), + 'zh-TW': new Set(), + 'en': new Set(), + }; + + for (const locale of LOCALES) { + const localeObj = loadLocaleFile(localesDir, locale); + localeKeys[locale] = flattenKeys(localeObj); + log(` ${locale}: ${localeKeys[locale].size} 个 key`, 'green'); + } + log(''); + + // 3. 检查 + log('🔎 检查 key 完整性...', 'blue'); + const result = checkKeys(usedKeys, localeKeys); + + // 4. 输出报告 + printReport(result); + + // 5. 退出码 + const hasErrors = + result.missingKeys['zh-CN'].length > 0 || + result.missingKeys['zh-TW'].length > 0 || + result.missingKeys['en'].length > 0 || + result.inconsistentKeys.length > 0; + + process.exit(hasErrors ? 1 : 0); +} + +// 运行 +main(); + diff --git a/.cursor/skills/frontend/check-i18n-keys/scripts/package-lock.json b/.cursor/skills/frontend/check-i18n-keys/scripts/package-lock.json new file mode 100644 index 0000000..a21e8cb --- /dev/null +++ b/.cursor/skills/frontend/check-i18n-keys/scripts/package-lock.json @@ -0,0 +1,680 @@ +{ + "name": "cursor-skill-frontend-scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cursor-skill-frontend-scripts", + "version": "1.0.0", + "dependencies": { + "glob": "^13.0.1" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", + "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.1.2", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/.cursor/skills/frontend/check-i18n-keys/scripts/package.json b/.cursor/skills/frontend/check-i18n-keys/scripts/package.json new file mode 100644 index 0000000..6a94cb9 --- /dev/null +++ b/.cursor/skills/frontend/check-i18n-keys/scripts/package.json @@ -0,0 +1,16 @@ +{ + "name": "cursor-skill-frontend-scripts", + "version": "1.0.0", + "type": "module", + "scripts": { + "check-i18n": "tsx check-i18n-keys.ts" + }, + "dependencies": { + "glob": "^13.0.1" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + } +} diff --git a/.cursor/skills/frontend/check-i18n-keys/scripts/tsconfig.json b/.cursor/skills/frontend/check-i18n-keys/scripts/tsconfig.json new file mode 100644 index 0000000..9b569f6 --- /dev/null +++ b/.cursor/skills/frontend/check-i18n-keys/scripts/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "downlevelIteration": true, + "types": ["node"] + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} + diff --git a/frontend/src/locales/en/common.json b/frontend/src/locales/en/common.json index 0c14178..7da1381 100644 --- a/frontend/src/locales/en/common.json +++ b/frontend/src/locales/en/common.json @@ -16,6 +16,10 @@ "submit": "Submit", "reset": "Reset", "close": "Close", + "create": "Create", + "copy": "Copy", + "copySuccess": "Copied successfully", + "copyFailed": "Copy failed", "yes": "Yes", "no": "No", "actions": "Actions", @@ -437,7 +441,8 @@ "deleteSuccess": "Leader deleted successfully", "deleteFailed": "Failed to delete Leader", "deleteConfirm": "Are you sure you want to delete this Leader?", - "deleteConfirmDesc": "This Leader has {{count}} copy trading relations, please delete them first" + "deleteConfirmDesc": "This Leader has {{count}} copy trading relations, please delete them first", + "openDetailFailed": "Failed to open detail" }, "leaderAdd": { "title": "Add Leader", @@ -510,6 +515,7 @@ "totalBalance": "Total Balance", "positions": "Positions", "noPositions": "No positions", + "market": "Market", "marketId": "Market ID", "side": "Side", "quantity": "Quantity", @@ -968,8 +974,10 @@ "priceRange": "Price Range Mismatch", "maxPositionValue": "Exceeds Max Position Value", "marketEndDate": "Market End Date Exceeds Limit", + "keywordFilter": "Keyword Filter", "unknown": "Unknown Reason" }, + "orderDetails": "Order Details", "noData": "No filtered orders", "noFilteredOrders": "No filtered orders" }, diff --git a/frontend/src/locales/zh-CN/common.json b/frontend/src/locales/zh-CN/common.json index 9a8e6de..2b6e185 100644 --- a/frontend/src/locales/zh-CN/common.json +++ b/frontend/src/locales/zh-CN/common.json @@ -35,6 +35,10 @@ "success": "成功", "failed": "失败", "close": "关闭", + "create": "创建", + "copy": "复制", + "copySuccess": "复制成功", + "copyFailed": "复制失败", "ascending": "升序", "descending": "降序", "day": "天" @@ -437,7 +441,8 @@ "deleteSuccess": "删除 Leader 成功", "deleteFailed": "删除 Leader 失败", "deleteConfirm": "确定要删除这个 Leader 吗?", - "deleteConfirmDesc": "该 Leader 还有 {{count}} 个跟单关系,请先删除跟单关系" + "deleteConfirmDesc": "该 Leader 还有 {{count}} 个跟单关系,请先删除跟单关系", + "openDetailFailed": "打开详情失败" }, "leaderAdd": { "title": "添加 Leader", @@ -510,6 +515,7 @@ "totalBalance": "总余额", "positions": "持仓列表", "noPositions": "暂无持仓", + "market": "市场", "marketId": "市场ID", "side": "方向", "quantity": "数量", @@ -968,8 +974,10 @@ "priceRange": "价格区间不符", "maxPositionValue": "超过最大仓位金额", "marketEndDate": "市场截止时间超出限制", + "keywordFilter": "关键字过滤", "unknown": "未知原因" }, + "orderDetails": "订单详情", "noData": "暂无已过滤订单", "noFilteredOrders": "暂无已过滤订单" }, diff --git a/frontend/src/locales/zh-TW/common.json b/frontend/src/locales/zh-TW/common.json index a6127d4..f1e9bd5 100644 --- a/frontend/src/locales/zh-TW/common.json +++ b/frontend/src/locales/zh-TW/common.json @@ -16,6 +16,10 @@ "submit": "提交", "reset": "重置", "close": "關閉", + "create": "創建", + "copy": "複製", + "copySuccess": "複製成功", + "copyFailed": "複製失敗", "yes": "是", "no": "否", "actions": "操作", @@ -437,7 +441,8 @@ "deleteSuccess": "刪除 Leader 成功", "deleteFailed": "刪除 Leader 失敗", "deleteConfirm": "確定要刪除這個 Leader 嗎?", - "deleteConfirmDesc": "該 Leader 還有 {{count}} 個跟單關係,請先刪除跟單關係" + "deleteConfirmDesc": "該 Leader 還有 {{count}} 個跟單關係,請先刪除跟單關係", + "openDetailFailed": "打開詳情失敗" }, "leaderAdd": { "title": "添加 Leader", @@ -510,6 +515,7 @@ "totalBalance": "總餘額", "positions": "持倉列表", "noPositions": "暫無持倉", + "market": "市場", "marketId": "市場ID", "side": "方向", "quantity": "數量", @@ -968,8 +974,10 @@ "priceRange": "價格區間不符", "maxPositionValue": "超過最大倉位金額", "marketEndDate": "市場截止時間超出限制", + "keywordFilter": "關鍵字過濾", "unknown": "未知原因" }, + "orderDetails": "訂單詳情", "noData": "暫無已過濾訂單", "noFilteredOrders": "暫無已過濾訂單" },