Browse Source

chore: format code (feat-i18n-918600b)

github-actions[bot] 5 months ago
parent
commit
5d73bf8fea
31 changed files with 301 additions and 228 deletions
  1. 29 10
      IMPLEMENTATION_GUIDE.md
  2. 20 6
      TRANSLATIONS_SUMMARY.md
  3. 37 2
      TRANSLATION_KEYS_REFERENCE.md
  4. 11 6
      docs/i18n-workflow.md
  5. 1 4
      messages/en/usage.json
  6. 1 4
      messages/ja/usage.json
  7. 1 4
      messages/ru/usage.json
  8. 2 9
      messages/zh-CN/usage.json
  9. 2 9
      messages/zh-TW/usage.json
  10. 68 64
      scripts/extract-translations.ts
  11. 12 2
      src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx
  12. 6 1
      src/app/[locale]/dashboard/quotas/users/_components/users-quota-client.tsx
  13. 1 3
      src/app/[locale]/login/page.tsx
  14. 2 6
      src/app/[locale]/settings/client-versions/page.tsx
  15. 1 4
      src/app/[locale]/settings/config/page.tsx
  16. 6 2
      src/app/[locale]/settings/notifications/page.tsx
  17. 1 4
      src/app/[locale]/settings/prices/page.tsx
  18. 1 4
      src/app/[locale]/settings/providers/page.tsx
  19. 7 7
      src/components/customs/active-sessions-panel.tsx
  20. 3 3
      src/components/customs/concurrent-sessions-card.tsx
  21. 16 16
      src/components/customs/overview-panel.tsx
  22. 5 5
      src/components/customs/session-card.tsx
  23. 14 13
      src/components/customs/version-checker.tsx
  24. 3 3
      src/components/customs/version-update-notifier.tsx
  25. 1 3
      src/components/error-boundary.tsx
  26. 4 4
      src/components/quota/quota-countdown.tsx
  27. 14 14
      src/components/quota/quota-toolbar.tsx
  28. 8 4
      src/components/quota/quota-window-type.tsx
  29. 6 6
      src/components/quota/user-quota-header.tsx
  30. 15 5
      src/i18n/README.md
  31. 3 1
      src/lib/utils/zod-i18n.ts

+ 29 - 10
IMPLEMENTATION_GUIDE.md

@@ -24,6 +24,7 @@ All translation keys are documented in `TRANSLATION_KEYS_REFERENCE.md`.
 ### Step 3: Test with Multiple Locales
 
 The application automatically loads translations based on the active locale:
+
 - English: `/en/*`
 - Japanese: `/ja/*`
 - Simplified Chinese: `/zh-CN/*`
@@ -34,7 +35,6 @@ The application automatically loads translations based on the active locale:
 
 - [x] Created `ui.json` for all 5 locales
   - UI states, table operations, error messages, pagination
-  
 - [x] Created `common.json` for all 5 locales
   - Action buttons, shared terms, status indicators
 
@@ -76,17 +76,20 @@ messages/
 ## Components Using These Translations
 
 ### 1. DataTable Component
+
 - **File**: `src/components/ui/data-table.tsx`
 - **Keys Used**:
   - `ui.common.noData` (line 107)
   - `ui.common.actions` (line 280)
 
 ### 2. List Component
+
 - **File**: `src/components/ui/list.tsx`
 - **Keys Used**:
   - `ui.common.noData` (line 240)
 
 ### 3. Error Boundary
+
 - **File**: `src/components/error-boundary.tsx`
 - **Keys Used**:
   - `ui.errorBoundary.title`
@@ -97,6 +100,7 @@ messages/
   - `common.retry`
 
 ### 4. Form Error Boundary
+
 - **File**: `src/components/form-error-boundary.tsx`
 - **Keys Used**:
   - `forms.errors.formErrorTitle`
@@ -104,6 +108,7 @@ messages/
   - `ui.common.retry` (through useTranslations("ui"))
 
 ### 5. Form Layout
+
 - **File**: `src/components/form/form-layout.tsx`
 - **Keys Used**:
   - `forms.common.cancel`
@@ -111,19 +116,21 @@ messages/
 
 ## Key Translation Statistics
 
-| Metric | Count |
-|--------|-------|
-| Total Locales | 5 |
-| Total Namespaces | 3 |
-| Total Files | 15 |
-| Keys per Locale | 141 |
-| Total Keys (across all locales) | 705 |
-| Average File Size | ~1.5 KB |
+| Metric                          | Count   |
+| ------------------------------- | ------- |
+| Total Locales                   | 5       |
+| Total Namespaces                | 3       |
+| Total Files                     | 15      |
+| Keys per Locale                 | 141     |
+| Total Keys (across all locales) | 705     |
+| Average File Size               | ~1.5 KB |
 
 ## Namespace Organization
 
 ### `ui.json` (47 keys per locale)
+
 Purpose: UI component states and operations
+
 - Common states (noData, loading, empty, error)
 - Table operations (pagination, sorting, filtering)
 - Error boundary messages
@@ -131,7 +138,9 @@ Purpose: UI component states and operations
 - Empty/Loading state messages
 
 ### `common.json` (43 keys per locale)
+
 Purpose: Cross-module shared terminology
+
 - Action buttons (save, cancel, delete, edit, create, etc.)
 - Generic operations (search, filter, export, import, etc.)
 - Status indicators (success, error, warning, info)
@@ -139,7 +148,9 @@ Purpose: Cross-module shared terminology
 - Boolean states (yes, no, ok)
 
 ### `forms.json` (51 keys per locale)
+
 Purpose: Form-specific texts
+
 - Form actions (submit, reset)
 - Form attributes (required, optional, processing)
 - Validation error messages
@@ -149,6 +160,7 @@ Purpose: Form-specific texts
 ## Usage Examples
 
 ### Example 1: DataTable with No Data
+
 ```typescript
 import { useTranslations } from "next-intl";
 
@@ -172,6 +184,7 @@ export function DataTable({ data, loading, error }) {
 ```
 
 ### Example 2: Form with Validation
+
 ```typescript
 import { useTranslations } from "next-intl";
 
@@ -204,6 +217,7 @@ export function LoginForm() {
 ```
 
 ### Example 3: Error Boundary
+
 ```typescript
 import { useTranslations } from "next-intl";
 import { ErrorBoundary } from "@/components/error-boundary";
@@ -272,6 +286,7 @@ test("displays no data message", () => {
    - `forms.json` - Form-specific texts
 
 2. Add the key to all 5 locale files:
+
    ```json
    {
      "ui": {
@@ -313,6 +328,7 @@ Translations support dynamic content using placeholders:
 ```
 
 Usage:
+
 ```typescript
 const t = useTranslations("forms");
 const message = t("errors.minLength", { min: 5 });
@@ -335,7 +351,8 @@ const message = t("errors.minLength", { min: 5 });
 
 **Problem**: Component shows key name instead of translation (e.g., "ui.common.noData")
 
-**Solution**: 
+**Solution**:
+
 1. Verify the key exists in all locale files
 2. Check spelling and nesting
 3. Use `TRANSLATION_KEYS_REFERENCE.md` to find correct key
@@ -345,6 +362,7 @@ const message = t("errors.minLength", { min: 5 });
 **Problem**: Same component shows different text in different locales
 
 **Solution**:
+
 1. Check that all locale files have identical key structure
 2. Verify no typos in key names
 3. Ensure all placeholders are consistent
@@ -354,6 +372,7 @@ const message = t("errors.minLength", { min: 5 });
 **Problem**: Translation file won't load
 
 **Solution**:
+
 1. Validate JSON: `jq empty messages/en/ui.json`
 2. Check for syntax errors (missing commas, quotes)
 3. Use JSON formatter to identify issues

+ 20 - 6
TRANSLATIONS_SUMMARY.md

@@ -43,6 +43,7 @@ messages/
 Used for displaying UI states and table/list component texts.
 
 **Key Categories:**
+
 - `common` - Basic UI states (noData, actions, loading, empty, error)
 - `table` - Table-specific features (pagination, sorting, filtering, search, refresh, columns)
 - `errorBoundary` - Error messages for error boundaries (title, description, refresh page, list errors)
@@ -51,6 +52,7 @@ Used for displaying UI states and table/list component texts.
 - `loading` - Loading state messages
 
 **Translation Keys** (Sample):
+
 ```json
 {
   "common": {
@@ -73,6 +75,7 @@ Used for displaying UI states and table/list component texts.
 Used for generic action buttons and common terminology shared across all modules.
 
 **Key Categories:**
+
 - Action buttons (save, cancel, delete, confirm, edit, create, close, back, next, previous, retry)
 - Generic operations (refresh, search, filter, export, import, submit, reset, view, copy, download, upload, add, remove, apply, clear)
 - Boolean/UI states (ok, yes, no)
@@ -80,6 +83,7 @@ Used for generic action buttons and common terminology shared across all modules
 - Status indicators (loading, error, success, warning, info, noData, emptyState)
 
 **Translation Keys** (Sample):
+
 ```json
 {
   "save": "Save",
@@ -97,6 +101,7 @@ Used for generic action buttons and common terminology shared across all modules
 Used for form components, validation messages, and form-specific UI.
 
 **Key Categories:**
+
 - Form actions (submit, reset)
 - Form attributes (required, optional, processing)
 - Common form texts (cancel, processing)
@@ -106,6 +111,7 @@ Used for form components, validation messages, and form-specific UI.
 - Form messages (success, error, saved, deleted, loading)
 
 **Translation Keys** (Sample):
+
 ```json
 {
   "submit": "Submit",
@@ -127,23 +133,27 @@ Used for form components, validation messages, and form-specific UI.
 ### Components Using These Translations
 
 **ui.json:**
+
 - `src/components/ui/data-table.tsx` - Line 62, 107
 - `src/components/ui/list.tsx` - Line 195, 240
 - `src/components/error-boundary.tsx` - Line 57, 64, 66, 75, 86, 93, 96, 100
 - `src/components/form-error-boundary.tsx` - Line 14
 
 **common.json:**
+
 - `src/components/error-boundary.tsx` - Line 72, 100
 - `src/components/form/form-layout.tsx` - Line 72, 79, 124, 131
 - `src/components/form-error-boundary.tsx` - Line 28
 
 **forms.json:**
+
 - `src/components/form/form-layout.tsx` - Line 51, 102
 - `src/components/form-error-boundary.tsx` - Line 13, 21, 24
 
 ## Translation Locales
 
 ### 1. English (en)
+
 - **File Paths**: `/messages/en/{ui,common,forms}.json`
 - **Language Code**: `en`
 - **Status**: Complete
@@ -153,6 +163,7 @@ Used for form components, validation messages, and form-specific UI.
   - Complete coverage of all keys
 
 ### 2. Japanese (ja)
+
 - **File Paths**: `/messages/ja/{ui,common,forms}.json`
 - **Language Code**: `ja`
 - **Status**: Complete
@@ -162,6 +173,7 @@ Used for form components, validation messages, and form-specific UI.
   - Complete coverage including form placeholders
 
 ### 3. Russian (ru)
+
 - **File Paths**: `/messages/ru/{ui,common,forms}.json`
 - **Language Code**: `ru`
 - **Status**: Complete
@@ -171,6 +183,7 @@ Used for form components, validation messages, and form-specific UI.
   - Complete coverage of all categories
 
 ### 4. Simplified Chinese (zh-CN)
+
 - **File Paths**: `/messages/zh-CN/{ui,common,forms}.json`
 - **Language Code**: `zh-CN`
 - **Status**: Complete
@@ -180,6 +193,7 @@ Used for form components, validation messages, and form-specific UI.
   - Complete coverage including form validation messages
 
 ### 5. Traditional Chinese (zh-TW)
+
 - **File Paths**: `/messages/zh-TW/{ui,common,forms}.json`
 - **Language Code**: `zh-TW`
 - **Status**: Complete
@@ -255,12 +269,12 @@ forms.errors.formErrorDescription
 
 ### Distribution by Namespace
 
-| Namespace | Key Count | Purpose |
-|-----------|-----------|---------|
-| ui.json | 47 | UI states, table/list operations, error messages |
-| common.json | 43 | Generic action buttons and shared terms |
-| forms.json | 51 | Form validation, messages, placeholders |
-| **Total** | **141** | **Comprehensive UI translation set** |
+| Namespace   | Key Count | Purpose                                          |
+| ----------- | --------- | ------------------------------------------------ |
+| ui.json     | 47        | UI states, table/list operations, error messages |
+| common.json | 43        | Generic action buttons and shared terms          |
+| forms.json  | 51        | Form validation, messages, placeholders          |
+| **Total**   | **141**   | **Comprehensive UI translation set**             |
 
 ## File Ownership
 

+ 37 - 2
TRANSLATION_KEYS_REFERENCE.md

@@ -26,6 +26,7 @@ export function MyComponent() {
 ## UI Namespace (`ui.json`)
 
 ### Common UI States
+
 ```
 ui.common.noData        "No data available"
 ui.common.actions       "Actions"
@@ -35,6 +36,7 @@ ui.common.error         "Error"
 ```
 
 ### Table Operations
+
 ```
 ui.table.pagination     "Pagination"
 ui.table.sorting        "Sort"
@@ -51,6 +53,7 @@ ui.table.deselectAll    "Deselect all"
 ```
 
 ### Error Boundary
+
 ```
 ui.errorBoundary.title                  "Something went wrong"
 ui.errorBoundary.defaultDescription     "An unexpected error occurred..."
@@ -60,6 +63,7 @@ ui.errorBoundary.listErrorDescription   "An error occurred while loading the lis
 ```
 
 ### Pagination
+
 ```
 ui.pagination.first     "First"
 ui.pagination.last      "Last"
@@ -71,6 +75,7 @@ ui.pagination.total     "Total {total} items"
 ```
 
 ### Empty & Loading States
+
 ```
 ui.empty.title          "No data"
 ui.empty.description    "No data to display"
@@ -82,6 +87,7 @@ ui.loading.description  "Please wait..."
 ## Common Namespace (`common.json`)
 
 ### Action Buttons
+
 ```
 common.save             "Save"
 common.cancel           "Cancel"
@@ -98,6 +104,7 @@ common.refresh          "Refresh"
 ```
 
 ### Form Operations
+
 ```
 common.search           "Search"
 common.filter           "Filter"
@@ -108,6 +115,7 @@ common.reset            "Reset"
 ```
 
 ### View Operations
+
 ```
 common.view             "View"
 common.copy             "Copy"
@@ -120,6 +128,7 @@ common.clear            "Clear"
 ```
 
 ### Boolean States
+
 ```
 common.ok               "OK"
 common.yes              "Yes"
@@ -127,6 +136,7 @@ common.no               "No"
 ```
 
 ### Time References
+
 ```
 common.today            "Today"
 common.yesterday        "Yesterday"
@@ -136,6 +146,7 @@ common.thisYear         "This year"
 ```
 
 ### Status Indicators
+
 ```
 common.loading          "Loading..."
 common.error            "Error"
@@ -149,12 +160,14 @@ common.emptyState       "No data to display"
 ## Forms Namespace (`forms.json`)
 
 ### Form Actions
+
 ```
 forms.submit            "Submit"
 forms.reset             "Reset"
 ```
 
 ### Form Attributes
+
 ```
 forms.required          "Required"
 forms.optional          "Optional"
@@ -162,12 +175,14 @@ forms.processing        "Processing..."
 ```
 
 ### Common Form Texts
+
 ```
 forms.common.cancel         "Cancel"
 forms.common.processing     "Processing..."
 ```
 
 ### Error Messages
+
 ```
 forms.errors.formErrorTitle             "Form error"
 forms.errors.formErrorDescription       "An error occurred while processing the form..."
@@ -184,6 +199,7 @@ forms.errors.pattern                    "Format does not match requirements"
 ```
 
 ### Input Placeholders
+
 ```
 forms.placeholder.text                  "Enter text"
 forms.placeholder.number                "Enter a number"
@@ -196,6 +212,7 @@ forms.placeholder.textarea              "Enter text here"
 ```
 
 ### Validation Messages
+
 ```
 forms.validation.required               "This field is required"
 forms.validation.invalid                "Invalid format"
@@ -207,6 +224,7 @@ forms.validation.invalidNumber          "Invalid number format"
 ```
 
 ### Form Messages
+
 ```
 forms.messages.success                  "Operation successful"
 forms.messages.error                    "Operation failed"
@@ -219,6 +237,7 @@ forms.messages.submit                   "Submitting..."
 ## Usage Patterns
 
 ### DataTable Component
+
 ```typescript
 const t = useTranslations("ui");
 
@@ -234,6 +253,7 @@ const t = useTranslations("ui");
 ```
 
 ### Form Component
+
 ```typescript
 const t = useTranslations("forms");
 const tCommon = useTranslations("common");
@@ -252,6 +272,7 @@ const tCommon = useTranslations("common");
 ```
 
 ### Error Boundary
+
 ```typescript
 const t = useTranslations("ui");
 
@@ -268,26 +289,31 @@ const t = useTranslations("ui");
 ## Localization Notes by Language
 
 ### English (en)
+
 - Standard professional English
 - Clear and concise terminology
 - Suitable for international business context
 
 ### Japanese (ja)
+
 - Uses appropriate Japanese grammatical structures
 - Technical terms in katakana
 - Polite form suitable for professional interfaces
 
 ### Simplified Chinese (zh-CN)
+
 - Mainland China standard terminology
 - Clear and direct communication style
 - Aligns with common technical community usage
 
 ### Traditional Chinese (zh-TW)
+
 - Taiwan/Hong Kong character set
 - Regional terminology alignment
 - Professional and respectful tone
 
 ### Russian (ru)
+
 - Full Russian language support
 - Proper case and gender agreement
 - Professional tone for business application
@@ -295,6 +321,7 @@ const t = useTranslations("ui");
 ## Best Practices
 
 1. **Always Use Translation Keys**: Never hardcode UI strings
+
    ```typescript
    // Good
    <p>{t("common.noData")}</p>
@@ -304,6 +331,7 @@ const t = useTranslations("ui");
    ```
 
 2. **Use Correct Namespace**: Choose the right namespace for context
+
    ```typescript
    // Use common for generic actions
    const tCommon = useTranslations("common");
@@ -316,15 +344,21 @@ const t = useTranslations("ui");
    ```
 
 3. **Handle Missing Keys Gracefully**: The i18n system will fallback to key name
+
    ```typescript
    // If key is missing, displays: "common.save"
-   {tCommon("save")}
+   {
+     tCommon("save");
+   }
    ```
 
 4. **Use Placeholders for Dynamic Content**: Format strings with placeholders
+
    ```typescript
    // Template has: "Minimum length is {min} characters"
-   {t("errors.minLength", { min: 5 })}  // "Minimum length is 5 characters"
+   {
+     t("errors.minLength", { min: 5 });
+   } // "Minimum length is 5 characters"
    ```
 
 5. **Keep Keys Consistent**: Use the provided keys, don't create new ones
@@ -359,6 +393,7 @@ const t = useTranslations("ui");
 ## Support & Maintenance
 
 For questions or to request new translation keys:
+
 1. Check this reference guide first
 2. Verify the key exists in all 5 locales
 3. Ensure consistent usage across components

+ 11 - 6
docs/i18n-workflow.md

@@ -127,7 +127,7 @@ Use variables in translations:
 
 ```tsx
 const t = useTranslations("common");
-<p>{t("greeting", { name: userName })}</p>
+<p>{t("greeting", { name: userName })}</p>;
 ```
 
 ## Automated String Extraction
@@ -211,7 +211,7 @@ const t = useTranslations("dashboard");
 <div>
   <h2>{t("newFeature.title")}</h2>
   <p>{t("newFeature.description")}</p>
-</div>
+</div>;
 ```
 
 ### Step 4: Validate JSON Syntax
@@ -247,8 +247,8 @@ next-intl provides type-safe translation keys when configured correctly:
 ```tsx
 // TypeScript will autocomplete and validate translation keys
 const t = useTranslations("dashboard");
-t("overview.title");  // ✓ Valid
-t("invalid.key");     // ✗ TypeScript error
+t("overview.title"); // ✓ Valid
+t("invalid.key"); // ✗ TypeScript error
 ```
 
 ## Common Patterns
@@ -256,7 +256,9 @@ t("invalid.key");     // ✗ TypeScript error
 ### Conditional Rendering
 
 ```tsx
-{isLoading ? t("common.status.loading") : t("common.actions.submit")}
+{
+  isLoading ? t("common.status.loading") : t("common.actions.submit");
+}
 ```
 
 ### Lists/Arrays
@@ -281,7 +283,7 @@ const weekdays = t.raw("weekdays") as string[];
 ```
 
 ```tsx
-t("itemCount", { count: items.length })
+t("itemCount", { count: items.length });
 ```
 
 ## Best Practices
@@ -309,6 +311,7 @@ t("itemCount", { count: items.length })
 
 **Problem**: `t("myKey")` returns the key instead of translation
 **Solution**:
+
 - Verify key exists in `messages/{locale}/{namespace}.json`
 - Check namespace matches `useTranslations("namespace")`
 - Ensure JSON syntax is valid
@@ -317,6 +320,7 @@ t("itemCount", { count: items.length })
 
 **Problem**: "Argument of type 'string' is not assignable to parameter"
 **Solution**:
+
 - Add key to translation file
 - Restart TypeScript server
 - Check for typos in key path
@@ -325,6 +329,7 @@ t("itemCount", { count: items.length })
 
 **Problem**: Fallback to Chinese appears in English locale
 **Solution**:
+
 - Add translation to `messages/en/{namespace}.json`
 - Copy key structure from `messages/zh-CN/`
 - Translate value to target language

+ 1 - 4
messages/en/usage.json

@@ -109,10 +109,7 @@
           ],
           "alpine": "Alpine Linux special notes",
           "alpineText": "Distributions based on musl/uClibc (such as Alpine Linux) need to install additional dependencies:",
-          "alpineCode": [
-            "apk add libgcc libstdc++ ripgrep",
-            "export USE_BUILTIN_RIPGREP=0"
-          ]
+          "alpineCode": ["apk add libgcc libstdc++ ripgrep", "export USE_BUILTIN_RIPGREP=0"]
         },
 
         "windows": {

+ 1 - 4
messages/ja/usage.json

@@ -109,10 +109,7 @@
           ],
           "alpine": "Alpine Linux の特別な注記",
           "alpineText": "musl/uClibc ベースのディストリビューション (Alpine Linux など) は追加の依存関係をインストールする必要があります:",
-          "alpineCode": [
-            "apk add libgcc libstdc++ ripgrep",
-            "export USE_BUILTIN_RIPGREP=0"
-          ]
+          "alpineCode": ["apk add libgcc libstdc++ ripgrep", "export USE_BUILTIN_RIPGREP=0"]
         },
 
         "windows": {

+ 1 - 4
messages/ru/usage.json

@@ -109,10 +109,7 @@
           ],
           "alpine": "Особые примечания для Alpine Linux",
           "alpineText": "Дистрибутивы на основе musl/uClibc (например Alpine Linux) требуют установки дополнительных зависимостей:",
-          "alpineCode": [
-            "apk add libgcc libstdc++ ripgrep",
-            "export USE_BUILTIN_RIPGREP=0"
-          ]
+          "alpineCode": ["apk add libgcc libstdc++ ripgrep", "export USE_BUILTIN_RIPGREP=0"]
         },
 
         "windows": {

+ 2 - 9
messages/zh-CN/usage.json

@@ -72,11 +72,7 @@
       "nativeInstall": {
         "title": "Native Install(推荐)",
         "description": "官方推荐使用 Native 安装方式,具有以下优势:",
-        "advantages": [
-          "单个可执行文件,无需 Node.js 依赖",
-          "自动更新机制更稳定",
-          "启动速度更快"
-        ],
+        "advantages": ["单个可执行文件,无需 Node.js 依赖", "自动更新机制更稳定", "启动速度更快"],
 
         "macos": {
           "homebrew": "方法一:Homebrew(推荐)",
@@ -109,10 +105,7 @@
           ],
           "alpine": "Alpine Linux 特殊说明",
           "alpineText": "基于 musl/uClibc 的发行版(如 Alpine Linux)需要安装额外依赖:",
-          "alpineCode": [
-            "apk add libgcc libstdc++ ripgrep",
-            "export USE_BUILTIN_RIPGREP=0"
-          ]
+          "alpineCode": ["apk add libgcc libstdc++ ripgrep", "export USE_BUILTIN_RIPGREP=0"]
         },
 
         "windows": {

+ 2 - 9
messages/zh-TW/usage.json

@@ -72,11 +72,7 @@
       "nativeInstall": {
         "title": "Native Install(推薦)",
         "description": "官方推薦使用 Native 安裝方式,具有以下優勢:",
-        "advantages": [
-          "單個可執行文件,無需 Node.js 依賴",
-          "自動更新機制更穩定",
-          "啟動速度更快"
-        ],
+        "advantages": ["單個可執行文件,無需 Node.js 依賴", "自動更新機制更穩定", "啟動速度更快"],
 
         "macos": {
           "homebrew": "方法一:Homebrew(推薦)",
@@ -109,10 +105,7 @@
           ],
           "alpine": "Alpine Linux 特殊說明",
           "alpineText": "基於 musl/uClibc 的發行版(如 Alpine Linux)需要安裝額外依賴:",
-          "alpineCode": [
-            "apk add libgcc libstdc++ ripgrep",
-            "export USE_BUILTIN_RIPGREP=0"
-          ]
+          "alpineCode": ["apk add libgcc libstdc++ ripgrep", "export USE_BUILTIN_RIPGREP=0"]
         },
 
         "windows": {

+ 68 - 64
scripts/extract-translations.ts

@@ -51,69 +51,69 @@ const NAMESPACE_MAP: Record<string, string> = {
 
 // Common Chinese phrases mapping
 const PHRASE_MAP: Record<string, string> = {
-  "仪表盘": "dashboard",
-  "设置": "settings",
-  "用户": "users",
-  "供应商": "providers",
-  "模型": "models",
-  "请求": "requests",
-  "成本": "cost",
-  "统计": "stats",
-  "日志": "logs",
-  "配额": "quotas",
-  "会话": "sessions",
-  "密钥": "keys",
-  "价格": "prices",
-  "配置": "config",
-  "数据": "data",
-  "通知": "notifications",
-  "版本": "versions",
-  "敏感词": "sensitiveWords",
-  "登录": "login",
-  "退出": "logout",
-  "保存": "save",
-  "取消": "cancel",
-  "删除": "delete",
-  "编辑": "edit",
-  "添加": "add",
-  "刷新": "refresh",
-  "搜索": "search",
-  "导出": "export",
-  "导入": "import",
-  "确认": "confirm",
-  "提交": "submit",
-  "重置": "reset",
-  "查看": "view",
-  "复制": "copy",
-  "下载": "download",
-  "上传": "upload",
-  "启用": "enabled",
-  "禁用": "disabled",
-  "成功": "success",
-  "失败": "failed",
-  "错误": "error",
-  "警告": "warning",
-  "信息": "info",
-  "加载中": "loading",
-  "标题": "title",
-  "描述": "description",
-  "名称": "name",
-  "状态": "status",
-  "时间": "time",
-  "操作": "actions",
-  "详情": "details",
-  "列表": "list",
-  "表单": "form",
-  "按钮": "button",
-  "输入": "input",
-  "选择": "select",
-  "选项": "options",
-  "全部": "all",
-  "": "none",
-  "": "yes",
-  "": "no",
-  "": "on",
-  "": "off",
+  仪表盘: "dashboard",
+  设置: "settings",
+  用户: "users",
+  供应商: "providers",
+  模型: "models",
+  请求: "requests",
+  成本: "cost",
+  统计: "stats",
+  日志: "logs",
+  配额: "quotas",
+  会话: "sessions",
+  密钥: "keys",
+  价格: "prices",
+  配置: "config",
+  数据: "data",
+  通知: "notifications",
+  版本: "versions",
+  敏感词: "sensitiveWords",
+  登录: "login",
+  退出: "logout",
+  保存: "save",
+  取消: "cancel",
+  删除: "delete",
+  编辑: "edit",
+  添加: "add",
+  刷新: "refresh",
+  搜索: "search",
+  导出: "export",
+  导入: "import",
+  确认: "confirm",
+  提交: "submit",
+  重置: "reset",
+  查看: "view",
+  复制: "copy",
+  下载: "download",
+  上传: "upload",
+  启用: "enabled",
+  禁用: "disabled",
+  成功: "success",
+  失败: "failed",
+  错误: "error",
+  警告: "warning",
+  信息: "info",
+  加载中: "loading",
+  标题: "title",
+  描述: "description",
+  名称: "name",
+  状态: "status",
+  时间: "time",
+  操作: "actions",
+  详情: "details",
+  列表: "list",
+  表单: "form",
+  按钮: "button",
+  输入: "input",
+  选择: "select",
+  选项: "options",
+  全部: "all",
+  无: "none",
+  是: "yes",
+  否: "no",
+  开: "on",
+  关: "off",
 };
 
 /**
@@ -185,7 +185,11 @@ function extractFromFile(filePath: string): ExtractedString[] {
   // This is a simplified version - a full AST parser would be more robust
   lines.forEach((line, lineIndex) => {
     // Skip imports, comments
-    if (line.trim().startsWith("import ") || line.trim().startsWith("//") || line.trim().startsWith("/*")) {
+    if (
+      line.trim().startsWith("import ") ||
+      line.trim().startsWith("//") ||
+      line.trim().startsWith("/*")
+    ) {
       return;
     }
 

+ 12 - 2
src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx

@@ -147,7 +147,12 @@ export function ProvidersQuotaClient({
                   className="h-2"
                 />
                 <p className="text-xs text-muted-foreground">
-                  重置于 {formatDateDistance(new Date(provider.quota.costWeekly.resetAt), new Date(), locale)}
+                  重置于{" "}
+                  {formatDateDistance(
+                    new Date(provider.quota.costWeekly.resetAt),
+                    new Date(),
+                    locale
+                  )}
                 </p>
               </div>
             )}
@@ -170,7 +175,12 @@ export function ProvidersQuotaClient({
                   className="h-2"
                 />
                 <p className="text-xs text-muted-foreground">
-                  重置于 {formatDateDistance(new Date(provider.quota.costMonthly.resetAt), new Date(), locale)}
+                  重置于{" "}
+                  {formatDateDistance(
+                    new Date(provider.quota.costMonthly.resetAt),
+                    new Date(),
+                    locale
+                  )}
                 </p>
               </div>
             )}

+ 6 - 1
src/app/[locale]/dashboard/quotas/users/_components/users-quota-client.tsx

@@ -127,7 +127,12 @@ export function UsersQuotaClient({
                       limit={user.quota.dailyCost.limit}
                     />
                     <p className="text-xs text-muted-foreground">
-                      重置于 {formatDateDistance(new Date(user.quota.dailyCost.resetAt), new Date(), locale)}
+                      重置于{" "}
+                      {formatDateDistance(
+                        new Date(user.quota.dailyCost.resetAt),
+                        new Date(),
+                        locale
+                      )}
                     </p>
                   </div>
                 </>

+ 1 - 3
src/app/[locale]/login/page.tsx

@@ -100,9 +100,7 @@ function LoginPageContent() {
                     <p className="font-medium">{t("security.solutionTitle")}</p>
                     <ol className="ml-4 mt-1 list-decimal space-y-1">
                       <li>{t("security.useHttps")}</li>
-                      <li>
-                        {t("security.disableSecureCookies")}
-                      </li>
+                      <li>{t("security.disableSecureCookies")}</li>
                     </ol>
                   </div>
                 </AlertDescription>

+ 2 - 6
src/app/[locale]/settings/client-versions/page.tsx

@@ -37,9 +37,7 @@ export default async function ClientVersionsPage() {
       <Card>
         <CardHeader>
           <CardTitle>{t("clientVersions.section.settings.title")}</CardTitle>
-          <CardDescription>
-            {t("clientVersions.section.settings.description")}
-          </CardDescription>
+          <CardDescription>{t("clientVersions.section.settings.description")}</CardDescription>
         </CardHeader>
         <CardContent>
           <ClientVersionToggle enabled={enableClientVersionCheck} />
@@ -50,9 +48,7 @@ export default async function ClientVersionsPage() {
       <Card>
         <CardHeader>
           <CardTitle>{t("clientVersions.section.distribution.title")}</CardTitle>
-          <CardDescription>
-            {t("clientVersions.section.distribution.description")}
-          </CardDescription>
+          <CardDescription>{t("clientVersions.section.distribution.description")}</CardDescription>
         </CardHeader>
         <CardContent>
           {stats && stats.length > 0 ? (

+ 1 - 4
src/app/[locale]/settings/config/page.tsx

@@ -13,10 +13,7 @@ export default async function SettingsConfigPage() {
 
   return (
     <>
-      <SettingsPageHeader
-        title={t("config.title")}
-        description={t("config.description")}
-      />
+      <SettingsPageHeader title={t("config.title")} description={t("config.description")} />
 
       <Section
         title={t("config.section.siteParams.title")}

+ 6 - 2
src/app/[locale]/settings/notifications/page.tsx

@@ -196,7 +196,9 @@ export default function NotificationsPage() {
               <AlertTriangle className="w-5 h-5 text-red-500" />
               {t("notifications.section.circuitBreaker.title")}
             </CardTitle>
-            <CardDescription>{t("notifications.section.circuitBreaker.description")}</CardDescription>
+            <CardDescription>
+              {t("notifications.section.circuitBreaker.description")}
+            </CardDescription>
           </CardHeader>
           <CardContent className="space-y-4">
             <div className="flex items-center justify-between">
@@ -260,7 +262,9 @@ export default function NotificationsPage() {
               <TrendingUp className="w-5 h-5 text-blue-500" />
               {t("notifications.section.dailyLeaderboard.title")}
             </CardTitle>
-            <CardDescription>{t("notifications.section.dailyLeaderboard.description")}</CardDescription>
+            <CardDescription>
+              {t("notifications.section.dailyLeaderboard.description")}
+            </CardDescription>
           </CardHeader>
           <CardContent className="space-y-4">
             <div className="flex items-center justify-between">

+ 1 - 4
src/app/[locale]/settings/prices/page.tsx

@@ -54,10 +54,7 @@ export default async function SettingsPricesPage({ searchParams }: SettingsPrice
 
   return (
     <>
-      <SettingsPageHeader
-        title={t("prices.title")}
-        description={t("prices.description")}
-      />
+      <SettingsPageHeader title={t("prices.title")} description={t("prices.description")} />
 
       <Section
         title={t("prices.section.title")}

+ 1 - 4
src/app/[locale]/settings/providers/page.tsx

@@ -25,10 +25,7 @@ export default async function SettingsProvidersPage() {
 
   return (
     <>
-      <SettingsPageHeader
-        title={t("providers.title")}
-        description={t("providers.description")}
-      />
+      <SettingsPageHeader title={t("providers.title")} description={t("providers.description")} />
 
       <Section
         title={t("providers.section.title")}

+ 7 - 7
src/components/customs/active-sessions-panel.tsx

@@ -143,8 +143,8 @@ function SessionListItem({
  */
 export function ActiveSessionsPanel({ currencyCode = "USD" }: { currencyCode?: CurrencyCode }) {
   const router = useRouter();
-  const tu = useTranslations('ui');
-  const tc = useTranslations('customs');
+  const tu = useTranslations("ui");
+  const tc = useTranslations("customs");
 
   const { data = [], isLoading } = useQuery<ActiveSessionInfo[], Error>({
     queryKey: ["active-sessions"],
@@ -157,16 +157,16 @@ export function ActiveSessionsPanel({ currencyCode = "USD" }: { currencyCode?: C
       <div className="px-4 py-3 border-b flex items-center justify-between">
         <div className="flex items-center gap-2">
           <Activity className="h-4 w-4 text-primary" />
-          <h3 className="font-semibold text-sm">{tc('activeSessions.title')}</h3>
+          <h3 className="font-semibold text-sm">{tc("activeSessions.title")}</h3>
           <span className="text-xs text-muted-foreground">
-            {tc('activeSessions.summary', { count: data.length, minutes: 5 })}
+            {tc("activeSessions.summary", { count: data.length, minutes: 5 })}
           </span>
         </div>
         <button
           onClick={() => router.push("/dashboard/sessions")}
           className="text-xs text-muted-foreground hover:text-foreground transition-colors"
         >
-          {tc('activeSessions.viewAll')} →
+          {tc("activeSessions.viewAll")} →
         </button>
       </div>
 
@@ -174,11 +174,11 @@ export function ActiveSessionsPanel({ currencyCode = "USD" }: { currencyCode?: C
         {isLoading && data.length === 0 ? (
           <div className="flex items-center justify-center h-[200px] text-muted-foreground text-sm">
             <Loader2 className="h-4 w-4 animate-spin mr-2" />
-            {tu('common.loading')}
+            {tu("common.loading")}
           </div>
         ) : data.length === 0 ? (
           <div className="flex items-center justify-center h-[200px] text-muted-foreground text-sm">
-            {tc('activeSessions.empty')}
+            {tc("activeSessions.empty")}
           </div>
         ) : (
           <div className="divide-y">

+ 3 - 3
src/components/customs/concurrent-sessions-card.tsx

@@ -25,7 +25,7 @@ async function fetchConcurrentSessions(): Promise<number> {
  */
 export function ConcurrentSessionsCard() {
   const router = useRouter();
-  const t = useTranslations('customs');
+  const t = useTranslations("customs");
   const { data = 0 } = useQuery<number, Error>({
     queryKey: ["concurrent-sessions"],
     queryFn: fetchConcurrentSessions,
@@ -39,12 +39,12 @@ export function ConcurrentSessionsCard() {
   return (
     <Card className="cursor-pointer hover:border-primary transition-colors" onClick={handleClick}>
       <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-        <CardTitle className="text-sm font-medium">{t('concurrent.title')}</CardTitle>
+        <CardTitle className="text-sm font-medium">{t("concurrent.title")}</CardTitle>
         <Activity className="h-4 w-4 text-muted-foreground" />
       </CardHeader>
       <CardContent>
         <div className="text-2xl font-bold">{data}</div>
-        <p className="text-xs text-muted-foreground">{t('concurrent.description')}</p>
+        <p className="text-xs text-muted-foreground">{t("concurrent.description")}</p>
       </CardContent>
     </Card>
   );

+ 16 - 16
src/components/customs/overview-panel.tsx

@@ -162,8 +162,8 @@ interface OverviewPanelProps {
  */
 export function OverviewPanel({ currencyCode = "USD", isAdmin = false }: OverviewPanelProps) {
   const router = useRouter();
-  const tc = useTranslations('customs');
-  const tu = useTranslations('ui');
+  const tc = useTranslations("customs");
+  const tu = useTranslations("ui");
 
   const { data, isLoading } = useQuery<OverviewData, Error>({
     queryKey: ["overview-data"],
@@ -197,27 +197,27 @@ export function OverviewPanel({ currencyCode = "USD", isAdmin = false }: Overvie
       <div className="lg:col-span-3">
         <div className="grid grid-cols-2 gap-3">
           <MetricCard
-            title={tc('metrics.concurrent')}
+            title={tc("metrics.concurrent")}
             value={metrics.concurrentSessions}
-            description={tc('activeSessions.recent')}
+            description={tc("activeSessions.recent")}
             icon={Activity}
           />
           <MetricCard
-            title={tc('metrics.todayRequests')}
+            title={tc("metrics.todayRequests")}
             value={metrics.todayRequests}
-            description={tc('metrics.requestsDescription')}
+            description={tc("metrics.requestsDescription")}
             icon={TrendingUp}
           />
           <MetricCard
-            title={tc('metrics.todayCost')}
+            title={tc("metrics.todayCost")}
             value={formatCurrency(metrics.todayCost, currencyCode)}
-            description={tc('metrics.costDescription')}
+            description={tc("metrics.costDescription")}
             icon={DollarSign}
           />
           <MetricCard
-            title={tc('metrics.avgResponse')}
+            title={tc("metrics.avgResponse")}
             value={metrics.avgResponseTime}
-            description={tc('metrics.responseDescription')}
+            description={tc("metrics.responseDescription")}
             icon={Clock}
             formatter={formatResponseTime}
           />
@@ -227,7 +227,7 @@ export function OverviewPanel({ currencyCode = "USD", isAdmin = false }: Overvie
             onClick={() => router.push("/dashboard/sessions")}
             className="w-full text-xs text-muted-foreground hover:text-foreground transition-colors text-center py-1.5 hover:bg-muted rounded-md"
           >
-            {tc('metrics.viewDetails')} →
+            {tc("metrics.viewDetails")} →
           </button>
         </div>
       </div>
@@ -238,16 +238,16 @@ export function OverviewPanel({ currencyCode = "USD", isAdmin = false }: Overvie
           <div className="px-4 py-3 border-b flex items-center justify-between flex-shrink-0">
             <div className="flex items-center gap-2">
               <Activity className="h-4 w-4 text-primary" />
-              <h3 className="font-semibold text-sm">{tc('activeSessions.title')}</h3>
+              <h3 className="font-semibold text-sm">{tc("activeSessions.title")}</h3>
               <span className="text-xs text-muted-foreground">
-                {tc('activeSessions.summary', { count: metrics.recentSessions.length, minutes: 5 })}
+                {tc("activeSessions.summary", { count: metrics.recentSessions.length, minutes: 5 })}
               </span>
             </div>
             <button
               onClick={() => router.push("/dashboard/sessions")}
               className="text-xs text-muted-foreground hover:text-foreground transition-colors"
             >
-              {tc('activeSessions.viewAll')} →
+              {tc("activeSessions.viewAll")} →
             </button>
           </div>
 
@@ -255,11 +255,11 @@ export function OverviewPanel({ currencyCode = "USD", isAdmin = false }: Overvie
             {isLoading && metrics.recentSessions.length === 0 ? (
               <div className="flex items-center justify-center h-full text-muted-foreground text-sm">
                 <Loader2 className="h-4 w-4 animate-spin mr-2" />
-                {tu('common.loading')}
+                {tu("common.loading")}
               </div>
             ) : metrics.recentSessions.length === 0 ? (
               <div className="flex items-center justify-center h-full text-muted-foreground text-sm">
-                {tc('activeSessions.empty')}
+                {tc("activeSessions.empty")}
               </div>
             ) : (
               <div className="divide-y">

+ 5 - 5
src/components/customs/session-card.tsx

@@ -67,11 +67,11 @@ function getStatusConfig(
  * 用于概览面板的横向滚动展示
  */
 export function SessionCard({ session, className, currencyCode = "USD" }: SessionCardProps) {
-  const t = useTranslations('customs');
+  const t = useTranslations("customs");
   const statusConfig = getStatusConfig(session.status, session.statusCode, {
-    inProgress: t('sessions.status.inProgress'),
-    error: t('sessions.status.error'),
-    completed: t('sessions.status.completed')
+    inProgress: t("sessions.status.inProgress"),
+    error: t("sessions.status.error"),
+    completed: t("sessions.status.completed"),
   });
   const inputTokensDisplay =
     session.inputTokens !== undefined ? formatTokenAmount(session.inputTokens) : null;
@@ -106,7 +106,7 @@ export function SessionCard({ session, className, currencyCode = "USD" }: Sessio
           {/* 模型和供应商 */}
           <div className="flex items-center gap-1.5 text-xs">
             <Badge variant="secondary" className="truncate max-w-[120px] font-mono">
-              {session.model || t('sessions.unknown')}
+              {session.model || t("sessions.unknown")}
             </Badge>
             {session.providerName && (
               <span className="text-muted-foreground truncate flex-1">

+ 14 - 13
src/components/customs/version-checker.tsx

@@ -17,7 +17,7 @@ interface VersionInfo {
 }
 
 export function VersionChecker() {
-  const t = useTranslations('customs');
+  const t = useTranslations("customs");
   const [versionInfo, setVersionInfo] = useState<VersionInfo | null>(null);
   const [loading, setLoading] = useState(true);
 
@@ -33,7 +33,7 @@ export function VersionChecker() {
         current: "dev",
         latest: null,
         hasUpdate: false,
-        error: t('version.errorNetwork'),
+        error: t("version.errorNetwork"),
       });
     } finally {
       setLoading(false);
@@ -48,12 +48,12 @@ export function VersionChecker() {
     return (
       <Card>
         <CardHeader>
-          <CardTitle>{t('version.title')}</CardTitle>
+          <CardTitle>{t("version.title")}</CardTitle>
         </CardHeader>
         <CardContent>
           <div className="flex items-center gap-2 text-muted-foreground">
             <RefreshCw className="h-4 w-4 animate-spin" />
-            <span>{t('version.checking')}</span>
+            <span>{t("version.checking")}</span>
           </div>
         </CardContent>
       </Card>
@@ -63,18 +63,18 @@ export function VersionChecker() {
   return (
     <Card>
       <CardHeader>
-        <CardTitle>{t('version.title')}</CardTitle>
-        <CardDescription>{t('version.description')}</CardDescription>
+        <CardTitle>{t("version.title")}</CardTitle>
+        <CardDescription>{t("version.description")}</CardDescription>
       </CardHeader>
       <CardContent className="space-y-4">
         <div className="flex items-center justify-between">
           <div>
-            <p className="text-sm text-muted-foreground">{t('version.current')}</p>
+            <p className="text-sm text-muted-foreground">{t("version.current")}</p>
             <p className="text-lg font-mono">{versionInfo?.current}</p>
           </div>
           {versionInfo?.latest && (
             <div>
-              <p className="text-sm text-muted-foreground">{t('version.latest')}</p>
+              <p className="text-sm text-muted-foreground">{t("version.latest")}</p>
               <p className="text-lg font-mono">{versionInfo.latest}</p>
             </div>
           )}
@@ -84,15 +84,16 @@ export function VersionChecker() {
           <div className="rounded-lg border border-orange-200 bg-orange-50 p-4 dark:border-orange-900 dark:bg-orange-950">
             <div className="flex items-start gap-3">
               <Badge variant="outline" className="bg-orange-500 text-white">
-                {t('version.updateAvailable')}
+                {t("version.updateAvailable")}
               </Badge>
               <div className="flex-1">
                 <p className="text-sm">
-                  {t('version.foundUpdate')} <code className="font-mono">{versionInfo.latest}</code>
+                  {t("version.foundUpdate")} <code className="font-mono">{versionInfo.latest}</code>
                 </p>
                 {versionInfo.publishedAt && (
                   <p className="mt-1 text-xs text-muted-foreground">
-                    {t('version.publishedAt')} {new Date(versionInfo.publishedAt).toLocaleDateString("zh-CN")}
+                    {t("version.publishedAt")}{" "}
+                    {new Date(versionInfo.publishedAt).toLocaleDateString("zh-CN")}
                   </p>
                 )}
               </div>
@@ -109,13 +110,13 @@ export function VersionChecker() {
         <div className="flex gap-2">
           <Button variant="outline" size="sm" onClick={checkVersion} disabled={loading}>
             <RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
-            {t('version.checkUpdate')}
+            {t("version.checkUpdate")}
           </Button>
           {versionInfo?.releaseUrl && (
             <Button variant="outline" size="sm" asChild>
               <a href={versionInfo.releaseUrl} target="_blank" rel="noopener noreferrer">
                 <ExternalLink className="h-4 w-4" />
-                {t('version.viewRelease')}
+                {t("version.viewRelease")}
               </a>
             </Button>
           )}

+ 3 - 3
src/components/customs/version-update-notifier.tsx

@@ -13,7 +13,7 @@ interface VersionInfo {
 }
 
 export function VersionUpdateNotifier() {
-  const t = useTranslations('customs');
+  const t = useTranslations("customs");
   const [versionInfo, setVersionInfo] = useState<VersionInfo | null>(null);
 
   useEffect(() => {
@@ -45,13 +45,13 @@ export function VersionUpdateNotifier() {
             target="_blank"
             rel="noopener noreferrer"
             className="inline-flex items-center justify-center text-orange-600 hover:text-orange-700 dark:text-orange-500 dark:hover:text-orange-400"
-            aria-label={t('version.ariaUpdateAvailable')}
+            aria-label={t("version.ariaUpdateAvailable")}
           >
             <AlertCircle className="h-5 w-5" />
           </a>
         </TooltipTrigger>
         <TooltipContent side="bottom" className="max-w-xs">
-          <p className="font-medium">{t('version.updateAvailable')}</p>
+          <p className="font-medium">{t("version.updateAvailable")}</p>
           <p className="text-xs text-muted-foreground">
             {versionInfo.current} → {versionInfo.latest}
           </p>

+ 1 - 3
src/components/error-boundary.tsx

@@ -62,9 +62,7 @@ function DefaultErrorFallback({ error, resetError }: { error?: Error; resetError
           <AlertTriangle className="h-12 w-12 text-destructive" />
         </div>
         <CardTitle className="text-destructive">{t("errorBoundary.title")}</CardTitle>
-        <CardDescription>
-          {error?.message || t("errorBoundary.defaultDescription")}
-        </CardDescription>
+        <CardDescription>{error?.message || t("errorBoundary.defaultDescription")}</CardDescription>
       </CardHeader>
       <CardFooter className="flex gap-2 justify-center">
         <Button variant="outline" onClick={resetError} size="sm">

+ 4 - 4
src/components/quota/quota-countdown.tsx

@@ -38,8 +38,8 @@ export function QuotaCountdown({
   showIcon = true,
   size = "md",
 }: QuotaCountdownProps) {
-  const t = useTranslations('quota');
-  const resolvedLabel = label ?? t('countdown.reset');
+  const t = useTranslations("quota");
+  const resolvedLabel = label ?? t("countdown.reset");
   const countdown = useCountdown(resetAt);
 
   // 根据剩余时间判断状态
@@ -144,9 +144,9 @@ export function QuotaCountdownWithProgress({
   label?: string;
   className?: string;
 }) {
-  const t = useTranslations('quota');
+  const t = useTranslations("quota");
   const countdown = useCountdown(resetAt);
-  const resolvedLabel = label ?? t('countdown.reset');
+  const resolvedLabel = label ?? t("countdown.reset");
 
   // 计算进度百分比
   const getProgress = () => {

+ 14 - 14
src/components/quota/quota-toolbar.tsx

@@ -44,17 +44,17 @@ export function QuotaToolbar({
   const [searchQuery, setSearchQuery] = useState("");
   const [autoRefresh, setAutoRefresh] = useState(false);
   const [refreshInterval, setRefreshInterval] = useState(30);
-  const t = useTranslations('quota');
+  const t = useTranslations("quota");
 
   // Provide translated defaults when options are not passed in
   const _sortOptions = sortOptions ?? [
-    { value: 'name', label: t('toolbar.sortOptions.name') },
-    { value: 'usage', label: t('toolbar.sortOptions.usage') },
+    { value: "name", label: t("toolbar.sortOptions.name") },
+    { value: "usage", label: t("toolbar.sortOptions.usage") },
   ];
   const _filterOptions = filterOptions ?? [
-    { value: 'all', label: t('toolbar.filterOptions.all') },
-    { value: 'warning', label: t('toolbar.filterOptions.warning') },
-    { value: 'exceeded', label: t('toolbar.filterOptions.exceeded') },
+    { value: "all", label: t("toolbar.filterOptions.all") },
+    { value: "warning", label: t("toolbar.filterOptions.warning") },
+    { value: "exceeded", label: t("toolbar.filterOptions.exceeded") },
   ];
 
   // 自动刷新机制
@@ -91,7 +91,7 @@ export function QuotaToolbar({
           <div className="relative flex-1 max-w-sm">
             <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
             <Input
-              placeholder={t('toolbar.searchPlaceholder')}
+              placeholder={t("toolbar.searchPlaceholder")}
               value={searchQuery}
               onChange={(e) => handleSearchChange(e.target.value)}
               className="pl-9"
@@ -103,7 +103,7 @@ export function QuotaToolbar({
         {showFilter && onFilter && (
           <Select defaultValue="all" onValueChange={onFilter}>
             <SelectTrigger className="w-[140px]">
-              <SelectValue placeholder={t('toolbar.filter')} />
+              <SelectValue placeholder={t("toolbar.filter")} />
             </SelectTrigger>
             <SelectContent>
               {_filterOptions.map((option) => (
@@ -119,7 +119,7 @@ export function QuotaToolbar({
         {showSort && onSort && (
           <Select defaultValue="name" onValueChange={onSort}>
             <SelectTrigger className="w-[140px]">
-              <SelectValue placeholder={t('toolbar.sort')} />
+              <SelectValue placeholder={t("toolbar.sort")} />
             </SelectTrigger>
             <SelectContent>
               {_sortOptions.map((option) => (
@@ -139,7 +139,7 @@ export function QuotaToolbar({
             <div className="flex items-center gap-2">
               <Switch id="auto-refresh" checked={autoRefresh} onCheckedChange={setAutoRefresh} />
               <Label htmlFor="auto-refresh" className="text-sm cursor-pointer">
-                {t('toolbar.autoRefresh')}
+                {t("toolbar.autoRefresh")}
               </Label>
             </div>
 
@@ -152,9 +152,9 @@ export function QuotaToolbar({
                   <SelectValue />
                 </SelectTrigger>
                 <SelectContent>
-                  <SelectItem value="10">{t('toolbar.interval.10s')}</SelectItem>
-                  <SelectItem value="30">{t('toolbar.interval.30s')}</SelectItem>
-                  <SelectItem value="60">{t('toolbar.interval.60s')}</SelectItem>
+                  <SelectItem value="10">{t("toolbar.interval.10s")}</SelectItem>
+                  <SelectItem value="30">{t("toolbar.interval.30s")}</SelectItem>
+                  <SelectItem value="60">{t("toolbar.interval.60s")}</SelectItem>
                 </SelectContent>
               </Select>
             )}
@@ -163,7 +163,7 @@ export function QuotaToolbar({
 
         <Button variant="outline" size="sm" onClick={handleManualRefresh} disabled={isPending}>
           <RefreshCw className={`h-4 w-4 ${isPending ? "animate-spin" : ""}`} />
-          <span className="ml-2">{t('toolbar.refresh')}</span>
+          <span className="ml-2">{t("toolbar.refresh")}</span>
         </Button>
       </div>
     </div>

+ 8 - 4
src/components/quota/quota-window-type.tsx

@@ -16,7 +16,11 @@ interface WindowTypeConfig {
 // Static icon/style config; labels/descriptions come from i18n
 const WINDOW_STYLE: Record<WindowType, WindowTypeConfig> = {
   "5h": { icon: RefreshCw, variant: "default", color: "text-blue-600 dark:text-blue-400" },
-  weekly: { icon: CalendarDays, variant: "secondary", color: "text-purple-600 dark:text-purple-400" },
+  weekly: {
+    icon: CalendarDays,
+    variant: "secondary",
+    color: "text-purple-600 dark:text-purple-400",
+  },
   monthly: { icon: Calendar, variant: "secondary", color: "text-green-600 dark:text-green-400" },
   daily: { icon: Clock, variant: "secondary", color: "text-orange-600 dark:text-orange-400" },
 };
@@ -51,7 +55,7 @@ export function QuotaWindowType({
   showDescription = false,
   size = "sm",
 }: QuotaWindowTypeProps) {
-  const t = useTranslations('quota');
+  const t = useTranslations("quota");
   const style = WINDOW_STYLE[type];
   const Icon = style.icon;
   const label = t(`windowType.${type}.label` as any);
@@ -87,7 +91,7 @@ export function QuotaWindowTypeCompact({
   type: WindowType;
   className?: string;
 }) {
-  const t = useTranslations('quota');
+  const t = useTranslations("quota");
   const label = t(`windowType.${type}.label` as any);
   return <span className={cn("text-xs text-muted-foreground", className)}>{label}</span>;
 }
@@ -102,7 +106,7 @@ export function QuotaWindowTypeWithTooltip({
   type: WindowType;
   className?: string;
 }) {
-  const t = useTranslations('quota');
+  const t = useTranslations("quota");
   const style = WINDOW_STYLE[type];
   const Icon = style.icon;
   const label = t(`windowType.${type}.label` as any);

+ 6 - 6
src/components/quota/user-quota-header.tsx

@@ -40,7 +40,7 @@ export function UserQuotaHeader({
   currencyCode = "USD",
   className,
 }: UserQuotaHeaderProps) {
-  const t = useTranslations('quota');
+  const t = useTranslations("quota");
   // 计算使用率
   const rpmRate = getUsageRate(rpmCurrent, rpmLimit);
   const dailyRate = getUsageRate(dailyCostCurrent, dailyCostLimit);
@@ -89,10 +89,10 @@ export function UserQuotaHeader({
               variant={userRole === "admin" ? "default" : "secondary"}
               className="flex-shrink-0"
             >
-              {userRole === "admin" ? t('header.role.admin') : t('header.role.user')}
+              {userRole === "admin" ? t("header.role.admin") : t("header.role.user")}
             </Badge>
             <Badge variant="outline" className="flex-shrink-0">
-              {keyCount} {t('header.keysCountSuffix')}
+              {keyCount} {t("header.keysCountSuffix")}
             </Badge>
           </div>
 
@@ -102,7 +102,7 @@ export function UserQuotaHeader({
               {/* RPM 进度条 */}
               <div className="flex items-center gap-2">
                 <span className="text-sm text-muted-foreground w-20 text-right flex-shrink-0">
-                  {t('header.rpm')}:
+                  {t("header.rpm")}:
                 </span>
                 <QuotaProgress current={rpmCurrent} limit={rpmLimit} className="flex-1" />
                 <span className="text-sm font-mono w-24 text-right flex-shrink-0">
@@ -116,7 +116,7 @@ export function UserQuotaHeader({
               {/* 今日消费进度条 */}
               <div className="flex items-center gap-2">
                 <span className="text-sm text-muted-foreground w-20 text-right flex-shrink-0">
-                  {t('header.todayCost')}:
+                  {t("header.todayCost")}:
                 </span>
                 <QuotaProgress
                   current={dailyCostCurrent}
@@ -159,7 +159,7 @@ export function UserQuotaHeader({
         {/* 超限提示 */}
         {colorClass === "exceeded" && (
           <div className="mt-2 text-sm text-red-600 dark:text-red-400 font-medium">
-            ⚠️ {t('header.exceededNotice')}
+            ⚠️ {t("header.exceededNotice")}
           </div>
         )}
       </CardHeader>

+ 15 - 5
src/i18n/README.md

@@ -5,6 +5,7 @@ This directory contains the internationalization (i18n) infrastructure for the a
 ## Overview
 
 The application supports 5 locales:
+
 - **zh-CN** (Chinese Simplified) - Default
 - **zh-TW** (Chinese Traditional)
 - **en** (English)
@@ -29,7 +30,7 @@ src/i18n/
 Defines supported locales, default locale, and locale labels.
 
 ```typescript
-import { locales, defaultLocale, localeLabels } from '@/i18n/config';
+import { locales, defaultLocale, localeLabels } from "@/i18n/config";
 ```
 
 ### 2. Routing (`routing.ts`)
@@ -37,6 +38,7 @@ import { locales, defaultLocale, localeLabels } from '@/i18n/config';
 Provides locale-aware routing configuration and navigation utilities.
 
 **Key Features:**
+
 - Automatic locale detection from:
   1. NEXT_LOCALE cookie (persisted for 1 year)
   2. Accept-Language header
@@ -44,6 +46,7 @@ Provides locale-aware routing configuration and navigation utilities.
 - Always-prefix strategy: All routes include locale prefix (e.g., `/zh-CN/dashboard`)
 
 **Navigation Utilities:**
+
 ```typescript
 import { Link, redirect, useRouter, usePathname } from '@/i18n/routing';
 
@@ -68,6 +71,7 @@ Configures how translations are loaded for each request. Currently returns empty
 ## Middleware Integration
 
 The middleware (`src/middleware.ts`) integrates:
+
 1. **Locale Detection**: Automatically detects and routes based on locale
 2. **Authentication**: Validates auth tokens and redirects if needed
 3. **Path Preservation**: Maintains locale prefix in redirects
@@ -75,6 +79,7 @@ The middleware (`src/middleware.ts`) integrates:
 ### Public Paths
 
 These paths don't require authentication:
+
 - `/[locale]/login`
 - `/[locale]/usage-doc`
 - `/api/auth/login`
@@ -121,15 +126,15 @@ export default function ClientComponent() {
 ### In Server Actions
 
 ```typescript
-import { redirect } from '@/i18n/routing';
-import { getTranslations } from 'next-intl/server';
+import { redirect } from "@/i18n/routing";
+import { getTranslations } from "next-intl/server";
 
 export async function serverAction() {
-  const t = await getTranslations('namespace');
+  const t = await getTranslations("namespace");
 
   // Do something...
 
-  redirect('/dashboard');
+  redirect("/dashboard");
 }
 ```
 
@@ -153,6 +158,7 @@ export async function serverAction() {
 ### Next.js 15 App Router Integration
 
 The configuration integrates with Next.js 15's App Router using:
+
 - `createNextIntlPlugin()` in `next.config.ts`
 - `createMiddleware()` for locale routing
 - `getRequestConfig()` for server-side translation loading
@@ -160,6 +166,7 @@ The configuration integrates with Next.js 15's App Router using:
 ### Type Safety
 
 All locale-related types are strictly typed:
+
 - `Locale` type: Union of supported locale codes
 - `Routing` type: Routing configuration type
 - Navigation utilities: Fully typed for IDE autocomplete
@@ -172,12 +179,15 @@ All locale-related types are strictly typed:
 ## Troubleshooting
 
 ### Issue: Locale not detected
+
 **Solution**: Check Accept-Language header or NEXT_LOCALE cookie
 
 ### Issue: Redirect loop
+
 **Solution**: Ensure public paths are correctly configured in middleware
 
 ### Issue: Type errors
+
 **Solution**: Run `pnpm typecheck` to verify TypeScript configuration
 
 ## References

+ 3 - 1
src/lib/utils/zod-i18n.ts

@@ -38,7 +38,9 @@ import { ERROR_CODES, zodErrorToCode } from "./error-messages";
  * const t = useTranslations("errors");
  * setZodErrorMap(t);
  */
-export function setZodErrorMap(t: (key: string, params?: Record<string, string | number>) => string): void {
+export function setZodErrorMap(
+  t: (key: string, params?: Record<string, string | number>) => string
+): void {
   // @ts-expect-error - Zod v4 type definition issue with error map signature
   z.setErrorMap((issue: z.ZodIssue, _ctx: { defaultError: string; data: unknown }) => {
     // Convert Zod error to our error code system