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
+7 -5
View File
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useState, type SyntheticEvent } from 'react';
import { useTranslation } from 'react-i18next';
import type { InfluenceLink, Painting, PaintingDetail } from '../types';
import { api, debugImageProxyUrl, imageUrl, paintingImageUrl, type DebugImageSearchResult, type DebugImageSearchResultItem, type FixPaintingImageResult } from '../api/client';
import DebugSearchResultsModal from './DebugSearchResultsModal';
@@ -204,6 +205,7 @@ export default function PaintingDetailView({
onPaintingCheckupFlagsUpdated,
onPaintingRemoved,
}: Props) {
const { t } = useTranslation('painting');
const { painting, influencedBy, influenced, annotations = [] } = data;
const [fullscreen, setFullscreen] = useState(false);
const [imageVersion, setImageVersion] = useState(0);
@@ -474,15 +476,15 @@ export default function PaintingDetailView({
</p>
</div>
<button className="bio-btn" onClick={onArtistBio}>
About {painting.artist_name}
{t('aboutArtist', { name: painting.artist_name })}
</button>
</header>
<div className="painting-layout">
<aside className="influence-panel influence-left">
<h3>Influenced By</h3>
<h3>{t('influencedBy')}</h3>
{influencedBy.length === 0 ? (
<p className="no-influences">No documented influences for this work.</p>
<p className="no-influences">{t('noInfluences')}</p>
) : (
<div className="influence-list">
{influencedBy.map((inf, index) => (
@@ -575,9 +577,9 @@ export default function PaintingDetailView({
</main>
<aside className="influence-panel influence-right">
<h3>Influenced</h3>
<h3>{t('influenced')}</h3>
{influenced.length === 0 ? (
<p className="no-influences">No documented works influenced by this painting yet.</p>
<p className="no-influences">{t('noInfluencedWorks')}</p>
) : (
<div className="influence-list">
{influenced.map((inf, index) => (