feat(phase6-9): Complete modular architecture and Agent Teams

Phase 6.4 - Modular Architecture:
- Add lewooogo-brain adapters for LLM providers
- Add lewooogo-data dual memory (Redis + PostgreSQL)
- Implement consensus engine for multi-agent decisions
- Add incident memory service for historical context

Phase 9 - Agent Teams (Claude Agent SDK):
- Add base agent class with Claude Sonnet 4 integration
- Implement action planner, blast radius, and security agents
- Add agent API endpoints and proposal workflow
- Integrate ADR-009 OpenClaw Agent Teams architecture

DevOps & CI/CD:
- Add GitHub Actions CI/CD workflows (ci.yaml, cd.yaml)
- Add pre-commit hooks and secrets baseline
- Add docker-compose for local development
- Update Kubernetes network policies

Frontend Improvements:
- Add auto-healing error boundary component
- Update i18n messages for agent features
- Enhance dual-state incident card with execution feedback

Documentation:
- Add 7 ADRs covering MCP, design system, architecture decisions
- Update ARCHITECTURE_MEMORY.md with modular design
- Add GLOBAL_RULES.md and SOUL.md for project identity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-23 18:40:36 +08:00
parent 6eccb45757
commit 7478dc0254
169 changed files with 24613 additions and 247 deletions

View File

@@ -0,0 +1,655 @@
# AWOOOI 原子組件庫規格
> **版本**: v1.0
> **建立日期**: 2026-03-20
> **負責人**: CPO
> **設計系統**: Nothing.tech 純白工業風
---
## 概述
本文件定義 AWOOOI 前端組件庫的設計規格,採用 Atomic Design 原則,確保視覺一致性與開發效率。
---
## 設計 Token
### 色彩系統
```typescript
// packages/lewooogo-ui/src/tokens/colors.ts
export const colors = {
// 基底色 (Pure White Base)
white: '#FFFFFF',
snow: '#FAFAFA', // 主背景
cloud: '#F5F5F5', // 次背景/卡片
mist: '#E5E5E5', // 邊框/分隔線
// 文字色 (High Contrast)
ink: '#0A0A0A', // 主文字
gray: {
600: '#6B7280', // 次文字
400: '#9CA3AF', // 輔助文字
200: '#E5E7EB', // 禁用文字
},
// 功能色
status: {
success: '#10B981',
warning: '#F59E0B',
error: '#EF4444',
info: '#3B82F6',
thinking: '#8B5CF6', // AI 思考中
},
// 品牌色
brand: {
primary: '#FF6B35', // AWOOOI 橘
nothingRed: '#D71921', // Nothing 品牌紅
},
} as const;
```
### 間距系統
```typescript
// packages/lewooogo-ui/src/tokens/spacing.ts
export const spacing = {
0: '0',
1: '4px',
2: '8px',
3: '12px',
4: '16px',
5: '20px',
6: '24px',
8: '32px',
10: '40px',
12: '48px',
16: '64px',
} as const;
```
### 字體系統
```typescript
// packages/lewooogo-ui/src/tokens/typography.ts
export const typography = {
fontFamily: {
display: '"NDot", monospace', // AI 介面/數字
body: '"Inter", system-ui', // 一般文字
mono: '"JetBrains Mono", monospace', // 程式碼
},
fontSize: {
xs: '12px',
sm: '14px',
base: '16px',
lg: '18px',
xl: '20px',
'2xl': '24px',
'3xl': '30px',
'4xl': '36px',
},
fontWeight: {
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
},
lineHeight: {
tight: 1.25,
normal: 1.5,
relaxed: 1.75,
},
} as const;
```
### 效果系統
```typescript
// packages/lewooogo-ui/src/tokens/effects.ts
export const effects = {
// 白玻璃效果 (White Glassmorphism)
glass: {
background: 'rgba(255, 255, 255, 0.7)',
blur: 'blur(20px)',
border: 'rgba(0, 0, 0, 0.05)',
},
// 點陣紋理 (Dot Matrix)
dotMatrix: {
pattern: 'radial-gradient(circle, rgba(0, 0, 0, 0.03) 1px, transparent 1px)',
size: '16px 16px',
},
// 陰影
shadow: {
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
md: '0 4px 6px rgba(0, 0, 0, 0.05)',
lg: '0 10px 15px rgba(0, 0, 0, 0.05)',
glow: '0 0 20px rgba(255, 107, 53, 0.2)', // 品牌光暈
},
// 圓角
radius: {
sm: '4px',
md: '8px',
lg: '12px',
xl: '16px',
full: '9999px',
},
// 過渡
transition: {
fast: '150ms ease',
normal: '250ms ease',
slow: '350ms ease',
},
} as const;
```
---
## Atoms (原子組件)
### StatusOrb - 狀態呼吸燈
```tsx
// packages/lewooogo-ui/src/atoms/StatusOrb.tsx
interface StatusOrbProps {
status: 'healthy' | 'warning' | 'critical' | 'unknown' | 'thinking';
size?: 'sm' | 'md' | 'lg';
pulse?: boolean;
label?: string;
}
/**
* 狀態呼吸燈
* - 即時反映系統/主機狀態
* - 支援脈衝動畫 (告警/思考中)
*
* @example
* <StatusOrb status="healthy" size="md" />
* <StatusOrb status="thinking" pulse label="AI 處理中" />
*/
```
**視覺規格**:
| Size | 直徑 | 光暈半徑 |
|------|------|---------|
| sm | 8px | 12px |
| md | 12px | 18px |
| lg | 16px | 24px |
**狀態色彩**:
| Status | 色彩 | 脈衝 |
|--------|------|------|
| healthy | `#10B981` | 無 |
| warning | `#F59E0B` | 慢 (2s) |
| critical | `#EF4444` | 快 (0.5s) |
| unknown | `#9CA3AF` | 無 |
| thinking | `#8B5CF6` | 中 (1s) |
---
### MetricValue - 數值顯示
```tsx
// packages/lewooogo-ui/src/atoms/MetricValue.tsx
interface MetricValueProps {
value: number | string;
unit?: string;
trend?: 'up' | 'down' | 'stable';
trendValue?: string;
size?: 'sm' | 'md' | 'lg' | 'xl';
format?: 'number' | 'percent' | 'bytes' | 'duration';
}
/**
* 數值顯示組件
* - NDot 字體呈現數字
* - 支援趨勢箭頭與變化值
*
* @example
* <MetricValue value={99.9} unit="%" trend="up" trendValue="+0.1%" />
* <MetricValue value={1024} format="bytes" /> // 顯示 "1 KB"
*/
```
**視覺規格**:
| Size | 字體大小 | 權重 |
|------|---------|------|
| sm | 18px | 500 |
| md | 24px | 600 |
| lg | 36px | 700 |
| xl | 48px | 700 |
---
### IconButton - 圖示按鈕
```tsx
// packages/lewooogo-ui/src/atoms/IconButton.tsx
interface IconButtonProps {
icon: ReactNode;
variant?: 'ghost' | 'outline' | 'solid';
size?: 'sm' | 'md' | 'lg';
color?: 'default' | 'primary' | 'danger';
disabled?: boolean;
loading?: boolean;
tooltip?: string;
onClick?: () => void;
}
/**
* 圖示按鈕
* - 用於工具列、操作區
* - 必須有 tooltip 說明
*/
```
---
### Badge - 標籤徽章
```tsx
// packages/lewooogo-ui/src/atoms/Badge.tsx
interface BadgeProps {
children: ReactNode;
variant?: 'solid' | 'outline' | 'subtle';
color?: 'gray' | 'green' | 'yellow' | 'red' | 'blue' | 'purple' | 'orange';
size?: 'sm' | 'md';
dot?: boolean;
}
/**
* 標籤徽章
* - 用於狀態標示、分類
*
* @example
* <Badge color="green">Active</Badge>
* <Badge color="red" dot>3 Alerts</Badge>
*/
```
---
## Molecules (分子組件)
### GlassCard - 玻璃卡片
```tsx
// packages/lewooogo-ui/src/molecules/GlassCard.tsx
interface GlassCardProps {
children: ReactNode;
variant?: 'default' | 'elevated' | 'bordered';
padding?: 'sm' | 'md' | 'lg';
interactive?: boolean;
selected?: boolean;
onClick?: () => void;
}
/**
* 白玻璃卡片
* - Nothing.tech 核心視覺元素
* - 支援點擊交互與選中狀態
*
* CSS:
* - background: rgba(255, 255, 255, 0.7)
* - backdrop-filter: blur(20px)
* - border: 1px solid rgba(0, 0, 0, 0.05)
*/
```
**視覺規格**:
| Variant | 背景 | 邊框 | 陰影 |
|---------|------|------|------|
| default | glass | subtle | sm |
| elevated | glass | subtle | lg |
| bordered | white | solid | none |
---
### HostCard - 主機卡片
```tsx
// packages/lewooogo-ui/src/molecules/HostCard.tsx
interface HostCardProps {
host: {
id: string;
name: string;
ip: string;
role: string;
status: 'healthy' | 'warning' | 'critical' | 'unknown';
metrics?: {
cpu: number;
memory: number;
disk: number;
};
lastSeen?: string;
};
variant?: 'compact' | 'detailed';
showMetrics?: boolean;
onClick?: () => void;
}
/**
* 主機狀態卡片
* - 戰情室核心組件
* - 整合 StatusOrb + MetricValue
*
* @example
* <HostCard host={hostData} variant="detailed" showMetrics />
*/
```
**佈局**:
```
┌──────────────────────────────────────┐
│ ● web-server-01 [healthy] │
│ 192.168.0.188 · AI+Web 中心 │
├──────────────────────────────────────┤
│ CPU Memory Disk │
│ 45% 72% 58% │
│ ████░ ███████░ █████░ │
└──────────────────────────────────────┘
```
---
### AlertPanel - 告警面板
```tsx
// packages/lewooogo-ui/src/molecules/AlertPanel.tsx
interface AlertPanelProps {
alerts: Alert[];
maxVisible?: number;
showTimestamp?: boolean;
onAlertClick?: (alert: Alert) => void;
onAcknowledge?: (alertId: string) => void;
}
interface Alert {
id: string;
severity: 'info' | 'warning' | 'critical';
title: string;
message: string;
source: string;
timestamp: string;
acknowledged?: boolean;
}
/**
* 告警列表面板
* - 即時更新 (SSE)
* - 支援確認操作
*/
```
---
### ApprovalCard - HITL 審批卡片
```tsx
// packages/lewooogo-ui/src/molecules/ApprovalCard.tsx
interface ApprovalCardProps {
approval: {
id: string;
type: 'deploy' | 'rollback' | 'config' | 'security';
title: string;
description: string;
requester: string;
blastRadius: 'low' | 'medium' | 'high' | 'critical';
signaturesRequired: number;
signaturesCollected: Signature[];
expiresAt: string;
aiSummary?: string;
aiConfidence?: number;
};
currentUser: string;
onApprove?: () => void;
onReject?: () => void;
onRequestInfo?: () => void;
}
/**
* Human-In-The-Loop 審批卡片
* - 顯示 Blast Radius 風險等級
* - 支援 Multi-Sig 簽核進度
* - AI 摘要與信心度
*/
```
**佈局**:
```
┌──────────────────────────────────────────────────┐
│ 🚀 部署請求: web-api v2.3.1 │
│ ════════════════════════════════════════════════ │
│ │
│ 📊 Blast Radius: ████████░░ HIGH │
│ 影響: 3 服務 · 12 Pods · ~5000 用戶 │
│ │
│ 🤖 AI 摘要: 此更新包含 API 破壞性變更,建議 │
│ 先通知下游服務團隊。信心度: 87% │
│ │
│ ✍️ 簽核進度: 1/2 │
│ ✅ CTO (2026-03-20 10:30) │
│ ⏳ CISO │
│ │
│ [詢問更多] [拒絕] [批准] │
└──────────────────────────────────────────────────┘
```
---
## Organisms (組織組件)
### CommandPalette - 快捷命令面板
```tsx
// packages/lewooogo-ui/src/organisms/CommandPalette.tsx
interface CommandPaletteProps {
isOpen: boolean;
onClose: () => void;
commands: Command[];
recentCommands?: string[];
onCommandSelect: (command: Command) => void;
}
interface Command {
id: string;
label: string;
description?: string;
icon?: ReactNode;
shortcut?: string;
category: 'navigation' | 'action' | 'ai' | 'settings';
action: () => void;
}
/**
* Cmd+K 快捷命令面板
* - 全站快速導航與操作
* - 支援模糊搜尋
* - 顯示快捷鍵提示
*/
```
**快捷鍵**:
| 快捷鍵 | 功能 |
|-------|------|
| `Cmd+K` | 開啟面板 |
| `Esc` | 關閉面板 |
| `↑/↓` | 選擇項目 |
| `Enter` | 執行命令 |
---
### ThinkingTerminal - AI 思考終端
```tsx
// packages/lewooogo-ui/src/organisms/ThinkingTerminal.tsx
interface ThinkingTerminalProps {
isOpen: boolean;
stream: ThinkingStream | null;
position?: 'bottom' | 'right';
collapsible?: boolean;
}
interface ThinkingStream {
status: 'idle' | 'thinking' | 'completed' | 'error';
steps: ThinkingStep[];
result?: string;
error?: string;
}
interface ThinkingStep {
id: string;
type: 'input' | 'process' | 'output' | 'tool_call';
content: string;
timestamp: string;
duration?: number;
}
/**
* AI 思考過程終端
* - SSE 即時串流
* - 打字機效果
* - 支援摺疊
*/
```
---
### DataPincer - 數據鉗視覺化
```tsx
// packages/lewooogo-ui/src/organisms/DataPincer.tsx
interface DataPincerProps {
data: {
hosts: HostData[];
connections: Connection[];
flows: DataFlow[];
};
viewMode?: '2d' | '3d';
interactive?: boolean;
highlightHost?: string;
onHostClick?: (hostId: string) => void;
}
/**
* 數據鉗視覺化組件
* - AWOOOI 品牌視覺符號
* - 四主機架構拓撲圖
* - 即時資料流動畫
*/
```
---
## Templates (模板)
### DashboardLayout - 儀表板佈局
```tsx
// packages/lewooogo-ui/src/templates/DashboardLayout.tsx
interface DashboardLayoutProps {
children: ReactNode;
sidebar?: ReactNode;
header?: ReactNode;
background?: 'default' | 'dotMatrix';
}
/**
* 儀表板頁面佈局
* - 支援側邊欄
* - 點陣背景紋理
* - 響應式設計
*/
```
**斷點**:
| 斷點 | 寬度 | 佈局 |
|------|------|------|
| mobile | < 768px | 單欄 + 抽屜選單 |
| tablet | 768-1024px | 摺疊側欄 |
| desktop | 1024-1440px | 展開側欄 |
| wide | > 1440px | 展開側欄 + 更多欄位 |
---
## 無障礙規範 (WCAG 2.1 AA)
### 對比度要求
| 元素 | 最小對比度 | 實際值 |
|------|-----------|--------|
| 正文文字 | 4.5:1 | 17.3:1 (ink/snow) |
| 大標題 | 3:1 | 17.3:1 |
| 非文字元素 | 3:1 | 符合 |
| 狀態色 (綠) | 3:1 | 4.5:1 (success/snow) |
### 互動元素
- 所有可點擊元素必須有 `focus-visible` 樣式
- 鍵盤可操作 (Tab 導航)
- 適當的 `aria-label`
- 螢幕閱讀器支援
### Focus 樣式
```css
.focus-visible {
outline: 2px solid var(--brand-primary);
outline-offset: 2px;
}
```
---
## 組件狀態
| 組件 | 狀態 | 負責人 |
|------|------|--------|
| StatusOrb | ⏳ 待開發 | CPO |
| MetricValue | ⏳ 待開發 | CPO |
| IconButton | ⏳ 待開發 | CPO |
| Badge | ⏳ 待開發 | CPO |
| GlassCard | ⏳ 待開發 | CPO |
| HostCard | ⏳ 待開發 | CPO |
| AlertPanel | ⏳ 待開發 | CPO |
| ApprovalCard | ⏳ 待開發 | CPO |
| CommandPalette | ⏳ 待開發 | CPO |
| ThinkingTerminal | ⏳ 待開發 | CPO |
| DataPincer | ⏳ 待開發 | CPO |
| DashboardLayout | ⏳ 待開發 | CPO |
---
## 變更記錄
| 日期 | 版本 | 變更 | 作者 |
|------|------|------|------|
| 2026-03-20 | v1.0 | 初版建立 | CPO |
---
*此文件由 CPO 維護,前端開發者必須遵守此規格。*

View File

@@ -0,0 +1,425 @@
# AWOOOI i18n 字典檔結構規範
> **版本**: v1.0
> **建立日期**: 2026-03-20
> **負責人**: CPO
> **框架**: next-intl
---
## 概述
> 🎯 **顧問深度討論 #4**: 語意化 Key 命名學
此文件定義 AWOOOI 國際化字典檔的結構規範,採用語意化樹狀命名,
確保 500+ 個翻譯項目在工程師與翻譯員之間有明確的上下文。
---
## 命名規範
### Key 命名格式
```
[頁面].[組件].[元素]_[動作/狀態]
```
### 正確與錯誤範例
```json
// ❌ 錯誤: 無上下文,容易混淆
{
"approve": "批准",
"cancel": "取消",
"error": "發生錯誤",
"warning": "警告"
}
// ✅ 正確: 語意化命名,清楚上下文
{
"dashboard": {
"approval_card": {
"btn_approve": "批准執行",
"btn_reject": "拒絕",
"btn_request_info": "詢問更多",
"label_blast_radius": "爆炸半徑",
"status_pending": "等待簽核",
"status_approved": "已批准"
}
}
}
```
---
## 字典檔結構
### 目錄結構
```
apps/web/messages/
├── zh-TW.json # 繁體中文 (主要)
└── en.json # English
```
### 頂層分類
```json
{
"common": {}, // 共用元素 (按鈕、狀態、錯誤)
"layout": {}, // 佈局元素 (導航、側邊欄、頁尾)
"dashboard": {}, // 戰情室頁面
"monitor": {}, // 監控模組
"security": {}, // 安全模組
"deploy": {}, // 部署模組
"tickets": {}, // 工單模組
"billing": {}, // 帳單模組
"settings": {}, // 設定模組
"ai_copilot": {}, // AI 助手
"errors": {}, // 錯誤訊息
"validation": {} // 表單驗證
}
```
---
## 完整字典檔範本
### zh-TW.json
```json
{
"common": {
"btn": {
"save": "儲存",
"cancel": "取消",
"confirm": "確認",
"delete": "刪除",
"edit": "編輯",
"view": "查看",
"back": "返回",
"next": "下一步",
"previous": "上一步",
"submit": "送出",
"reset": "重設",
"search": "搜尋",
"filter": "篩選",
"export": "匯出",
"import": "匯入",
"refresh": "重新整理"
},
"status": {
"loading": "載入中...",
"success": "成功",
"error": "失敗",
"pending": "處理中",
"active": "啟用",
"inactive": "停用",
"healthy": "健康",
"warning": "警告",
"critical": "嚴重",
"unknown": "未知"
},
"time": {
"just_now": "剛剛",
"minutes_ago": "{count} 分鐘前",
"hours_ago": "{count} 小時前",
"days_ago": "{count} 天前",
"today": "今天",
"yesterday": "昨天"
},
"pagination": {
"page": "第 {current} 頁,共 {total} 頁",
"showing": "顯示 {from}-{to},共 {total} 筆",
"per_page": "每頁顯示"
}
},
"layout": {
"nav": {
"dashboard": "戰情室",
"monitor": "監控",
"security": "安全",
"deploy": "部署",
"tickets": "工單",
"billing": "帳單",
"settings": "設定"
},
"sidebar": {
"collapse": "收合側邊欄",
"expand": "展開側邊欄"
},
"header": {
"search_placeholder": "搜尋... (⌘K)",
"notifications": "通知",
"profile": "個人檔案",
"logout": "登出"
},
"footer": {
"copyright": "© {year} 岑洋國際行銷有限公司",
"version": "版本 {version}"
}
},
"dashboard": {
"page_title": "戰情室",
"page_description": "系統健康狀態總覽",
"host_card": {
"title": "主機狀態",
"label_ip": "IP 位址",
"label_role": "角色",
"label_cpu": "CPU",
"label_memory": "記憶體",
"label_disk": "磁碟",
"label_last_seen": "最後更新",
"status_online": "上線",
"status_offline": "離線"
},
"alert_panel": {
"title": "即時告警",
"btn_acknowledge": "確認",
"btn_view_all": "查看全部",
"empty_state": "目前沒有告警",
"severity_info": "資訊",
"severity_warning": "警告",
"severity_critical": "嚴重"
},
"approval_card": {
"title": "待簽核項目",
"btn_approve": "批准執行",
"btn_reject": "拒絕",
"btn_request_info": "詢問更多",
"label_requester": "申請人",
"label_blast_radius": "爆炸半徑",
"label_signatures": "簽核進度",
"label_expires_in": "剩餘時間",
"label_ai_summary": "AI 摘要",
"label_confidence": "信心度",
"status_pending": "等待簽核",
"status_approved": "已批准",
"status_rejected": "已拒絕",
"status_expired": "已過期",
"blast_low": "低",
"blast_medium": "中",
"blast_high": "高",
"blast_critical": "嚴重"
},
"metrics": {
"total_hosts": "主機總數",
"active_alerts": "活躍告警",
"pending_approvals": "待簽核",
"deployments_today": "今日部署"
}
},
"ai_copilot": {
"title": "AI 助手",
"placeholder": "輸入問題或指令...",
"btn_send": "送出",
"btn_stop": "停止",
"btn_clear": "清除對話",
"thinking": "AI 思考中...",
"error_offline": "AI 服務暫時不可用",
"error_timeout": "回應超時,請重試",
"suggestion_prefix": "建議",
"action_prefix": "建議執行",
"warning_destructive": "此操作具有破壞性,請謹慎執行"
},
"command_palette": {
"placeholder": "輸入指令...",
"category_navigation": "導航",
"category_action": "操作",
"category_ai": "AI 功能",
"category_settings": "設定",
"no_results": "沒有符合的結果",
"hint_shortcut": "快捷鍵"
},
"errors": {
"generic": "發生錯誤,請稍後再試",
"network": "網路連線失敗",
"unauthorized": "您沒有權限執行此操作",
"not_found": "找不到請求的資源",
"validation": "輸入資料驗證失敗",
"server": "伺服器錯誤",
"timeout": "請求超時",
"rate_limited": "請求過於頻繁,請稍後再試"
},
"validation": {
"required": "此欄位為必填",
"email": "請輸入有效的電子郵件",
"min_length": "至少需要 {min} 個字元",
"max_length": "最多 {max} 個字元",
"pattern": "格式不正確"
}
}
```
### en.json
```json
{
"common": {
"btn": {
"save": "Save",
"cancel": "Cancel",
"confirm": "Confirm",
"delete": "Delete",
"edit": "Edit",
"view": "View",
"back": "Back",
"next": "Next",
"previous": "Previous",
"submit": "Submit",
"reset": "Reset",
"search": "Search",
"filter": "Filter",
"export": "Export",
"import": "Import",
"refresh": "Refresh"
},
"status": {
"loading": "Loading...",
"success": "Success",
"error": "Failed",
"pending": "Processing",
"active": "Active",
"inactive": "Inactive",
"healthy": "Healthy",
"warning": "Warning",
"critical": "Critical",
"unknown": "Unknown"
}
},
"dashboard": {
"page_title": "War Room",
"page_description": "System Health Overview",
"approval_card": {
"title": "Pending Approvals",
"btn_approve": "Approve",
"btn_reject": "Reject",
"btn_request_info": "Request Info",
"label_blast_radius": "Blast Radius",
"label_signatures": "Signatures",
"status_pending": "Pending"
}
},
"ai_copilot": {
"title": "AI Assistant",
"placeholder": "Enter a question or command...",
"thinking": "AI is thinking..."
},
"errors": {
"generic": "An error occurred. Please try again later.",
"network": "Network connection failed",
"unauthorized": "You don't have permission to perform this action"
}
}
```
---
## 使用方式
### 組件中使用
```tsx
// apps/web/src/components/ApprovalCard.tsx
import { useTranslations } from 'next-intl';
export function ApprovalCard({ approval }: Props) {
const t = useTranslations('dashboard.approval_card');
return (
<div>
<h3>{t('title')}</h3>
<p>{t('label_blast_radius')}: {t(`blast_${approval.blastRadius}`)}</p>
<button>{t('btn_approve')}</button>
<button>{t('btn_reject')}</button>
</div>
);
}
```
### 動態參數
```tsx
// 使用變數
const t = useTranslations('common.time');
t('minutes_ago', { count: 5 }); // "5 分鐘前"
const t = useTranslations('common.pagination');
t('showing', { from: 1, to: 20, total: 100 }); // "顯示 1-20共 100 筆"
```
---
## CI 檢查規則
### 翻譯完整性檢查
```typescript
// scripts/check-i18n.ts
import zhTW from '../messages/zh-TW.json';
import en from '../messages/en.json';
function getAllKeys(obj: object, prefix = ''): string[] {
return Object.entries(obj).flatMap(([key, value]) => {
const fullKey = prefix ? `${prefix}.${key}` : key;
return typeof value === 'object'
? getAllKeys(value, fullKey)
: [fullKey];
});
}
const zhKeys = new Set(getAllKeys(zhTW));
const enKeys = new Set(getAllKeys(en));
const missingInEn = [...zhKeys].filter(k => !enKeys.has(k));
const missingInZh = [...enKeys].filter(k => !zhKeys.has(k));
if (missingInEn.length > 0) {
console.error('❌ Missing in en.json:', missingInEn);
process.exit(1);
}
if (missingInZh.length > 0) {
console.error('❌ Missing in zh-TW.json:', missingInZh);
process.exit(1);
}
console.log('✅ All translations are complete');
```
### PR Checklist
```markdown
## i18n Checklist
- [ ] 新增的 UI 文字已加入 zh-TW.json
- [ ] 新增的 UI 文字已加入 en.json
- [ ] Key 命名遵循 `[頁面].[組件].[元素]` 格式
- [ ] CI i18n 檢查通過
```
---
## 變更記錄
| 日期 | 版本 | 變更 | 作者 |
|------|------|------|------|
| 2026-03-20 | v1.0 | 初版建立 | CPO |
---
*此文件由 CPO 維護,前端開發者新增 UI 文字時必須遵守。*