mirror of
https://github.com/lfnovo/open-notebook.git
synced 2026-04-28 03:19:59 +00:00
chore: post-i18n cleanup and version bump to 1.5.0 (#433)
* chore: post-i18n cleanup and version bump to 1.5.0
- Restore missing .dockerignore entries (notebook_data, surreal_data, docs, etc.)
- Fix lint command for Next.js 16 (use eslint directly instead of next lint)
- Remove aria-describedby={undefined} causing Radix UI warnings
- Bump version to 1.5.0
- Update CHANGELOG with i18n features
- Add multi-language UI mention to README
- Add i18n contribution guide to README.dev
- Document i18n system in CLAUDE.md files
Closes #344, #349, #360
* docs: fix provider order in CLAUDE.md to match layout.tsx
This commit is contained in:
parent
67dd85c928
commit
b7ff0ccfe9
11 changed files with 242 additions and 12 deletions
|
|
@ -23,13 +23,30 @@ frontend/out
|
|||
frontend/.env*
|
||||
frontend/*.log
|
||||
|
||||
# Project
|
||||
# Project data
|
||||
.antigravity
|
||||
.gemini
|
||||
tmp
|
||||
data
|
||||
mydata
|
||||
notebook_data
|
||||
surreal_data
|
||||
surreal-data
|
||||
surreal_single_data
|
||||
*.db
|
||||
*.log
|
||||
docker.env
|
||||
.env
|
||||
.env
|
||||
docker-compose*
|
||||
|
||||
# Documentation & CI (not needed in image)
|
||||
docs
|
||||
.github
|
||||
|
||||
# IDE and OS files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.5.0] - 2026-01-15
|
||||
|
||||
### Added
|
||||
- Internationalization (i18n) support with Chinese (Simplified and Traditional) translations (#371, closes #344, #349, #360)
|
||||
- Frontend test infrastructure with Vitest (#371)
|
||||
- Language toggle component for switching UI language (#371)
|
||||
- Date localization using date-fns locales (#371)
|
||||
- Error message translation system (#371)
|
||||
|
||||
### Fixed
|
||||
- Accessibility improvements: added missing `id`, `name`, and `autoComplete` attributes to form inputs (#371)
|
||||
- Added `DialogDescription` to dialogs for Radix UI accessibility compliance (#371)
|
||||
- Fixed "Collapsible is changing from uncontrolled to controlled" warning in SettingsForm (#371)
|
||||
- Fixed lint command for Next.js 16 compatibility (`eslint` instead of `next lint`)
|
||||
|
||||
### Changed
|
||||
- Dockerfile optimizations: better layer caching, `--no-install-recommends` for smaller images (#371)
|
||||
- Dockerfile.single refactored into 3 separate build stages for better caching (#371)
|
||||
|
||||
## [1.4.0] - 2026-01-14
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -244,6 +244,47 @@ uv sync
|
|||
cd frontend && npm install package-name
|
||||
```
|
||||
|
||||
### Adding a New Language (i18n)
|
||||
|
||||
Open Notebook supports internationalization. To add a new language:
|
||||
|
||||
1. **Create locale file**: Copy an existing locale as template
|
||||
```bash
|
||||
cp frontend/src/lib/locales/en-US/index.ts frontend/src/lib/locales/pt-BR/index.ts
|
||||
```
|
||||
|
||||
2. **Translate all strings** in the new file. The structure includes:
|
||||
- `common`: Shared UI elements (buttons, labels)
|
||||
- `notebooks`, `sources`, `notes`: Feature-specific strings
|
||||
- `chat`, `search`, `podcasts`: Module-specific strings
|
||||
- `apiErrors`: Error message translations
|
||||
|
||||
3. **Register the locale** in `frontend/src/lib/locales/index.ts`:
|
||||
```typescript
|
||||
import { ptBR } from './pt-BR'
|
||||
|
||||
export const locales = {
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW,
|
||||
'pt-BR': ptBR, // Add your locale
|
||||
}
|
||||
```
|
||||
|
||||
4. **Add date-fns locale** in `frontend/src/lib/utils/date-locale.ts`:
|
||||
```typescript
|
||||
import { zhCN, enUS, zhTW, ptBR } from 'date-fns/locale'
|
||||
|
||||
const LOCALE_MAP: Record<string, Locale> = {
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW,
|
||||
'en-US': enUS,
|
||||
'pt-BR': ptBR, // Add your locale
|
||||
}
|
||||
```
|
||||
|
||||
5. **Test**: Switch languages using the language toggle in the UI header.
|
||||
|
||||
### Database Migrations
|
||||
|
||||
Database migrations run **automatically** when the API starts.
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ In a world dominated by Artificial Intelligence, having the ability to think
|
|||
- 🎙️ **Generate professional podcasts** - Advanced multi-speaker podcast generation
|
||||
- 🔍 **Search intelligently** - Full-text and vector search across all your content
|
||||
- 💬 **Chat with context** - AI conversations powered by your research
|
||||
- 🌐 **Multi-language UI** - Chinese (Simplified & Traditional) support, more languages coming soon!
|
||||
|
||||
Learn more about our project at [https://www.open-notebook.ai](https://www.open-notebook.ai)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "node start-server.js",
|
||||
"lint": "next lint",
|
||||
"lint": "eslint src/",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ User interactions trigger mutations/queries via hooks, which communicate with th
|
|||
- **`lib/api/CLAUDE.md`**: Axios client, FormData handling, interceptors
|
||||
- **`lib/hooks/CLAUDE.md`**: TanStack Query wrappers, SSE streaming, context building
|
||||
- **`lib/stores/CLAUDE.md`**: Zustand auth/modal state, localStorage persistence
|
||||
- **`lib/locales/CLAUDE.md`**: Internationalization (i18n) system, translation files
|
||||
- **`components/ui/CLAUDE.md`**: Radix UI primitives, CVA styling, accessibility
|
||||
|
||||
## Architectural Layers
|
||||
|
|
@ -60,6 +61,12 @@ User interactions trigger mutations/queries via hooks, which communicate with th
|
|||
- API request/response shapes, domain models (Notebook, Source, Note, etc.)
|
||||
- Ensures type safety across API calls and store mutations
|
||||
|
||||
#### `lib/locales/` — Internationalization (i18n)
|
||||
- **Locale files** (`en-US/`, `zh-CN/`, `zh-TW/`): 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`
|
||||
|
||||
## Data & Control Flow Walkthrough
|
||||
|
||||
### Example: Notebook Chat
|
||||
|
|
@ -123,12 +130,13 @@ User interactions trigger mutations/queries via hooks, which communicate with th
|
|||
|
||||
## Providers & Context Setup
|
||||
|
||||
**Root layout** (`app/layout.tsx`) wraps app with:
|
||||
1. `ThemeProvider` — next-themes for light/dark mode
|
||||
2. `QueryProvider` — TanStack Query client
|
||||
3. `ErrorBoundary` — React error boundary
|
||||
4. `ConnectionGuard` — checks backend connectivity on startup
|
||||
5. `Toaster` — sonner toast notification system
|
||||
**Root layout** (`app/layout.tsx`) wraps app with (outermost → innermost):
|
||||
1. `ErrorBoundary` — React error boundary (catches all render errors)
|
||||
2. `ThemeProvider` — next-themes for light/dark mode
|
||||
3. `QueryProvider` — TanStack Query client
|
||||
4. `I18nProvider` — i18next initialization and language loading overlay
|
||||
5. `ConnectionGuard` — checks backend connectivity on startup
|
||||
6. `Toaster` — sonner toast notification system (inside ConnectionGuard)
|
||||
|
||||
## Important Gotchas & Design Decisions
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ function AlertDialogContent({
|
|||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
data-slot="alert-dialog-content"
|
||||
aria-describedby={undefined}
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
|
|
|
|||
|
|
@ -10,6 +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
|
||||
|
||||
## Important Patterns
|
||||
|
||||
|
|
@ -21,6 +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')`
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
|
|
@ -47,6 +49,8 @@ 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
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
|
|
|
|||
141
frontend/src/lib/locales/CLAUDE.md
Normal file
141
frontend/src/lib/locales/CLAUDE.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# Locales Module (i18n)
|
||||
|
||||
Internationalization system providing multi-language UI support using i18next with type-safe translation access.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
lib/
|
||||
├── i18n.ts # i18next initialization and configuration
|
||||
├── i18n-events.ts # Language change event emitters
|
||||
├── hooks/
|
||||
│ └── use-translation.ts # Custom hook with Proxy-based API
|
||||
├── utils/
|
||||
│ └── date-locale.ts # date-fns locale mapping
|
||||
└── locales/
|
||||
├── index.ts # Locale registry and type exports
|
||||
├── en-US/index.ts # English translations
|
||||
├── zh-CN/index.ts # Simplified Chinese translations
|
||||
└── zh-TW/index.ts # Traditional Chinese translations
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
- **`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
|
||||
|
||||
## Translation Structure
|
||||
|
||||
Each locale file exports a flat object with nested keys:
|
||||
|
||||
```typescript
|
||||
export const enUS = {
|
||||
common: {
|
||||
save: 'Save',
|
||||
cancel: 'Cancel',
|
||||
delete: 'Delete',
|
||||
// ...
|
||||
},
|
||||
notebooks: {
|
||||
title: 'Notebooks',
|
||||
createNew: 'Create Notebook',
|
||||
// ...
|
||||
},
|
||||
// ... other sections
|
||||
}
|
||||
```
|
||||
|
||||
**Sections**:
|
||||
- `common`: Shared UI elements (buttons, labels, actions)
|
||||
- `notebooks`, `sources`, `notes`: Feature-specific strings
|
||||
- `chat`, `search`, `podcasts`: Module-specific strings
|
||||
- `models`, `transformations`, `settings`: Configuration UI
|
||||
- `advanced`: System administration strings
|
||||
- `apiErrors`: Backend error message translations
|
||||
|
||||
## Usage Pattern
|
||||
|
||||
```typescript
|
||||
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>
|
||||
|
||||
// With interpolation
|
||||
return <p>{t.common.updated.replace('{time}', timeAgo)}</p>
|
||||
|
||||
// Change language
|
||||
await setLanguage('zh-CN')
|
||||
}
|
||||
```
|
||||
|
||||
## Important Patterns
|
||||
|
||||
- **Proxy-based access**: `t.section.key` instead of `t('section.key')` for better DX
|
||||
- **Type safety**: `TranslationKeys` type derived from `enUS` locale
|
||||
- **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`
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- `i18next`: Core internationalization framework
|
||||
- `react-i18next`: React bindings for i18next
|
||||
- `i18next-browser-languagedetector`: Auto-detect browser language
|
||||
- `date-fns/locale`: Date formatting locales
|
||||
|
||||
## How to Add a New Language
|
||||
|
||||
1. Create locale folder: `locales/pt-BR/index.ts`
|
||||
2. Copy structure from `en-US/index.ts` and translate all strings
|
||||
3. Register in `locales/index.ts`:
|
||||
```typescript
|
||||
import { ptBR } from './pt-BR'
|
||||
export const resources = {
|
||||
// ...existing
|
||||
'pt-BR': { translation: ptBR },
|
||||
}
|
||||
export const languages: Language[] = [
|
||||
// ...existing
|
||||
{ code: 'pt-BR', label: 'Português' },
|
||||
]
|
||||
```
|
||||
4. Add to `utils/date-locale.ts`:
|
||||
```typescript
|
||||
import { ptBR } from 'date-fns/locale'
|
||||
const LOCALE_MAP = { ...existing, 'pt-BR': ptBR }
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
```typescript
|
||||
// Mock useTranslation in tests (see test/setup.ts)
|
||||
vi.mock('@/lib/hooks/use-translation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: enUS, // Use English locale directly
|
||||
language: 'en-US',
|
||||
setLanguage: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
// Test locale completeness
|
||||
import { enUS, zhCN } from '@/lib/locales'
|
||||
const enKeys = Object.keys(flatten(enUS))
|
||||
const zhKeys = Object.keys(flatten(zhCN))
|
||||
expect(zhKeys).toEqual(enKeys) // All keys present
|
||||
```
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "open-notebook"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
description = "An open source implementation of a research assistant, inspired by Google Notebook LM"
|
||||
authors = [
|
||||
{name = "Luis Novo", email = "lfnovo@gmail.com"}
|
||||
|
|
|
|||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -2375,7 +2375,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "open-notebook"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "ai-prompter" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue