fix: use getApiErrorMessage in use-settings and update CLAUDE.md docs
- Replace getApiErrorKey with getApiErrorMessage in use-settings.ts
so error toasts show translated messages instead of raw i18n keys
- Update CLAUDE.md files to reflect the new t('section.key') pattern
and remove outdated Proxy-related gotchas
This commit is contained in:
parent
98a528158a
commit
e2cf35060b
4 changed files with 31 additions and 23 deletions
|
|
@ -64,8 +64,8 @@ User interactions trigger mutations/queries via hooks, which communicate with th
|
||||||
#### `lib/locales/` — Internationalization (i18n)
|
#### `lib/locales/` — Internationalization (i18n)
|
||||||
- **Locale files** (`en-US/`, `pt-BR/`, `zh-CN/`, `zh-TW/`, `ja-JP/`): Translation strings organized by feature
|
- **Locale files** (`en-US/`, `pt-BR/`, `zh-CN/`, `zh-TW/`, `ja-JP/`): Translation strings organized by feature
|
||||||
- **`i18n.ts`**: i18next configuration with language detection
|
- **`i18n.ts`**: i18next configuration with language detection
|
||||||
- **`use-translation.ts`**: Custom hook with Proxy-based `t.section.key` access pattern
|
- **`use-translation.ts`**: Thin wrapper around react-i18next's `useTranslation` with language change events
|
||||||
- **Pattern**: Components call `useTranslation()` hook; access strings via `t.common.save`, `t.notebooks.title`
|
- **Pattern**: Components call `useTranslation()` hook; access strings via `t('common.save')`, `t('notebooks.title')`
|
||||||
|
|
||||||
## Data & Control Flow Walkthrough
|
## Data & Control Flow Walkthrough
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ React hooks for API data fetching, state management, and complex workflows (chat
|
||||||
- **Streaming hooks** (`useAsk`): SSE parsing for multi-stage Ask workflows (strategy → answers → final answer)
|
- **Streaming hooks** (`useAsk`): SSE parsing for multi-stage Ask workflows (strategy → answers → final answer)
|
||||||
- **Model/config hooks** (`useModels`, `useSettings`, `useTransformations`): Application-level settings and model management
|
- **Model/config hooks** (`useModels`, `useSettings`, `useTransformations`): Application-level settings and model management
|
||||||
- **Utility hooks** (`useMediaQuery`, `useToast`, `useNavigation`, `useAuth`): UI state and auth checking
|
- **Utility hooks** (`useMediaQuery`, `useToast`, `useNavigation`, `useAuth`): UI state and auth checking
|
||||||
- **i18n hook** (`useTranslation`): Proxy-based translation access with `t.section.key` pattern and language switching
|
- **i18n hook** (`useTranslation`): Thin wrapper around react-i18next with `t('section.key')` pattern and language switching
|
||||||
|
|
||||||
## Important Patterns
|
## Important Patterns
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ React hooks for API data fetching, state management, and complex workflows (chat
|
||||||
- **SSE streaming pattern**: `useAsk` manually parses newline-delimited JSON from `/api/search/ask`; handles incomplete buffers
|
- **SSE streaming pattern**: `useAsk` manually parses newline-delimited JSON from `/api/search/ask`; handles incomplete buffers
|
||||||
- **Status polling**: `useSourceStatus` auto-refetches every 2s while `status === 'running' | 'queued' | 'new'`
|
- **Status polling**: `useSourceStatus` auto-refetches every 2s while `status === 'running' | 'queued' | 'new'`
|
||||||
- **Context building**: `useNotebookChat.buildContext()` assembles selected sources + notes with token/char counts
|
- **Context building**: `useNotebookChat.buildContext()` assembles selected sources + notes with token/char counts
|
||||||
- **i18n Proxy pattern**: `useTranslation` returns `t` object with Proxy; access `t.section.key` instead of `t('section.key')`
|
- **i18n pattern**: `useTranslation` returns standard react-i18next `t` function; access translations via `t('section.key')`
|
||||||
|
|
||||||
## Key Dependencies
|
## Key Dependencies
|
||||||
|
|
||||||
|
|
@ -49,8 +49,7 @@ React hooks for API data fetching, state management, and complex workflows (chat
|
||||||
- **Status polling race**: `useSourceStatus` may refetch stale data before server catches up; retry logic has 3-attempt limit
|
- **Status polling race**: `useSourceStatus` may refetch stale data before server catches up; retry logic has 3-attempt limit
|
||||||
- **Keyboard trap in dialogs**: Some hooks manage modal state; ensure Dialog/Modal components handle escape key properly
|
- **Keyboard trap in dialogs**: Some hooks manage modal state; ensure Dialog/Modal components handle escape key properly
|
||||||
- **Form data handling**: `useFileUpload` and source creation convert JSON fields to strings in FormData
|
- **Form data handling**: `useFileUpload` and source creation convert JSON fields to strings in FormData
|
||||||
- **useTranslation depth limit**: Proxy limits nesting to 4 levels; deeper access returns path string as fallback
|
- **useTranslation**: Thin wrapper preserving `setLanguage` with language change events for `LanguageLoadingOverlay`
|
||||||
- **useTranslation loop detection**: >1000 accesses to same key in 1s triggers error and breaks recursion
|
|
||||||
|
|
||||||
## Testing Patterns
|
## Testing Patterns
|
||||||
|
|
||||||
|
|
@ -187,7 +186,7 @@ function CredentialSettings() {
|
||||||
### Important Notes
|
### Important Notes
|
||||||
|
|
||||||
- **Toast notifications**: All mutations show success/error toasts automatically
|
- **Toast notifications**: All mutations show success/error toasts automatically
|
||||||
- **i18n integration**: Toast messages use translation keys from `t.apiKeys.*` and `t.common.*`
|
- **i18n integration**: Toast messages use translation keys from `t('apiKeys.*')` and `t('common.*')`
|
||||||
- **Error handling**: Uses `getApiErrorKey()` utility to extract error messages from API responses
|
- **Error handling**: Uses `getApiErrorKey()` utility to extract error messages from API responses
|
||||||
- **Local test results**: `useTestCredential` stores results in local state (not cached in TanStack Query)
|
- **Local test results**: `useTestCredential` stores results in local state (not cached in TanStack Query)
|
||||||
- **Migration feedback**: Migration hooks show different toasts based on migrated/skipped/error counts
|
- **Migration feedback**: Migration hooks show different toasts based on migrated/skipped/error counts
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { settingsApi } from '@/lib/api/settings'
|
||||||
import { QUERY_KEYS } from '@/lib/api/query-client'
|
import { QUERY_KEYS } from '@/lib/api/query-client'
|
||||||
import { useToast } from '@/lib/hooks/use-toast'
|
import { useToast } from '@/lib/hooks/use-toast'
|
||||||
import { useTranslation } from '@/lib/hooks/use-translation'
|
import { useTranslation } from '@/lib/hooks/use-translation'
|
||||||
import { getApiErrorKey } from '@/lib/utils/error-handler'
|
import { getApiErrorMessage } from '@/lib/utils/error-handler'
|
||||||
import { SettingsResponse } from '@/lib/types/api'
|
import { SettingsResponse } from '@/lib/types/api'
|
||||||
|
|
||||||
export function useSettings() {
|
export function useSettings() {
|
||||||
|
|
@ -30,7 +30,7 @@ export function useUpdateSettings() {
|
||||||
onError: (error: unknown) => {
|
onError: (error: unknown) => {
|
||||||
toast({
|
toast({
|
||||||
title: t('common.error'),
|
title: t('common.error'),
|
||||||
description: getApiErrorKey(error, t('common.error')),
|
description: getApiErrorMessage(error, (key) => t(key), 'common.error'),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Locales Module (i18n)
|
# Locales Module (i18n)
|
||||||
|
|
||||||
Internationalization system providing multi-language UI support using i18next with type-safe translation access.
|
Internationalization system providing multi-language UI support using i18next with standard `t()` function calls.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ lib/
|
||||||
├── i18n.ts # i18next initialization and configuration
|
├── i18n.ts # i18next initialization and configuration
|
||||||
├── i18n-events.ts # Language change event emitters
|
├── i18n-events.ts # Language change event emitters
|
||||||
├── hooks/
|
├── hooks/
|
||||||
│ └── use-translation.ts # Custom hook with Proxy-based API
|
│ └── use-translation.ts # Thin wrapper around react-i18next with language change events
|
||||||
├── utils/
|
├── utils/
|
||||||
│ └── date-locale.ts # date-fns locale mapping
|
│ └── date-locale.ts # date-fns locale mapping
|
||||||
└── locales/
|
└── locales/
|
||||||
|
|
@ -28,7 +28,7 @@ lib/
|
||||||
- **`i18n.ts`**: i18next initialization with language detection (localStorage → browser)
|
- **`i18n.ts`**: i18next initialization with language detection (localStorage → browser)
|
||||||
- **`i18n-events.ts`**: Event emitters for language change start/end (used by loading overlay)
|
- **`i18n-events.ts`**: Event emitters for language change start/end (used by loading overlay)
|
||||||
- **`locales/index.ts`**: Central registry exporting all locales and `LanguageCode` type
|
- **`locales/index.ts`**: Central registry exporting all locales and `LanguageCode` type
|
||||||
- **`use-translation.ts`**: Custom hook providing `t` object with nested property access
|
- **`use-translation.ts`**: Thin wrapper around react-i18next returning `{ t, i18n, language, setLanguage }`
|
||||||
|
|
||||||
## Translation Structure
|
## Translation Structure
|
||||||
|
|
||||||
|
|
@ -67,24 +67,36 @@ import { useTranslation } from '@/lib/hooks/use-translation'
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
const { t, language, setLanguage } = useTranslation()
|
const { t, language, setLanguage } = useTranslation()
|
||||||
|
|
||||||
// Nested property access (Proxy-based)
|
// Standard t() function call
|
||||||
return <h1>{t.notebooks.title}</h1>
|
return <h1>{t('notebooks.title')}</h1>
|
||||||
|
|
||||||
// With interpolation
|
// With string interpolation
|
||||||
return <p>{t.common.updated.replace('{time}', timeAgo)}</p>
|
return <p>{t('common.updated').replace('{time}', timeAgo)}</p>
|
||||||
|
|
||||||
// Change language
|
// Change language
|
||||||
await setLanguage('zh-CN')
|
await setLanguage('zh-CN')
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Functions that accept t as a parameter
|
||||||
|
|
||||||
|
Use `TFunction` from i18next:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { TFunction } from 'i18next'
|
||||||
|
|
||||||
|
const getNavigation = (t: TFunction) => [
|
||||||
|
{ name: t('navigation.sources'), href: '/sources' },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## Important Patterns
|
## Important Patterns
|
||||||
|
|
||||||
- **Proxy-based access**: `t.section.key` instead of `t('section.key')` for better DX
|
- **Standard t() calls**: `t('section.key')` — standard react-i18next pattern
|
||||||
- **Type safety**: `TranslationKeys` type derived from `enUS` locale
|
|
||||||
- **Language persistence**: Saved to localStorage, auto-detected on load
|
- **Language persistence**: Saved to localStorage, auto-detected on load
|
||||||
- **Fallback**: Falls back to `en-US` if key missing in current locale
|
- **Fallback**: Falls back to `en-US` if key missing in current locale
|
||||||
- **Date localization**: Use `getDateLocale(language)` from `utils/date-locale.ts`
|
- **Date localization**: Use `getDateLocale(language)` from `utils/date-locale.ts`
|
||||||
|
- **Language change events**: `setLanguage` emits start/end events for `LanguageLoadingOverlay`
|
||||||
|
|
||||||
## Key Dependencies
|
## Key Dependencies
|
||||||
|
|
||||||
|
|
@ -117,13 +129,10 @@ function MyComponent() {
|
||||||
|
|
||||||
## Important Quirks & Gotchas
|
## Important Quirks & Gotchas
|
||||||
|
|
||||||
- **Proxy depth limit**: `useTranslation` limits nesting to 4 levels to prevent infinite loops
|
|
||||||
- **Blocked properties**: React internals (`__proto__`, `$$typeof`, etc.) are blocked from Proxy traversal
|
|
||||||
- **Loop detection**: Access counts reset every 1s; >1000 accesses triggers error and breaks recursion
|
|
||||||
- **String methods**: `.replace()`, `.split()` work on translated strings via Proxy magic
|
|
||||||
- **Language change events**: `emitLanguageChangeStart/End` used by `LanguageLoadingOverlay` for UX
|
- **Language change events**: `emitLanguageChangeStart/End` used by `LanguageLoadingOverlay` for UX
|
||||||
- **No SSR**: `useSuspense: false` disables React Suspense for i18next (avoids hydration issues)
|
- **No SSR**: `useSuspense: false` disables React Suspense for i18next (avoids hydration issues)
|
||||||
- **All keys required**: Missing keys in non-English locales fall back to English; keep locales in sync
|
- **All keys required**: Missing keys in non-English locales fall back to English; keep locales in sync
|
||||||
|
- **ErrorBoundary**: Uses raw `enUS` locale object directly (class component, can't use hooks)
|
||||||
|
|
||||||
## Testing Patterns
|
## Testing Patterns
|
||||||
|
|
||||||
|
|
@ -131,7 +140,7 @@ function MyComponent() {
|
||||||
// Mock useTranslation in tests (see test/setup.ts)
|
// Mock useTranslation in tests (see test/setup.ts)
|
||||||
vi.mock('@/lib/hooks/use-translation', () => ({
|
vi.mock('@/lib/hooks/use-translation', () => ({
|
||||||
useTranslation: () => ({
|
useTranslation: () => ({
|
||||||
t: enUS, // Use English locale directly
|
t: (key: string) => key, // Identity function returns the key
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
setLanguage: vi.fn(),
|
setLanguage: vi.fn(),
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue