From 6880ae7a7962639c0cf7552dcb5e43da41c6c875 Mon Sep 17 00:00:00 2001 From: PineHomePC Date: Sun, 23 Aug 2026 22:40:13 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E9=85=8D=E7=BD=AE=E4=B8=8E=E6=9D=82?= =?UTF-8?q?=E9=A1=B9=EF=BC=88makefile/.env.example/CI=20=E5=85=83=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/i18n-translate/SKILL.md | 314 ++ .agents/skills/shadcn-ui/SKILL.md | 105 + .../shadcn-ui/vendor/shadcn/UPSTREAM.txt | 4 + .agents/skills/shadcn-ui/vendor/shadcn/cli.md | 276 ++ .../shadcn-ui/vendor/shadcn/customization.md | 209 ++ .agents/skills/shadcn-ui/vendor/shadcn/mcp.md | 94 + .../shadcn/official-shadcn-ui-workflow.md | 257 ++ .../vendor/shadcn/rules/base-vs-radix.md | 306 ++ .../vendor/shadcn/rules/composition.md | 195 ++ .../shadcn-ui/vendor/shadcn/rules/forms.md | 192 ++ .../shadcn-ui/vendor/shadcn/rules/icons.md | 101 + .../shadcn-ui/vendor/shadcn/rules/styling.md | 162 + .../vercel-react-best-practices/SKILL.md | 31 + .../references/full-guide.md | 2934 +++++++++++++++++ .dockerignore | 12 + .env.example | 120 + .gitattributes | 43 + makefile | 73 + 18 files changed, 5428 insertions(+) create mode 100644 .agents/skills/i18n-translate/SKILL.md create mode 100644 .agents/skills/shadcn-ui/SKILL.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/UPSTREAM.txt create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/cli.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/customization.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/mcp.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/official-shadcn-ui-workflow.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/rules/base-vs-radix.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/rules/composition.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/rules/forms.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/rules/icons.md create mode 100644 .agents/skills/shadcn-ui/vendor/shadcn/rules/styling.md create mode 100644 .agents/skills/vercel-react-best-practices/SKILL.md create mode 100644 .agents/skills/vercel-react-best-practices/references/full-guide.md create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 makefile diff --git a/.agents/skills/i18n-translate/SKILL.md b/.agents/skills/i18n-translate/SKILL.md new file mode 100644 index 0000000..6b17ac3 --- /dev/null +++ b/.agents/skills/i18n-translate/SKILL.md @@ -0,0 +1,314 @@ +--- +name: i18n-translate +description: >- + Complete and maintain frontend i18n translations for this project. Covers + finding missing translation keys, detecting untranslated entries, and adding + translations for all supported locales (en, zh, zh-TW, fr, ja, ru, vi). Use for any + task involving frontend locale files, missing translation keys, untranslated + UI text, `t(...)` keys, `useTranslation()`, static i18n keys, button/label/ + toast/dialog/placeholder/validation copy, or adding/fixing even a single + i18n key. Use when review findings mention missing i18n, when new UI text + needs translation, or when the user asks to add translations, fix i18n, or + complete missing translations. Always load and follow this skill before + translating, adding locale keys, or editing frontend i18n files. +--- + +# Frontend i18n Translation Workflow + +## Mandatory Preflight + +- Read this entire `SKILL.md` before any frontend i18n work, including one-key fixes. +- Before editing locale files, confirm the source text comes from a `t(...)` key, `en.json`, existing UI copy, or an explicitly requested new UI string. +- Use the user conversation only to understand the task target. Do not copy conversation text, review wording, or task descriptions directly into locale values. +- Before translating each key, re-think the intended UI copy from the code and locale context instead of treating the surrounding chat as the translation source. + +### Hard Constraint: Locale Writes Go Through the Script + +- You MUST NOT edit `web/src/i18n/locales/*.json` directly with text-editing tools (StrReplace, Write, search-and-replace, manual JSON edits, etc.). This applies even to a single key. +- ALL locale writes MUST go through the `add-missing-keys.mjs` script, followed by `bun run i18n:sync`. The script is the only sanctioned way to add or change locale values. +- Why this is mandatory, not optional: + - Hand-editing reliably drops one or more of the seven locales (`en`, `zh`, `zh-TW`, `fr`, `ja`, `ru`, `vi`), leaving keys missing in some languages. + - Hand-editing breaks the required alphabetical key order and introduces JSON syntax errors (trailing commas, mismatched quotes). + - The script writes all seven files atomically with consistent sorting, so the locale set stays in sync by construction. +- The script does not do the translation for you. You still must reason out each locale's copy and populate the script's `newKeys` object; the script only handles insertion, sorting, and writing. Do not skip the script just because the thinking happens regardless. + +## Scope Checklist + +Before editing files, treat the task as covered by this skill if it involves: + +- `i18n`, translation, locale files, language packs, missing keys, or untranslated text +- `t('...')`, `useTranslation()`, `static-keys.ts`, or `locales/*.json` +- UI copy in buttons, labels, toasts, dialogs, placeholders, validation messages, descriptions, or table/empty states +- A review finding about missing i18n keys + +Do not skip this workflow because the fix is "just one key". + +## Overview + +- Locale files: `web/src/i18n/locales/{en,zh,zh-TW,fr,ja,ru,vi}.json` +- Format: flat JSON under `"translation"` key, keys are English source strings +- Base locale: `en.json` (most keys), fallback: `zh` (Chinese) +- Sync script: `bun run i18n:sync` (from `web/`) +- All `t()` calls must have corresponding keys in every locale file + +## Small Fix Path + +For a single known missing key (still script-only, no direct JSON edits): + +1. Confirm the exact key at the call site and verify it is absent from all locale files. +2. Add the key via `add-missing-keys.mjs`, populating its `newKeys` object for every supported locale: `en`, `zh`, `zh-TW`, `fr`, `ja`, `ru`, `vi`. Even one key goes through the script; do not hand-edit the JSON. +3. The script preserves the flat `"translation"` object and keeps keys alphabetically sorted automatically. +4. Run a targeted search for the key in code and locale files. +5. Run `bun run i18n:sync` to normalize file order. This step is mandatory, not optional. + +## Workflow + +### Step 1: Run sync and read report + +```bash +cd web && bun run i18n:sync +``` + +Read `web/src/i18n/locales/_reports/_sync-report.json` to see per-locale status (missingCount, extrasCount, untranslatedCount). + +### Step 2: Find missing keys (used in code but not in locale files) + +Create and run `web/scripts/find-missing-keys.mjs`: + +```javascript +import fs from 'node:fs/promises' +import path from 'node:path' + +const LOCALES_DIR = path.resolve('src/i18n/locales') +const SRC_DIR = path.resolve('src') + +const en = JSON.parse(await fs.readFile(path.join(LOCALES_DIR, 'en.json'), 'utf8')) +const enKeys = new Set(Object.keys(en.translation)) + +const tCallRegex = /\bt\(\s*['"`]([^'"`\n]+?)['"`]\s*[,)]/g +const tCallMultilineRegex = /\bt\(\s*['"`]([^'"`]+?)['"`]\s*\)/g + +async function walkDir(dir) { + const files = [] + const entries = await fs.readdir(dir, { withFileTypes: true }) + for (const entry of entries) { + const fullPath = path.join(dir, entry.name) + if (entry.isDirectory()) { + if (['node_modules', '.git', 'locales', '_reports', '_extras'].includes(entry.name)) continue + files.push(...(await walkDir(fullPath))) + } else if (/\.(tsx?|jsx?)$/.test(entry.name)) { + files.push(fullPath) + } + } + return files +} + +const files = await walkDir(SRC_DIR) +const missingKeys = new Map() + +for (const file of files) { + const content = await fs.readFile(file, 'utf8') + const relPath = path.relative(SRC_DIR, file) + for (const regex of [tCallRegex, tCallMultilineRegex]) { + regex.lastIndex = 0 + let match + while ((match = regex.exec(content)) !== null) { + const key = match[1] + if (key.startsWith('{{') || key.includes('${')) continue + if (!enKeys.has(key)) { + if (!missingKeys.has(key)) missingKeys.set(key, []) + missingKeys.get(key).push(relPath) + } + } + } +} + +if (missingKeys.size === 0) { + console.log('All t() keys found in en.json!') +} else { + console.log(`Found ${missingKeys.size} missing keys:\n`) + for (const [key, files] of [...missingKeys.entries()].sort(([a], [b]) => a.localeCompare(b))) { + console.log(` "${key}"`) + for (const f of [...new Set(files)]) console.log(` -> ${f}`) + } +} +``` + +### Step 3: Find untranslated entries (value equals English) + +Create and run `web/scripts/find-untranslated.mjs`: + +```javascript +import fs from 'node:fs/promises' +import path from 'node:path' + +const LOCALES_DIR = path.resolve('src/i18n/locales') +const en = JSON.parse(await fs.readFile(path.join(LOCALES_DIR, 'en.json'), 'utf8')) +const enTrans = en.translation + +// Brand names, URLs, technical terms — skip these +const skipPatterns = [ + /^https?:\/\//, /^smtp\./, /^socks5:/, /^name@/, /^noreply@/, + /^org-/, /^price_/, /^whsec_/, /^edit_this$/, /^my-status$/, + /^_copy$/, /^gpt-/, /^checkout\./, /^footer\./, /^\[?\{/, + /^"default/, /^\/status\//, /^\/your\//, /^example\.com/, + /^AZURE_/, /^AccessKey/, /^OAuth/, /^Client /, /^Webhook URL/, + /^API URL$/, /^Well-Known/, /^Worker URL$/, /^Uptime Kuma/, + /^New API/, /^Baidu V2$/, /^Zhipu V4$/, /^Quota:$/, +] + +const brandNames = new Set([ + 'AIGC2D','Anthropic','API2GPT','Claude','Cloudflare','Cohere','DeepSeek', + 'Discord','DoubaoVideo','FastGPT','Gemini','GitHub','Jimeng','JustSong', + 'LingYiWanWu','LinuxDO','Midjourney','MidjourneyPlus','MiniMax','Mistral', + 'MokaAI','Moonshot','NewAPI','OhMyGPT','Ollama','OpenAI','OpenAIMax', + 'OpenRouter','Passkey','Perplexity','QuantumNous','Replicate','SiliconFlow', + 'Stripe','Submodel','SunoAPI','Telegram','Tencent','Vertex AI','VolcEngine', + 'WeChat','Xinference','Xunfei','AI Proxy','One API', +]) + +const locales = ['fr', 'ja', 'ru', 'zh', 'zh-TW', 'vi'] + +for (const locale of locales) { + const locFile = JSON.parse(await fs.readFile(path.join(LOCALES_DIR, `${locale}.json`), 'utf8')) + const locTrans = locFile.translation + const untranslated = {} + + for (const [key, enVal] of Object.entries(enTrans)) { + const locVal = locTrans[key] + if (locVal === undefined || locVal !== enVal) continue + if (brandNames.has(key)) continue + if (skipPatterns.some(p => p.test(key))) continue + if (typeof enVal === 'string' && enVal.length < 4) continue + if (/[a-zA-Z]{3,}/.test(String(enVal))) untranslated[key] = enVal + } + + const count = Object.keys(untranslated).length + if (count > 0) { + console.log(`\n=== ${locale} (${count} untranslated) ===`) + for (const [k, v] of Object.entries(untranslated)) + console.log(` ${JSON.stringify(k)}: ${JSON.stringify(v)}`) + } else { + console.log(`\n=== ${locale}: all translated ===`) + } +} +``` + +### Step 4: Add translations + +This script is the ONLY sanctioned way to write locale values. You MUST NOT bypass it by hand-filling the JSON files. Create `web/scripts/add-missing-keys.mjs` with this exact structure: + +```javascript +import fs from 'node:fs/promises' +import path from 'node:path' + +const LOCALES_DIR = path.resolve('src/i18n/locales') + +function stableStringify(obj) { + return JSON.stringify(obj, null, 2) + '\n' +} + +const newKeys = { + en: { /* "key": "English value" */ }, + zh: { /* "key": "中文翻译" */ }, + 'zh-TW': { /* "key": "繁體中文翻譯" */ }, + fr: { /* "key": "Traduction française" */ }, + ja: { /* "key": "日本語翻訳" */ }, + ru: { /* "key": "Русский перевод" */ }, + vi: { /* "key": "Bản dịch tiếng Việt" */ }, +} + +async function main() { + let totalAdded = 0 + + for (const [locale, trans] of Object.entries(newKeys)) { + const filePath = path.join(LOCALES_DIR, `${locale}.json`) + const json = JSON.parse(await fs.readFile(filePath, 'utf8')) + + let count = 0 + for (const [key, value] of Object.entries(trans)) { + if (!Object.prototype.hasOwnProperty.call(json.translation, key)) { + json.translation[key] = value + count++ + } else if (json.translation[key] !== value) { + json.translation[key] = value + count++ + } + } + + if (count > 0) { + json.translation = Object.fromEntries( + Object.entries(json.translation).sort(([a], [b]) => a.localeCompare(b)) + ) + await fs.writeFile(filePath, stableStringify(json), 'utf8') + } + + console.log(`${locale}: ${count} translations applied`) + totalAdded += count + } + + console.log(`\nTotal: ${totalAdded} translations applied`) +} + +main().catch((err) => { console.error(err); process.exitCode = 1 }) +``` + +Populate the `newKeys` object with actual translations for each locale. + +### Step 5: Verify and clean up + +```bash +cd web +node scripts/add-missing-keys.mjs # apply translations +node scripts/find-missing-keys.mjs # verify: should say "All t() keys found" +bun run i18n:sync # normalize file order +``` + +Delete temporary scripts after completion. + +## Translation Guidelines + +### Source Text Rules + +- Reconsider every key's UI meaning before translating: component location, user action, placeholder variables, button/label/toast/dialog/validation context, and whether the copy is a noun, command, status, or full sentence. +- Prefer the English key or `en` value as the source text. Use the call site only to clarify meaning, tone, and constraints. +- Do not copy chat messages, review comments, issue descriptions, or task wording as translation text. +- If the source text is unclear, inspect the code and locale files first. Ask the user for exact source copy only when the intended UI text remains ambiguous. + +### Length and Layout Awareness + +- Consider whether translated text may overflow the UI before choosing final wording, especially for buttons, table headers, menu items, labels, toasts, dialog titles, tabs, badges, and empty states. +- For languages that often expand relative to English, especially French, Russian, and Vietnamese, prefer natural but compact wording. +- Do not sacrifice meaning just to shorten text. When the call site has limited space, choose the shortest clear translation that preserves the UI intent. +- For interpolated variables, counts, model names, provider names, quotas, and dates, consider the longest realistic rendered text, not only the translation string itself. + +| Language | Code | Notes | +|----------|------|-------| +| English | en | Base locale, key = value | +| Chinese | zh | Fallback locale, must be complete | +| Traditional Chinese | zh-TW | Use natural Traditional Chinese wording | +| French | fr | Many English cognates are valid (e.g., "Configuration") | +| Japanese | ja | Use katakana for technical loanwords | +| Russian | ru | Use formal register | +| Vietnamese | vi | Use standard Vietnamese | + +**Keep as English (do not translate):** +- Brand/product names (OpenAI, Claude, Gemini, etc.) +- URLs and email placeholders +- Technical identifiers (JSON keys, API paths, model names) +- Code-like strings (gpt-3.5-turbo, price_xxx, etc.) + +**Always translate:** +- UI labels, button text, error messages, descriptions +- Time units (hours, minutes, months, years) +- Action words (Move, Show, Delete, etc.) + +## Key Rules + +1. All scripts run from `web/` directory +2. Use `node scripts/xxx.mjs` (ESM format with top-level await) +3. Sort keys alphabetically when writing locale files +4. Always run `bun run i18n:sync` as the final step +5. Delete temporary scripts after completion +6. The `{{variable}}` placeholders in keys must be preserved in all translations +7. NEVER edit `locales/*.json` directly. Any non-script write to a locale file (StrReplace, Write, manual JSON edit) is non-compliant, including single-key fixes. diff --git a/.agents/skills/shadcn-ui/SKILL.md b/.agents/skills/shadcn-ui/SKILL.md new file mode 100644 index 0000000..7622820 --- /dev/null +++ b/.agents/skills/shadcn-ui/SKILL.md @@ -0,0 +1,105 @@ +--- +name: shadcn-ui +description: >- + Give the assistant project-aware shadcn/ui context: components.json, + composition patterns, CLI, registries, theming, and MCP. Use when working on + web UI, shadcn components, or presets. Overview aligns with + https://ui.shadcn.com/docs/skills.md; full upstream skill text is vendored + under vendor/shadcn/. +--- + + + +# Skills (shadcn/ui) + +Skills give AI assistants project-aware context about shadcn/ui. When used, the assistant knows how to find, install, compose, and customize components using the correct APIs and patterns for your project. + +For example, you can ask: + +- _"Add a login form with email and password fields."_ +- _"Create a settings page with a form for updating profile information."_ +- _"Build a dashboard with a sidebar, stats cards, and a data table."_ +- _"Switch to --preset [CODE]"_ +- _"Can you add a hero from @tailark?"_ + +The skill reads your project's `components.json` and provides your framework, aliases, installed components, icon library, and base library so it can generate correct code on the first try. + +--- + +## Install (ecosystem vs this repo) + +Official install from [Skills — shadcn/ui](https://ui.shadcn.com/docs/skills.md): + +```bash +npx skills add shadcn/ui +``` + +That installs the skill where the `skills` CLI is available. **This repository** keeps the same intent under `.agents/skills/shadcn-ui/` (overview here + **vendored** upstream docs in [`vendor/shadcn/`](./vendor/shadcn/)) and runs the shadcn CLI from the frontend app root: + +```bash +cd web && bunx shadcn@latest info --json +``` + +Learn more about skills at [skills.sh](https://skills.sh). + +--- + +## What's included (and where) + +### Project context + +Run **`shadcn info --json`** (here: `cd web && bunx shadcn@latest info --json`) for framework, Tailwind version, aliases, base (`radix` | `base`), icon library, installed components, and resolved paths. + +### CLI commands + +Full command reference (vendored): [`vendor/shadcn/cli.md`](./vendor/shadcn/cli.md). + +### Theming and customization + +Vendored: [`vendor/shadcn/customization.md`](./vendor/shadcn/customization.md). Live docs: [Theming](https://ui.shadcn.com/docs/theming). + +### Registry authoring + +Not duplicated as a single file in the vendor tree; see [Registry](https://ui.shadcn.com/docs/registry) and `build` in [`vendor/shadcn/cli.md`](./vendor/shadcn/cli.md). + +### MCP server + +Vendored: [`vendor/shadcn/mcp.md`](./vendor/shadcn/mcp.md). Live docs: [MCP Server](https://ui.shadcn.com/docs/mcp). + +--- + +## How it works + +1. **Project detection** — Applies when `components.json` exists (here: `web/components.json`). +2. **Context injection** — Use `shadcn info --json` as ground truth for imports and APIs. +3. **Pattern enforcement** — Use [`vendor/shadcn/rules/`](./vendor/shadcn/rules/) for concrete markup checks; the complete official workflow reference is listed below for deeper CLI, registry, and preset questions. +4. **Component discovery** — `shadcn docs`, `shadcn search`, MCP, or registries — see the official workflow reference and MCP doc when deeper context is needed. + +--- + +## Learn more (web) + +- [CLI](https://ui.shadcn.com/docs/cli) — complements [`vendor/shadcn/cli.md`](./vendor/shadcn/cli.md) +- [Theming](https://ui.shadcn.com/docs/theming) +- [Registry](https://ui.shadcn.com/docs/registry) +- [skills.sh](https://skills.sh) + +--- + +## Vendored upstream bundle (deep rules) + +Snapshot from [shadcn-ui/ui `skills/shadcn`](https://github.com/shadcn-ui/ui/tree/main/skills/shadcn); revision note in [`vendor/shadcn/UPSTREAM.txt`](./vendor/shadcn/UPSTREAM.txt). The upstream workflow is stored as a reference file, with its original skill frontmatter removed, so the vendored copy is not discovered as a second local skill. + +| Doc | Path | +| --- | --- | +| Official shadcn/ui workflow reference | [`vendor/shadcn/official-shadcn-ui-workflow.md`](./vendor/shadcn/official-shadcn-ui-workflow.md) | +| CLI reference | [`vendor/shadcn/cli.md`](./vendor/shadcn/cli.md) | +| Theming / customization | [`vendor/shadcn/customization.md`](./vendor/shadcn/customization.md) | +| MCP | [`vendor/shadcn/mcp.md`](./vendor/shadcn/mcp.md) | +| Forms | [`vendor/shadcn/rules/forms.md`](./vendor/shadcn/rules/forms.md) | +| Composition | [`vendor/shadcn/rules/composition.md`](./vendor/shadcn/rules/composition.md) | +| Icons | [`vendor/shadcn/rules/icons.md`](./vendor/shadcn/rules/icons.md) | +| Styling | [`vendor/shadcn/rules/styling.md`](./vendor/shadcn/rules/styling.md) | +| Base vs Radix | [`vendor/shadcn/rules/base-vs-radix.md`](./vendor/shadcn/rules/base-vs-radix.md) | + +**Workflow:** Prefer this **root** `SKILL.md` for repo paths (`web`, Bun). Read **`vendor/shadcn/official-shadcn-ui-workflow.md`** only when you need the complete official component, registry, or preset workflow. Use **`vendor/shadcn/rules/*.md`** when validating concrete markup. diff --git a/.agents/skills/shadcn-ui/vendor/shadcn/UPSTREAM.txt b/.agents/skills/shadcn-ui/vendor/shadcn/UPSTREAM.txt new file mode 100644 index 0000000..aab92e2 --- /dev/null +++ b/.agents/skills/shadcn-ui/vendor/shadcn/UPSTREAM.txt @@ -0,0 +1,4 @@ +Source: https://github.com/shadcn-ui/ui/tree/56161142f1b83f612462772d18883807b5f0d601/skills/shadcn +Branch: main +Fetched: 2026-04-29 +Local file note: upstream SKILL.md is stored as official-shadcn-ui-workflow.md with its original frontmatter removed to avoid exposing the vendored copy as a separate local skill. diff --git a/.agents/skills/shadcn-ui/vendor/shadcn/cli.md b/.agents/skills/shadcn-ui/vendor/shadcn/cli.md new file mode 100644 index 0000000..7e389a1 --- /dev/null +++ b/.agents/skills/shadcn-ui/vendor/shadcn/cli.md @@ -0,0 +1,276 @@ +# shadcn CLI Reference + +Configuration is read from `components.json`. + +> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag. + +## Contents + +- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build +- Templates: next, vite, start, react-router, astro +- Presets: named, code, URL formats and fields +- Switching presets + +--- + +## Commands + +### `init` — Initialize or create a project + +```bash +npx shadcn@latest init [components...] [options] +``` + +Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step. + +| Flag | Short | Description | Default | +| ----------------------- | ----- | --------------------------------------------------------- | ------- | +| `--template