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:
Luis Novo 2026-04-14 18:31:18 -03:00
parent 98a528158a
commit e2cf35060b
4 changed files with 31 additions and 23 deletions

View file

@ -64,8 +64,8 @@ User interactions trigger mutations/queries via hooks, which communicate with th
#### `lib/locales/` — Internationalization (i18n)
- **Locale files** (`en-US/`, `pt-BR/`, `zh-CN/`, `zh-TW/`, `ja-JP/`): Translation strings organized by feature
- **`i18n.ts`**: i18next configuration with language detection
- **`use-translation.ts`**: Custom hook with Proxy-based `t.section.key` access pattern
- **Pattern**: Components call `useTranslation()` hook; access strings via `t.common.save`, `t.notebooks.title`
- **`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')`
## Data & Control Flow Walkthrough

View file

@ -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)
- **Model/config hooks** (`useModels`, `useSettings`, `useTransformations`): Application-level settings and model management
- **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
@ -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
- **Status polling**: `useSourceStatus` auto-refetches every 2s while `status === 'running' | 'queued' | 'new'`
- **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
@ -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
- **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
- **useTranslation depth limit**: Proxy limits nesting to 4 levels; deeper access returns path string as fallback
- **useTranslation loop detection**: >1000 accesses to same key in 1s triggers error and breaks recursion
- **useTranslation**: Thin wrapper preserving `setLanguage` with language change events for `LanguageLoadingOverlay`
## Testing Patterns
@ -187,7 +186,7 @@ function CredentialSettings() {
### Important Notes
- **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
- **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

View file

@ -3,7 +3,7 @@ import { settingsApi } from '@/lib/api/settings'
import { QUERY_KEYS } from '@/lib/api/query-client'
import { useToast } from '@/lib/hooks/use-toast'
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'
export function useSettings() {
@ -30,7 +30,7 @@ export function useUpdateSettings() {
onError: (error: unknown) => {
toast({
title: t('common.error'),
description: getApiErrorKey(error, t('common.error')),
description: getApiErrorMessage(error, (key) => t(key), 'common.error'),
variant: 'destructive',
})
},

View file

@ -1,6 +1,6 @@
# 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
@ -9,7 +9,7 @@ lib/
├── i18n.ts # i18next initialization and configuration
├── i18n-events.ts # Language change event emitters
├── hooks/
│ └── use-translation.ts # Custom hook with Proxy-based API
│ └── use-translation.ts # Thin wrapper around react-i18next with language change events
├── utils/
│ └── date-locale.ts # date-fns locale mapping
└── locales/
@ -28,7 +28,7 @@ lib/
- **`i18n.ts`**: i18next initialization with language detection (localStorage → browser)
- **`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
- **`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
@ -67,24 +67,36 @@ import { useTranslation } from '@/lib/hooks/use-translation'
function MyComponent() {
const { t, language, setLanguage } = useTranslation()
// Nested property access (Proxy-based)
return <h1>{t.notebooks.title}</h1>
// Standard t() function call
return <h1>{t('notebooks.title')}</h1>
// With interpolation
return <p>{t.common.updated.replace('{time}', timeAgo)}</p>
// With string interpolation
return <p>{t('common.updated').replace('{time}', timeAgo)}</p>
// Change language
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
- **Proxy-based access**: `t.section.key` instead of `t('section.key')` for better DX
- **Type safety**: `TranslationKeys` type derived from `enUS` locale
- **Standard t() calls**: `t('section.key')` — standard react-i18next pattern
- **Language persistence**: Saved to localStorage, auto-detected on load
- **Fallback**: Falls back to `en-US` if key missing in current locale
- **Date localization**: Use `getDateLocale(language)` from `utils/date-locale.ts`
- **Language change events**: `setLanguage` emits start/end events for `LanguageLoadingOverlay`
## Key Dependencies
@ -117,13 +129,10 @@ function MyComponent() {
## 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
- **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
- **ErrorBoundary**: Uses raw `enUS` locale object directly (class component, can't use hooks)
## Testing Patterns
@ -131,7 +140,7 @@ function MyComponent() {
// Mock useTranslation in tests (see test/setup.ts)
vi.mock('@/lib/hooks/use-translation', () => ({
useTranslation: () => ({
t: enUS, // Use English locale directly
t: (key: string) => key, // Identity function returns the key
language: 'en-US',
setLanguage: vi.fn(),
}),