Localise timeline and movement chart captions in English and Russian.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-08-13 12:45:44 +03:00
co-authored by Cursor
parent 44092d102b
commit 1c9fa20191
7 changed files with 25 additions and 6 deletions
+3 -1
View File
@@ -1,4 +1,5 @@
import { useMemo, useRef, useCallback, useEffect, useLayoutEffect, useState, memo } from 'react'; import { useMemo, useRef, useCallback, useEffect, useLayoutEffect, useState, memo } from 'react';
import { useTranslation } from 'react-i18next';
import type { ArtMovement, Artist } from '../types'; import type { ArtMovement, Artist } from '../types';
import { portraitThumbUrl } from '../api/client'; import { portraitThumbUrl } from '../api/client';
import { useQueuedImageSrc } from '../hooks/useQueuedImageSrc'; import { useQueuedImageSrc } from '../hooks/useQueuedImageSrc';
@@ -1695,6 +1696,7 @@ export default function MovementBands({
onArtistHover, onArtistHover,
onPortraitsLoadingChange, onPortraitsLoadingChange,
}: Props) { }: Props) {
const { t } = useTranslation('home');
const canvasRef = useRef<HTMLDivElement>(null); const canvasRef = useRef<HTMLDivElement>(null);
const pendingPortraitsRef = useRef(0); const pendingPortraitsRef = useRef(0);
const [portraitsLoading, setPortraitsLoading] = useState(false); const [portraitsLoading, setPortraitsLoading] = useState(false);
@@ -2193,7 +2195,7 @@ export default function MovementBands({
return ( return (
<div className="movements-flow"> <div className="movements-flow">
<p className="movements-flow-caption"> <p className="movements-flow-caption">
Scroll to zoom · drag to pan · each movement stream is a solid colour band through history {t('captionClassicFlow')}
</p> </p>
<div <div
+3 -2
View File
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { ArtMovement } from '../types'; import type { ArtMovement } from '../types';
import { import {
branchOriginYear, branchOriginYear,
@@ -179,6 +180,7 @@ export default function MovementTree({
onViewChange, onViewChange,
onMovementClick, onMovementClick,
}: Props) { }: Props) {
const { t } = useTranslation('home');
const canvasRef = useRef<HTMLDivElement>(null); const canvasRef = useRef<HTMLDivElement>(null);
const [canvas, setCanvas] = useState({ w: 900, h: 600 }); const [canvas, setCanvas] = useState({ w: 900, h: 600 });
const [panning, setPanning] = useState(false); const [panning, setPanning] = useState(false);
@@ -534,8 +536,7 @@ export default function MovementTree({
return ( return (
<div className="mtree"> <div className="mtree">
<p className="mtree-caption"> <p className="mtree-caption">
Roots at the bottom, living movements at the crown · scroll to zoom · drag to {t('captionTreeFlow')}
pan · click a branch to enter its gallery
</p> </p>
<div <div
ref={canvasRef} ref={canvasRef}
+3 -1
View File
@@ -1,4 +1,5 @@
import { useRef, useState, useCallback, useEffect, useMemo, useLayoutEffect } from 'react'; import { useRef, useState, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
import { useTranslation } from 'react-i18next';
import type { HistoricalEra } from '../types'; import type { HistoricalEra } from '../types';
import { import {
HISTORICAL_EVENTS, HISTORICAL_EVENTS,
@@ -38,6 +39,7 @@ function formatYear(year: number): string {
} }
export default function Timeline({ eras, viewStart, viewEnd, onViewChange, absoluteMin, absoluteMax, lifespanHighlight }: Props) { export default function Timeline({ eras, viewStart, viewEnd, onViewChange, absoluteMin, absoluteMax, lifespanHighlight }: Props) {
const { t } = useTranslation('home');
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [dragging, setDragging] = useState<'left' | 'right' | 'pan' | null>(null); const [dragging, setDragging] = useState<'left' | 'right' | 'pan' | null>(null);
const [containerWidth, setContainerWidth] = useState(800); const [containerWidth, setContainerWidth] = useState(800);
@@ -365,7 +367,7 @@ export default function Timeline({ eras, viewStart, viewEnd, onViewChange, absol
</div> </div>
<p className="timeline-hint"> <p className="timeline-hint">
Click an era or event to zoom · Scroll to zoom · Drag to pan {t('captionClassicTimeline')}
</p> </p>
</div> </div>
); );
@@ -1,4 +1,5 @@
import { useMemo, useRef, useCallback, useEffect, useLayoutEffect, useState } from 'react'; import { useMemo, useRef, useCallback, useEffect, useLayoutEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { ArtMovement } from '../types'; import type { ArtMovement } from '../types';
import { MOVEMENT_LINEAGE } from '../data/movement-lineage'; import { MOVEMENT_LINEAGE } from '../data/movement-lineage';
import { panTimelineView, zoomTimelineView } from '../utils/timelineView'; import { panTimelineView, zoomTimelineView } from '../utils/timelineView';
@@ -364,6 +365,7 @@ export default function VerticalMovementBands({
onViewChange, onViewChange,
onMovementClick, onMovementClick,
}: Props) { }: Props) {
const { t } = useTranslation('home');
const canvasRef = useRef<HTMLDivElement>(null); const canvasRef = useRef<HTMLDivElement>(null);
const [canvasSize, setCanvasSize] = useState({ w: 800, h: 600 }); const [canvasSize, setCanvasSize] = useState({ w: 800, h: 600 });
const [panning, setPanning] = useState(false); const [panning, setPanning] = useState(false);
@@ -631,7 +633,7 @@ export default function VerticalMovementBands({
return ( return (
<div className="vflow"> <div className="vflow">
<p className="vflow-caption"> <p className="vflow-caption">
Bottom top through history · scroll to zoom · drag to pan · click a stream {t('captionVerticalFlow')}
</p> </p>
<div <div
ref={canvasRef} ref={canvasRef}
+3 -1
View File
@@ -1,4 +1,5 @@
import { useRef, useState, useCallback, useEffect, useMemo, useLayoutEffect } from 'react'; import { useRef, useState, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
import { useTranslation } from 'react-i18next';
import type { HistoricalEra } from '../types'; import type { HistoricalEra } from '../types';
import { import {
HISTORICAL_EVENTS, HISTORICAL_EVENTS,
@@ -60,6 +61,7 @@ export default function VerticalTimeline({
absoluteMax, absoluteMax,
lifespanHighlight, lifespanHighlight,
}: Props) { }: Props) {
const { t } = useTranslation('home');
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [dragging, setDragging] = useState<'start' | 'end' | 'pan' | null>(null); const [dragging, setDragging] = useState<'start' | 'end' | 'pan' | null>(null);
const [containerHeight, setContainerHeight] = useState(600); const [containerHeight, setContainerHeight] = useState(600);
@@ -409,7 +411,7 @@ export default function VerticalTimeline({
/> />
</div> </div>
<p className="vtimeline-hint">Bottom top · Scroll to zoom · Drag to pan</p> <p className="vtimeline-hint">{t('captionVerticalTimeline')}</p>
</aside> </aside>
); );
} }
+5
View File
@@ -22,6 +22,11 @@
"layoutHorizontal": "Classic timeline →", "layoutHorizontal": "Classic timeline →",
"layoutVertical": "↑ Vertical timeline", "layoutVertical": "↑ Vertical timeline",
"layoutTree": "🌳 Tree of art", "layoutTree": "🌳 Tree of art",
"captionClassicTimeline": "Click an era or event to zoom · Scroll to zoom · Drag to pan",
"captionClassicFlow": "Scroll to zoom · drag to pan · each movement stream is a solid colour band through history",
"captionVerticalTimeline": "Bottom → top · Scroll to zoom · Drag to pan",
"captionVerticalFlow": "Bottom → top through history · scroll to zoom · drag to pan · click a stream",
"captionTreeFlow": "Roots at the bottom, living movements at the crown · scroll to zoom · drag to pan · click a branch to enter its gallery",
"openingTourGallery": "Opening guided tour…", "openingTourGallery": "Opening guided tour…",
"tourEmpty": "This tour has no paintings yet.", "tourEmpty": "This tour has no paintings yet.",
"tourLoadFailed": "Failed to load the tour.", "tourLoadFailed": "Failed to load the tour.",
+5
View File
@@ -22,6 +22,11 @@
"layoutHorizontal": "Классическая шкала →", "layoutHorizontal": "Классическая шкала →",
"layoutVertical": "↑ Вертикальная шкала", "layoutVertical": "↑ Вертикальная шкала",
"layoutTree": "🌳 Древо искусства", "layoutTree": "🌳 Древо искусства",
"captionClassicTimeline": "Щёлкните эпоху или событие для приближения · Колесо — масштаб · Перетащите для панорамы",
"captionClassicFlow": "Колесо — масштаб · перетащите для панорамы · каждое направление — цветная полоса сквозь историю",
"captionVerticalTimeline": "Снизу вверх · Колесо — масштаб · Перетащите для панорамы",
"captionVerticalFlow": "Снизу вверх по истории · колесо — масштаб · перетащите для панорамы · щёлкните поток",
"captionTreeFlow": "Корни внизу, живые направления в кроне · колесо — масштаб · перетащите для панорамы · щёлкните ветвь, чтобы войти в зал",
"openingTourGallery": "Открытие экскурсии…", "openingTourGallery": "Открытие экскурсии…",
"tourEmpty": "В этой экскурсии пока нет картин.", "tourEmpty": "В этой экскурсии пока нет картин.",
"tourLoadFailed": "Не удалось загрузить экскурсию.", "tourLoadFailed": "Не удалось загрузить экскурсию.",