Skip to main content

elf-i18n

Elf Express 前端多語系(vue-i18n)與中文輸入法(IME)規範:i18n 初始化、locale JSON 結構、key 命名、 `useI18n()` 用法、zh-TW / en 文案、語言切換、缺漏 key 偵測、i18n-ally 設定、IME 組字欄位處理、ant-design-vue 元件語系。 當任務涉及:新增或修改 UI 文字、`t()` / `$t()`、`src/i18n/`、locale JSON、語言切換器、日期 / 數字格式、 i18n-ally 警告、翻譯缺漏、或處理中文輸入(composition 事件、注音 / 倉頡輸入、Enter 送出)時觸發。

소스 정보

저장소
elf-express/skillshare-source
최근 소스 활동
2026년 9월 27일 01:26
감지된 SKILL.md 언어
중국어
스타
0
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
2 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
elf-i18n
description
Elf Express 前端多語系(vue-i18n)與中文輸入法(IME)規範:i18n 初始化、locale JSON 結構、key 命名、 `useI18n()` 用法、zh-TW / en 文案、語言切換、缺漏 key 偵測、i18n-ally 設定、IME 組字欄位處理、ant-design-vue 元件語系。 當任務涉及:新增或修改 UI 文字、`t()` / `$t()`、`src/i18n/`、locale JSON、語言切換器、日期 / 數字格式、 i18n-ally 警告、翻譯缺漏、或處理中文輸入(composition 事件、注音 / 倉頡輸入、Enter 送出)時觸發。
metadata
{"version":"1.0.0","owner":"Elf Express"}
# Elf Express i18n 與 IME 規範 > 事實來源:參考專案 `TypingTrainer/apps/src/i18n/`(vue-i18n 10、`legacy: false`、`en` / `zh-TW` / `zh-CN` JSON)、 > `docs/architecture.md`(copy vs data)、`CLAUDE.md` 與 `.github/workflows/claude-review.yml`(IME 規則)。 > 完整範本:[`references/setup.md`](references/setup.md)。 相關 skill: - `frontend/vue/vue3-i18n-debugging`(第三方)— i18n-ally 誤報、worktree 重複警告、`this.$t` 錯誤的**除錯流程**;本檔不重複。 - `frontend/i18n-localization`(第三方)— 通用 i18n 觀念、RTL;本檔只規定團隊硬規則。 - `elf-vue`(SFC / store 寫法)、`elf-ui-pattern`(aria 文字、表單)、`elf-e2e`(IME / focus 測試)、`elf-api-contract`(API 資料的語系參數)。 --- ## 1. 何時使用 - 畫面上新增 / 修改任何使用者看得到的文字(含 `aria-label`、`placeholder`、`title`、toast、錯誤訊息) - 建立或修改 `src/i18n/index.ts`、`src/i18n/locales/*.json` - 語言切換器、`<html lang>`、日期 / 數字 / 百分比顯示 - 撰寫處理中文輸入的欄位或 `keydown` / Enter 處理 - 出現 i18n-ally 警告或畫面顯示原始 key --- ## 2. 固定規則 ### 2.1 設定(MUST) 1. **MUST** 使用 `vue-i18n` Composition 模式:`createI18n({ legacy: false, ... })`,實例放在 `src/i18n/index.ts` 並具名匯出 `i18n`。 2. **MUST** 語系代碼用 BCP 47,大小寫固定:`en`、`zh-TW`、`zh-CN`;locale 檔名與代碼完全一致(`zh-TW.json`)。 3. **MUST** 以 `export const LOCALES = [...] as const` 定義支援清單,`type Locale = (typeof LOCALES)[number]`;其他地方 **MUST NOT** 寫死語系字串陣列。 4. **MUST** `fallbackLocale: 'en'`,且 `en.json` 為**來源語系**(source of truth):新 key 先加 `en.json`,再補其他語系。缺翻譯時畫面顯示英文,而不是原始 key。 5. **MUST** 初始語系:`localStorage` 已存值且在 `LOCALES` 內 → 使用;否則用 `DEFAULT_LOCALE`(值見待確認 #1)。`localStorage` 存取 **MUST** 包 `try/catch`(無痕視窗會丟例外)。 6. **MUST** 切換語系只透過 `setLocale(locale)`:同時設定 `i18n.global.locale.value`、`document.documentElement.lang`、寫入 `localStorage`。**MUST** 在啟動時也設定一次 `<html lang>`。 7. **MUST** 設定頁的語言選擇器是**唯一寫入者**:settings store 持有 `uiLang`,`watch(uiLang, setLocale)`;其他元件 **MUST NOT** 直接改 `i18n.global.locale`。 8. **MUST** 開發環境開啟缺漏警告(`missingWarn` / `fallbackWarn` 在 `import.meta.env.DEV` 為 true),正式環境關閉。 ### 2.2 Locale 檔案結構(MUST) 9. **MUST** 位置:`src/i18n/locales/<locale>.json`,巢狀 JSON;**MUST NOT** 使用扁平 `"a.b.c"` 字串 key 或 YAML。 10. **MUST** 第一層為功能命名空間:`common`(跨頁共用:取消 / 儲存 / 關閉 / 載入中)、`nav`、`shell`(殼層)、`page`(頁首標題 / 副標)、`status`、`form`,其餘每個 view 一個(`practice`、`library`、`settings`…)。 11. **MUST** key 為語意名稱、camelCase、最多 3 層:`library.import.title`、`settings.update.checking`。**MUST NOT** 用中文或英文原句當 key(`"儲存"`、`"Save changes"`)。 12. **MUST** 所有語系檔 key 集合完全相同、每個 key 的插值參數相同;由 `locales.test.ts` 強制(範本見 references)。 13. **MUST** 插值用具名參數 `{name}`:`"streak": "連續 {days} 天"`。**MUST NOT** 在程式中串接翻譯片段(`t('a') + value + t('b')`)— 語序因語言而異,整句放一個 key。 14. **MUST** 英文複數用 vue-i18n 管線語法 `"items": "no items | one item | {count} items"`,呼叫 `t('library.items', count)`;中文同一個 key 可只寫一種形式。 15. **MUST** JSON 由 Prettier 格式化(2 空格),key 順序:新增 key 放在同命名空間末尾,三個語系檔順序一致。 ### 2.3 使用方式(MUST) 16. **MUST** 在 `<script setup>` 用 `const { t } = useI18n()`,template 也用同一個 `t(...)`。**MUST NOT** 使用 `this.$t`(`<script setup>` 沒有 `this`);新程式 **MUST NOT** 在 template 用 `$t`(統一 `t`)。 17. **MUST** 翻譯在元件內做。store / `lib/` / `api/` **MUST** 回傳 key(例:`statusKey: 'ready'`、`speedUnitKey: 'wpm'`),**MUST NOT** 回傳翻譯後字串 — store 不會因切換語系重算。 18. 非元件情境確實需要翻譯(例:router guard 設定 `document.title`)時,**MUST** 用 `i18n.global.t(...)`,且在語系切換時重新計算。 19. **MUST** 動態 key 用「完整 key 對照表」而非字串拼接:`const STATUS_LABEL: Record<Status, string> = { ready: 'status.ready', running: 'status.running' }` → `t(STATUS_LABEL[s])`。**MUST NOT** 以任何形式組 key:`` t(`status.${s}`) ``、`t('status.' + s)`、`t(prefix + key)` 一律禁止(拼出來的 key i18n-ally 與 grep 找不到、typo 不會報錯;改用 `Record<Status, string>` 對照表時,少一格 `vue-tsc` 就會直接報錯)。清單資料(導覽、選項)帶完整 key 欄位(`labelKey: 'nav.practice'`)。此規則無例外,`elf-vue` / `elf-ui-pattern` 範本皆遵守。 20. **MUST** 所有 aria 文字、`placeholder`、`title`、`alt`、`document.title` 走 i18n。 21. 快捷鍵、單位(`WPM`、`CPM`)、品牌名稱也 **MUST** 放在 locale 檔(可同值),以便日後調整。 ### 2.4 Copy 與 Data 的分界(MUST) 22. **Copy**(出貨的 UI 文字、內建選項名稱)**MUST** 放在前端 locale JSON。 23. **Data**(使用者建立的分類 / 文字、後端資料)**MUST NOT** 放 locale JSON;原樣顯示使用者輸入的內容。內建資料列若需翻譯,由後端帶 `i18nKey` 欄位,前端 `t(item.i18nKey)`;否則顯示 `item.name`。 24. 後端提供的目錄型資料(說明文字、提示詞名稱)**MUST** 以 API 帶語系參數取得(參考專案:`GET /prompts?locale=zh-TW`),格式依 `elf-api-contract`。 ### 2.5 zh-TW / en 文案(MUST) 25. **MUST** zh-TW 使用台灣用語:儲存(非保存)、設定(非設置)、資料(非數據,統計類「數據」除外依產品決定)、檔案(非文件)、預設(非默認)、登入(非登錄)、網路(非網絡)、程式(非程序)。 26. **MUST** zh-TW 使用全形標點(,。:;!?「」()),中文與英文 / 數字之間加半形空格(`AI 設定`、`連續 {days} 天`)。 27. **MUST** en 使用句首大寫(sentence case):`"Pin sidebar"`,不用 Title Case;按鈕為動詞開頭。 28. **MUST NOT** 以機器翻譯直接覆蓋既有人工翻譯;zh-CN 由 zh-TW 轉換時須經人工檢查(工具見待確認 #3)。 ### 2.6 中文輸入法(IME)(MUST) 29. **MUST NOT** 在由 `compositionstart` / `compositionupdate` / `compositionend` 驅動的非受控輸入欄位上綁定 `v-model` 或 `:value`。重新渲染時 Vue 會寫回 `value`,**清掉瀏覽器的組字緩衝**,使用者打到一半的字會消失。 30. **MUST** 非受控 IME 欄位的處理順序: - `compositionstart` → 標記 `composing = true` - `compositionupdate` → 只更新「組字中」顯示(例:注音泡泡),不比對、不送出 - `compositionend` → `composing = false`,讀 `(e.target as HTMLInputElement).value` 並處理 - `input` → `composing` 為 true 時直接 return 31. **MUST** 需要清空欄位時,只在 `!composing` 時以 `el.value = ''` 直接操作 DOM。 32. **MUST** 任何 `keydown` 的 Enter / 快捷鍵處理先 `if (e.isComposing) return`(選字的 Enter 不能觸發送出)。 33. 一般表單的 `<input v-model>` 可照常使用 — Vue 的 `v-model` 本身會等 `compositionend` 才更新;規則 29 只針對自行處理 composition 事件的欄位。 34. **MUST** 中文模式下 IME 欄位全程持有 focus(見 `elf-ui-pattern` 規則 19);英文練習直接讀 `keydown`,不經 IME 欄位。 35. **MUST** IME 與 focus 行為以 Playwright 測試(派發 `CompositionEvent`),jsdom 單元測試不能證明其正確。 ### 2.7 測試(MUST) 36. **MUST** 有 `src/i18n/locales.test.ts`:比對所有語系 key 集合與插值參數,缺一個即失敗。 37. **MUST** 元件單元測試以 `en` 斷言(`i18n.global.locale.value = 'en'`)。 38. **MUST** e2e 斷言英文文案:全新 Playwright context 無 localStorage,初始語系為預設值;**MUST NOT** 用「某中文字串不存在」當斷言 — 在英文畫面下它永遠成立(參考專案曾有兩個此類斷言無法失敗)。若預設語系改為 zh-TW,e2e 須先 `addInitScript` 寫入語系。 ### 2.8 ant-design-vue 元件語系(MUST) 39. **MUST** ant-design-vue 內建文字由 `<a-config-provider :locale>` 提供,值取自完整對照表 `ANTD_LOCALE: Record<Locale, AntdLocale>`,並隨 settings store 的 `uiLang` 切換(範本見 references §9)。**MUST NOT** 讓函式庫停在預設英文而頁面是中文。 40. **MUST** `setLocale` 同時切換 dayjs 語系(ant-design-vue 4 的日期元件使用 dayjs);新增語系時 `ANTD_LOCALE`、dayjs 對照、`LOCALES` 三處同 PR 更新。 --- ## 3. 標準範本 完整範本在 [`references/setup.md`](references/setup.md):`i18n/index.ts`、三個 locale JSON 片段、settings store 接線、 語言選擇器、`locales.test.ts`、`.vscode/settings.json`(i18n-ally)、IME 欄位完整範例、e2e IME 測試、ant-design-vue / dayjs 語系同步。 ### 3.1 元件中使用 ```vue <script setup lang="ts"> import { computed } from 'vue' import { useI18n } from 'vue-i18n' import { useSessionStore } from '@/stores/session' type SpeedUnit = 'wpm' | 'cpm' const UNIT_LABEL: Record<SpeedUnit, string> = { wpm: 'status.wpm', cpm: 'status.cpm' } const { t } = useI18n() const session = useSessionStore() const unitLabel = computed(() => t(UNIT_LABEL[session.speedUnit])) </script> <template> <span class="label">{{ unitLabel }}</span> <span class="value nums">{{ session.speed }}</span> <span>{{ t('shell.streak', { days: session.streakDays }) }}</span> </template> ``` ### 3.2 Locale JSON 片段 `en.json` ```json { "common": { "cancel": "Cancel", "save": "Save", "loading": "Loading…", "retry": "Retry" }, "shell": { "streak": "{days}-day streak", "toggleSidebar": "Toggle sidebar" }, "library": { "items": "no texts | one text | {count} texts" } } ``` `zh-TW.json` ```json { "common": { "cancel": "取消", "save": "儲存", "loading": "載入中…", "retry": "重試" }, "shell": { "streak": "連續 {days} 天", "toggleSidebar": "收合/展開側欄" }, "library": { "items": "{count} 篇文本" } } ``` ### 3.3 IME 欄位(最小版) ```vue <input ref="imeRef" class="ime" :aria-label="t('practice.imeField')" autocomplete="off" @compositionstart="onCompStart" @compositionupdate="onCompUpdate" @compositionend="onCompEnd" @input="onInput" /> <!-- No v-model, no :value. The field is uncontrolled on purpose. --> ``` --- ## 4. 檢查清單 - [ ] 新文字已加到 `en.json`,且 `zh-TW.json`(與其他語系)同位置同 key - [ ] key 為語意 camelCase、≤ 3 層、放在正確命名空間 - [ ] 沒有翻譯片段串接;插值參數在各語系一致 - [ ] `<script setup>` 用 `useI18n()` 的 `t`,沒有 `this.$t` - [ ] store / lib 回傳 key 而非翻譯字串 - [ ] 沒有 `` t(`xxx.${var}`) `` / `t('xxx.' + var)`;動態 key 走完整 key 對照表(`` grep -rnE "\bt\((\`|'[^']*' *\+)" apps/src `` 應無結果) - [ ] 切換語系後 ant-design-vue 內建文字(Modal 按鈕、Empty、分頁)也跟著切換 - [ ] 使用者資料沒有被塞進 locale JSON - [ ] zh-TW 為台灣用語、全形標點、中英間半形空格 - [ ] IME 欄位沒有 `v-model` / `:value`;Enter 處理有 `isComposing` 防護 - [ ] `pnpm --filter ./apps test`(含 `locales.test.ts`)通過 - [ ] e2e 斷言英文,沒有「中文不存在」式斷言 --- ## 5. 常見錯誤 | 錯誤寫法 | 正確寫法 | 原因 | | --- | --- | --- | | `this.$t('save')` in `<script setup>` | `const { t } = useI18n(); t('common.save')` | 沒有 `this` | | `createI18n({ ... })` 未設 `legacy: false` | `legacy: false` | `useI18n()` 在 legacy 模式會丟錯 | | `fallbackLocale: 'en'` 但 messages 的 key 是 `'en-US'` | 兩者代碼一致 | fallback 找不到語系,顯示原始 key | | `t('practice.useFinger', …) + t('practice.pressKey', …)`(參考專案) | 整句一個 key:`"hint": "用{finger}按 {key} 鍵"` | 語序無法在他語系調整 | | `` t(`nav.${n.id}`) ``(參考專案)、`t('status.' + s)` | `labelKey: 'nav.practice'` 完整 key;狀態用 `Record<Status, string>` 對照表 | 工具找不到、typo 無警告 | | 頁面切到 zh-TW,`a-modal` 按鈕仍是 OK / Cancel | `a-config-provider :locale="ANTD_LOCALE[uiLang]"` + dayjs 同步 | 函式庫文字不走 vue-i18n | | store 回傳 `t('status.ready')` | store 回傳 `'ready'`,元件翻譯 | 切換語系不更新 | | 把使用者建立的分類名放進 `zh-TW.json` | 顯示 `item.name`;內建項目用 `item.i18nKey` | 建置後新增的資料不可能在 bundle 內 | | `<input v-model="text" @compositionend=…>` | 非受控,`compositionend` 讀 `e.target.value` | 重繪清掉組字緩衝 | | Enter 送出不檢查 `isComposing` | `if (e.isComposing) return` | 選字 Enter 直接送出半成品 | | e2e `expect(page.getByText('開始')).toHaveCount(0)` | 斷言英文 `'Start'` 存在 | 英文畫面下永遠通過 | | 用 i18n-ally 警告判斷 key 是否存在 | 以 `locales.test.ts` / node 讀 JSON 為準 | 擴充套件有快取(見 `vue3-i18n-debugging`) | | `setLocale` 只改 `locale`,沒改 `<html lang>` | 同時設 `document.documentElement.lang` | 螢幕閱讀器發音、瀏覽器翻譯判斷錯誤 | --- ## 6. 待確認 1. **預設語系**:參考專案無儲存值時用 `en`;SoybeanAdmin 系(`platform.client`)預設 `zh-TW`。團隊預設(`DEFAULT_LOCALE`)待定。若改為 `zh-TW`,e2e 規則 38 的作法需同步調整。 2. **必備語系**:團隊要求 `zh-TW` + `en`;`zh-CN` 是否為必備(參考專案三語皆有)待定。
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기