chore: post-i18n cleanup and version bump to 1.5.0 (#433)
Some checks are pending
Development Build / extract-version (push) Waiting to run
Development Build / test-build (push) Blocked by required conditions
Development Build / summary (push) Blocked by required conditions

* 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:
Luis Novo 2026-01-15 14:20:13 -03:00 committed by GitHub
parent 67dd85c928
commit b7ff0ccfe9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 242 additions and 12 deletions

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View 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
```

View file

@ -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
View file

@ -2375,7 +2375,7 @@ wheels = [
[[package]]
name = "open-notebook"
version = "1.4.0"
version = "1.5.0"
source = { editable = "." }
dependencies = [
{ name = "ai-prompter" },