feat(i18n): bilingual UI (Russian + English) + language selector
Все UI-строки приложения переведены и переключаются на лету через
Settings → Язык интерфейса.
== i18n архитектура ==
- src/renderer/src/i18n/dict.ts — плоский словарь ru/en с ~190 ключами,
поддержка интерполяции {var} и плюрализации
- src/renderer/src/i18n/index.ts — useT() React hook + чистые
translate/translateN функции (для ReminderApp вне store context)
- Settings.language: 'ru' | 'en', default 'ru'
- Изменение языка применяется немедленно через Zustand reactive update
== Что переведено ==
- Sidebar nav + slogan + status
- Titlebar window controls (aria-labels)
- Dashboard: hero, 3 stat-карточки (Активных / До следующего /
Трекинг матчей), Paused banner, empty state
- Exercises: hero, секции (активные / выключенные), row meta, empty
- Challenges: hero, formula subtitle, warning, row format
«{stat} × {mult} → {exercise}», empty
- Games: hero, status badges (Live/Ready/Queued/Installed/Not found),
queued/no_user banners, dev panel
- Settings: все секции + новый Language selector
- UpdaterCard: все состояния (checking/available/downloading/
downloaded/error/idle) с интерполяцией версии и MB/s
- ReminderApp: kicker «Время тренировки», reps подпись, snooze label
с динамическими минутами, кнопки done/skip
- Match summary: победа/поражение, плюрализация «N челлендж/-а/-ей»
vs «N challenge/-s»
- Format helpers (formatCountdown, formatInterval) — теперь принимают
Language параметр
== Локалезависимая дата ==
Dashboard hero показывает today в текущей локали:
ru-RU → "воскресенье, 17 мая"
en-US → "Sunday, May 17"
== STAT_LABELS bilingual ==
- shared/types.ts: STAT_LABELS_EN + statLabel(stat, lang) helper
- ChallengeResult получил поле stat?: GameStat (для resolve на стороне
renderer'а с актуальным языком, вместо baked-in label)
- main/games/registry.ts кладёт stat в результат
== Тесты ==
- src/renderer/src/i18n/i18n.test.ts: 10 кейсов
* translate: lookup, fallback, interpolation, multi-var, lang fallback
* translateN: ru plural rules (1/21/101 → one; 2-4 → few; 0/5-20 → many)
и en (1 → one, else → many)
- Всего 33 теста зелёные
== Известное ограничение ==
SAMPLE_EXERCISES (5-6 русских "Приседания / Отжимания / ...") остаются
русскими — это seed данных на первый запуск. Английский юзер сразу
переключит язык и сможет переименовать вручную. Делать seed-per-locale
оверкилл — слишком много кода ради малого.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ export type Exercise = {
|
||||
|
||||
export type NotificationMode = 'toast' | 'modal' | 'both'
|
||||
export type Theme = 'light' | 'dark' | 'system'
|
||||
export type Language = 'ru' | 'en'
|
||||
|
||||
export type Settings = {
|
||||
globalEnabled: boolean
|
||||
@@ -20,6 +21,7 @@ export type Settings = {
|
||||
minimizeToTray: boolean
|
||||
startMinimized: boolean
|
||||
theme: Theme
|
||||
language: Language
|
||||
snoozeMinutes: number
|
||||
}
|
||||
|
||||
@@ -71,6 +73,19 @@ export const STAT_LABELS: Record<GameStat, string> = {
|
||||
duration_min: 'минут матча'
|
||||
}
|
||||
|
||||
export const STAT_LABELS_EN: Record<GameStat, string> = {
|
||||
deaths: 'deaths',
|
||||
kills: 'kills',
|
||||
assists: 'assists',
|
||||
last_hits: 'last hits',
|
||||
denies: 'denies',
|
||||
duration_min: 'match minutes'
|
||||
}
|
||||
|
||||
export function statLabel(stat: GameStat, lang: Language): string {
|
||||
return (lang === 'en' ? STAT_LABELS_EN : STAT_LABELS)[stat]
|
||||
}
|
||||
|
||||
export type Challenge = {
|
||||
id: string
|
||||
name: string
|
||||
@@ -103,7 +118,10 @@ export type ChallengeResult = {
|
||||
exerciseName: string
|
||||
reps: number
|
||||
statValue: number
|
||||
/** Pre-localised label for backward compat; renderer prefers `stat`. */
|
||||
statLabel: string
|
||||
/** Stat key; renderer uses this to localise on demand. */
|
||||
stat?: GameStat
|
||||
}
|
||||
|
||||
export type MatchSummary = {
|
||||
@@ -122,6 +140,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
minimizeToTray: true,
|
||||
startMinimized: false,
|
||||
theme: 'light',
|
||||
language: 'ru',
|
||||
snoozeMinutes: 5
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user