Add Russian i18n with DB translations, locale API, and curator review UI.

UI chrome via react-i18next, catalog text in entity_translations with ru.wikipedia seeding, locale-aware search, and Translations page for publish workflow.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-15 11:51:46 +03:00
co-authored by Cursor
parent f247b418d8
commit ca58c43648
51 changed files with 2252 additions and 101 deletions
+28
View File
@@ -171,6 +171,32 @@ Movements are filtered to those with at least one artist active in the requested
---
## Locale (`?locale=ru`)
Public catalog endpoints accept optional **`locale`** query param (`en` default, `ru` supported) or `Accept-Language: ru`.
Affected routes: `/api/catalog/bootstrap`, `/api/timeline`, `/api/search`, `/api/artists`, `/api/artists/:id`, `/api/paintings/:id`, `/api/movements/:id/gallery`, `/api/movements/:id/artists`, `/api/artists/:id/navigation`.
Responses include `"locale": "ru"` when resolved. Display field names are unchanged; values come from `entity_translations` when `status = published`, else canonical English.
Full guide: [i18n-russian.md](i18n-russian.md).
---
## Translations (curator)
Requires curator session. Base path: `/api/translations`.
| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/api/translations/coverage?locale=ru` | Coverage stats |
| `GET` | `/api/translations/worklist/:entityType?locale=ru` | Artists/paintings/movements with translation status |
| `GET` | `/api/translations/:entityType/:id` | Canonical + all translation rows |
| `PUT` | `/api/translations/:entityType/:id` | Upsert fields `{ locale, fields, status }` |
| `POST` | `/api/translations/:entityType/:id/publish` | Publish all draft/reviewed rows for locale |
---
## `GET /api/search`
Public catalog search over **artists**, **paintings**, and **art movements**. Used by the timeline header search bar (`CatalogSearchBar.tsx`).
@@ -182,6 +208,7 @@ Public catalog search over **artists**, **paintings**, and **art movements**. Us
| `q` | string | — | Search text (min **2** characters after trim; shorter returns `{ q, results: [] }`) |
| `limit` | int | 20 | Max results total (capped at **50**) |
| `types` | string | all | Optional comma list: `artist`, `painting`, `movement` |
| `locale` | string | `en` | `ru` — search and return published Russian aliases when available |
**Matching (case-insensitive `ILIKE`):**
@@ -190,6 +217,7 @@ Public catalog search over **artists**, **paintings**, and **art movements**. Us
| Artist | `name`, `wikipedia_title`, movement name |
| Movement | movement `name`, era name |
| Painting | `title`, `wikipedia_title`, `year` (as text), artist name, movement name |
| All (when `locale=ru`) | Published rows in `entity_translations` for `name` / `title` |
Prefix matches on primary labels (`name` / `title`) rank before substring matches.
+1 -1
View File
@@ -1,6 +1,6 @@
this file contains draft for future releases and features
1. Multy language support, russian version at least, search for best implementation, preferably story in db and easily expandable. tool to check and correct translation
1. ~~Multi language support, russian version at least~~ — done: UI i18n (EN/RU) + `entity_translations` DB + curator Translations tool — [i18n-russian.md](i18n-russian.md)
2. tool to manage links (influence/influenced by ) import csv's ( define format), edit ,add, delete, visualize, map to pictures/ entities
3. tool to monitor/manage (plan actions) of curator actions, markers to check painting/text ?
4. ~~tool to sync prod /env resources (both ways), db structure, db data, images, users etc~~ — done for catalog DB + images: `npm run harmonize` (schema dev→prod only; users/audit excluded) — [harmonize-dev-prod.md](harmonize-dev-prod.md)
+1 -1
View File
@@ -107,7 +107,7 @@ Reports are written to `db/SyncReports/harmonize_db_*.json` and `harmonize_image
Processed in FK order:
`historical_eras` → `art_movements` → `artists` → `artist_periods` → `paintings` → `painting_influences` → `painting_influence_sources` → `painting_annotations`
`historical_eras` → `art_movements` → `artists` → `artist_periods` → `paintings` → `painting_influences` → `painting_influence_sources` → `painting_annotations` → **`entity_translations`**
---
+105
View File
@@ -0,0 +1,105 @@
# Russian localization (i18n)
Full Russian support: **UI chrome** via `react-i18next`, **catalog text** via PostgreSQL `entity_translations`, with Cyrillic display aliases where available and English fallback.
---
## Architecture
| Layer | English | Russian |
|-------|---------|---------|
| UI labels, buttons | `client/src/locales/en/*.json` | `client/src/locales/ru/*.json` |
| Names / titles (display) | canonical DB columns | `entity_translations` (`name`, `title`) |
| Bios, notes, descriptions | canonical DB columns | `entity_translations` (`bio_full`, `body`, `notes`, …) |
English remains canonical in main tables. Russian rows use `status`: `draft``reviewed``published`. Public API returns only **`published`** (unless curator preview).
---
## User-facing locale switch
Timeline header: **EN | RU** toggle (`LocaleSwitcher`).
- Persists `gallery_locale` in `localStorage`
- Sets `document.documentElement.lang`
- Passes `?locale=ru` on catalog API requests
- Refetches bootstrap catalog when locale changes
---
## Setup (dev)
```powershell
npm run dev:migrate # includes migrate-i18n.sql
npm run dev:fetch-artist-bios-ru # draft bios + Cyrillic names from ru.wikipedia
# Curator: Translations page → review → Publish
npm run dev:import-translations -- --file path/to/translations.json --publish
```
Optional manual import JSON:
```json
[
{ "entity_type": "painting", "entity_id": 42, "field_name": "title", "value": "Джоконда", "status": "published", "source": "manual" }
]
```
CSV: `entity_type,entity_id,field_name,value,status,source`
---
## Curator translation review
1. Sign in as curator
2. Header → **Translations** (or **Переводы** in RU UI)
3. Pick entity type (artist / painting / movement)
4. Select row → edit Russian fields side-by-side with English canonical
5. **Publish** saves and marks rows `published`
Coverage stats show artists with `bio_full`, paintings with `title` alias, draft vs published counts.
API (curator-only): see [API.md](API.md#translations-curator).
---
## Translatable fields (v1)
| entity_type | fields |
|-------------|--------|
| `era`, `movement` | `name`, `description` |
| `artist` | `name`, `bio_short`, `bio_full` |
| `artist_period` | `name`, `description` |
| `painting` | `title`, `description` |
| `annotation` | `label`, `body` |
| `influence_source` | `notes`, `aspects`, `quote`, `period_note` |
---
## API locale
Public endpoints accept `?locale=ru` or `Accept-Language: ru`. Responses include `"locale": "ru"` on catalog payloads; field names unchanged — values are already resolved.
Search matches canonical text **or** published Russian aliases.
---
## Prod rollout
1. `npm run dev:migrate` on dev; prod schema: `npm run harmonize:schema` (dev → prod only)
2. Seed Russian on dev: `npm run dev:fetch-artist-bios-ru`
3. Curator review + publish
4. `npm run harmonize` to sync `entity_translations` to prod (or full promote if preferred)
`entity_translations` is included in [`harmonize-db.js`](../scripts/harmonize-db.js) catalog sync.
---
## npm scripts
| Script | Purpose |
|--------|---------|
| `dev:migrate:i18n` | Apply `entity_translations` table only |
| `dev:fetch-artist-bios-ru` | Fetch ru.wikipedia bios into translations (draft) |
| `dev:import-translations` | Import JSON/CSV translation rows |
See also [setup.md](setup.md), [environments.md](environments.md), [harmonize-dev-prod.md](harmonize-dev-prod.md).
+4 -1
View File
@@ -89,6 +89,9 @@ npm run dev:update-influences # painting influence graph for detail vie
npm run dev:migrate:checkup-flags # optional: review/fixed flags for Checkup page (paintings)
npm run dev:migrate:artist-checkup-flags # optional: same flags for artist portraits (bio debug)
npm run dev:migrate:search # optional on very old DBs — also applied by dev:migrate / prod Step 4
npm run dev:migrate:i18n # optional — entity_translations (also in dev:migrate)
npm run dev:fetch-artist-bios-ru # draft Russian bios + Cyrillic names from ru.wikipedia
npm run dev:import-translations -- --file path/to/file.json # bulk translation import
npm run dev:migrate:painting-annotations # optional: art-history notes table
npm run dev:update-painting-annotations # optional: load curated notes (+ --wikipedia for Wikipedia intros)
npm run dev:fetch-images -- --limit=50 # random sample; 10s max per painting (default)
@@ -117,7 +120,7 @@ Image fetch can take hours if you run it for the entire catalog. The first line
| `npm run dev:server` | API with nodemon reload (local HMR workflow) |
| `npm run dev:client` | Vite dev server on :5173 |
See [environments.md](environments.md) for dev/prod URLs, database split, Docker deploy, and sync commands. For incremental dev ↔ prod merge (both sides edited), see [harmonize-dev-prod.md](harmonize-dev-prod.md). Quick reference: [FAC.md](FAC.md).
See [environments.md](environments.md) for dev/prod URLs, database split, Docker deploy, and sync commands. For Russian UI + catalog text, see [i18n-russian.md](i18n-russian.md). Quick reference: [FAC.md](FAC.md).
**Production frontend:** build the client, then start the server: