Files
TeleWave/frontend/src/shared/lib/i18n.ts
T

747 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
const resources = {
ru: {
translation: {
appName: 'TeleWave',
nav: {
home: 'Главная',
dashboard: 'Эфир',
admin: 'Админка',
settings: 'Настройки',
login: 'Войти',
register: 'Регистрация',
logout: 'Выйти',
},
theme: { light: 'Светлая', dark: 'Тёмная', system: 'Системная' },
lang: { ru: 'RU', en: 'EN' },
common: {
save: 'Сохранить',
cancel: 'Отмена',
retry: 'Повторить',
delete: 'Удалить',
create: 'Создать',
loading: 'Загрузка…',
error: 'Что-то пошло не так',
confirm: 'Подтвердить',
search: 'Поиск',
actions: 'Действия',
prevPage: 'Предыдущая страница',
nextPage: 'Следующая страница',
yes: 'Да',
no: 'Нет',
},
home: {
title: 'TELEWAVE',
subtitle: 'ЭФИРНАЯ СЕТКА КАНАЛОВ',
tagline: 'Твои каналы. Твой эфир. В любое время.',
cta: 'Войти в эфир',
ctaRegister: 'Создать аккаунт',
},
auth: {
userName: 'Имя пользователя',
password: 'Пароль',
loginTitle: 'Вход в эфир',
loginSubtitle: 'Введите учётные данные для доступа к сетке каналов',
registerTitle: 'Новый зритель',
registerSubtitle: 'Создайте аккаунт, чтобы настроить свою сетку каналов',
submitLogin: 'Войти',
submitRegister: 'Зарегистрироваться',
noAccount: 'Нет аккаунта?',
haveAccount: 'Уже есть аккаунт?',
registrationClosed: 'Регистрация закрыта',
registrationClosedHint: 'Открытая регистрация отключена. Учётную запись может завести администратор.',
invalidCredentials: 'Неверное имя пользователя или пароль',
userNameTaken: 'Это имя пользователя уже занято',
blocked: 'Аккаунт заблокирован администратором',
genericError: 'Не удалось выполнить вход. Попробуйте ещё раз',
},
dashboard: {
welcome: 'На связи, {{userName}}',
placeholder: 'Список каналов появится здесь позже — пока в эфире только тестовая заставка.',
role: 'Роль',
},
air: {
now: 'Сейчас',
next: 'Далее',
ad: 'Реклама',
bumper: 'Заставка',
episode: 'Серия',
live: 'В эфире',
volume: 'Громкость',
noChannels: 'Пока нет доступных каналов. Загляните позже.',
offline: 'Канал сейчас не в эфире',
offlineHint: 'Нет расписания или контента. Загляните позже.',
retry: 'Повторить',
},
settings: {
title: 'Настройки аккаунта',
changeUserName: 'Смена имени пользователя',
newUserName: 'Новое имя пользователя',
changePassword: 'Смена пароля',
currentPassword: 'Текущий пароль',
newPassword: 'Новый пароль',
dangerZone: 'Опасная зона',
deleteAccount: 'Удалить аккаунт',
deleteAccountConfirm: 'Аккаунт и все данные будут удалены безвозвратно. Продолжить?',
saved: 'Сохранено',
},
admin: {
roles: {
title: 'Роли',
name: 'Название',
system: 'Системная',
create: 'Новая роль',
rename: 'Переименовать',
cannotModifySystem: 'Системную роль нельзя изменить или удалить',
roleInUse: 'Роль назначена пользователям',
},
users: {
title: 'Пользователи',
userName: 'Имя пользователя',
role: 'Роль',
status: 'Статус',
createdAt: 'Регистрация',
blocked: 'Заблокирован',
active: 'Активен',
block: 'Заблокировать',
unblock: 'Разблокировать',
resetPassword: 'Пароль',
resetPasswordFor: 'Сменить пароль: {{name}}',
newPassword: 'Новый пароль',
passwordReset: 'Пароль изменён',
filterAll: 'Все роли',
createTitle: 'Создать пользователя',
password: 'Пароль',
passwordHint: 'Минимум 8 символов, хотя бы одна цифра и заглавная буква.',
create: 'Создать',
created: 'Пользователь создан',
},
media: {
title: 'Медиа',
upload: 'Загрузить',
uploadToShow: 'Загрузить в шоу',
toShowTitle: 'Загрузить и добавить в шоу',
toShowSubtitle: 'Файлов выбрано: {{count}}. После загрузки они добавятся сериями в выбранное шоу.',
autoDetectLabel: 'Определять шоу по названию файла',
autoDetectHint:
'Каждый файл привяжется к шоу, чьё оригинальное (или отображаемое) название есть в имени релиза, напр. «The.Simpsons.S33E01…» → The Simpsons.',
toShowShow: 'Шоу',
toShowFallback: 'Для нераспознанных',
toShowLibrary: 'В библиотеку',
toShowPick: 'Выберите шоу',
toShowSeason: 'Сезон (вручную)',
toShowAuto: 'авто',
toShowRegex: 'Regex серии',
toShowRegexInvalid: 'некорректный regex',
toShowHint:
'Сезон и regex — необязательны: обычно номера распознаются сами (см. ниже). Regex: 1 группа = серия, 2 группы = сезон и серия. Пример: ^(\\d+) для «01. Название.mkv».',
toShowPreview: 'Что распознаем',
toShowRecognized: 'распознано {{recognized}} из {{total}}',
toShowMatched: 'шоу распознано у {{matched}} из {{total}}',
applyToAll: 'Задать всем…',
toShowUnknown: '—',
toShowConfirm: 'Загрузить и добавить',
uploaded: 'Файл загружен, идёт обработка',
uploadedCount: 'Загружено файлов: {{count}}',
uploadingCount: 'Загрузка {{done}}/{{total}}',
cancelAll: 'Отменить все загрузки',
skippedDuplicates: 'Пропущено дубликатов: {{count}}',
filterActive: 'Активные',
filterAll: 'Все',
name: 'Файл',
status: 'Статус',
duration: 'Длительность',
resolution: 'Разрешение',
empty: 'Пока нет загруженных файлов',
statuses: {
Pending: 'В очереди',
Processing: 'Обработка',
Ready: 'Готов',
Failed: 'Ошибка',
},
},
gallery: {
title: 'Галерея',
upload: 'Загрузить',
empty: 'В этой категории пока нет изображений',
pickHint: 'Выберите изображение или загрузите новое',
browseHint: 'Все изображения приложения по категориям',
categories: {
Library: 'Библиотека',
ShowPoster: 'Постеры шоу',
EpisodeStill: 'Кадры серий',
BumperBackground: 'Фоны заставок',
},
},
shows: {
title: 'Шоу',
name: 'Название',
originalName: 'Оригинальное название (eng)',
kind: 'Тип',
kinds: { Series: 'Сериал', Single: 'Полнометражка' },
seasons: 'Сезоны',
loadedSeasons: 'Загружены сезоны',
episodes: 'Серии',
episode: 'Серия',
addEpisode: 'Добавить серию',
pickAsset: 'Выберите файл',
noEpisodes: 'Серий пока нет',
filterAssets: 'Фильтр по имени, напр. Family.Guy.S16',
selectAll: 'Выбрать все',
deselectAll: 'Снять все',
addSelected: 'Добавить выбранные',
noMatches: 'Нет подходящих файлов',
addedCount: 'Добавлено серий: {{count}}',
candidatesTruncated:
'Показаны не все файлы (слишком много готовых ассетов) — уточните фильтр, чтобы увидеть остальные.',
},
channels: {
title: 'Каналы',
name: 'Название',
slug: 'Slug',
state: 'Состояние',
enabled: 'В эфире',
disabled: 'Выключен',
enabledLabel: 'Канал в эфире',
regenerate: 'Пересобрать',
regenerated: 'Расписание пересобрано',
settings: 'Настройки',
adPolicy: 'Реклама',
betweenBlocks: 'Между блоками',
betweenEpisodes: 'Между сериями',
adsPerBreak: 'Роликов подряд',
bumpers: 'ТВ-заставки',
bumpersLabel: 'Заставки на переходах',
bumpersHint: 'Короткая заставка «Сейчас / Далее» между разными шоу',
bumperSelection: 'Выбор блока',
bumperSelectionRotation: 'По кругу',
bumperSelectionRandom: 'Случайно',
bumperSelectionWeighted: 'Случайно взвешенный',
bumperSelectionAlwaysFirst: 'Всегда первый',
bumperMinInterval: 'Мин. интервал, мин',
bumperShowChangeChance: 'Вероятность на смене шоу',
bumperShowChangeChanceHint: '0..1: 1 — на каждой смене, 0 — никогда',
bumperEpisodeChangeChance: 'Вероятность между сериями',
bumperEpisodeChangeChanceHint: '0..1: напр. 0.3 — примерно в 30% переходов между сериями',
bumperFont: 'Шрифт',
bumperFontSans: 'Гротеск',
bumperFontSerif: 'Антиква',
bumperNowLabel: 'Подпись «Сейчас»',
bumperNextLabel: 'Подпись «Далее»',
bumperBg: 'Фон (цвет 1)',
bumperBg2: 'Фон (цвет 2)',
bumperAccent: 'Акцент',
bumperText: 'Текст',
bumperOnlyDifferent: 'Только на смене шоу (не внутри марафона)',
bumperTemplates: 'Блоки заставок',
bumperTemplatesHint:
'Каждый блок — свой звук и оформление. Первый блок дефолтный, его нельзя удалить. Длительность заставки — по длине звука.',
bumperAddTemplate: 'Добавить блок',
bumperTemplateName: 'Название',
bumperVariants: 'Подблоки (текст)',
bumperVariantsHint:
'Разный текст на одной музыке и оформлении блока. Правило показа — у каждого подблока своё.',
bumperAddVariant: 'Добавить текст',
bumperVariantName: 'Название',
bumperTextKind: 'Режим текста',
bumperKindNowNext: 'Сейчас / Далее',
bumperKindFree: 'Свободный текст',
bumperLine1: 'Строка 1',
bumperLine2: 'Строка 2',
bumperTrigger: 'Показывать',
bumperTriggerOnShowChange: 'При смене шоу',
bumperTriggerBetweenEpisodes: 'Между сериями',
bumperTriggerBoth: 'Оба',
bumperVariantWeight: 'Вес',
bumperVariantWeightHint: 'Для стратегии «случайно взвешенный»: чем больше — тем чаще (0 — не выбирается)',
bumperDefault: 'по умолчанию',
bumperSeconds: 'с',
bumperDefaultDuration: '≈8 с (джингл)',
bumperAudio: 'Звук',
bumperAudioHint: 'Звук заставки; иначе — синтезированный джингл',
bumperPreview: 'Отрендерить примеры',
bumperPreviewRendering: 'Рендерим…',
bumperPreviewHint: 'Примеры всех подблоков со звуком и анимацией (примерные названия шоу). Использует сохранённые настройки.',
bumperBackground: 'Фон-картинка',
bumperBackgroundHint: 'Картинка фона; иначе — постер шоу или градиент',
bumperBackgroundPick: 'Выбрать из галереи',
bumperFileLoaded: 'загружено',
bumperFileDefault: 'по умолчанию',
bumperUpload: 'Загрузить',
bumperReset: 'Сбросить',
filler: 'Заглушка',
noFiller: 'Без заглушки',
shows: 'Шоу канала',
show: 'Шоу',
weight: 'Вес',
block: 'Блок',
on: 'Вкл',
noShows: 'Шоу не добавлены',
pickShow: 'Выберите шоу',
blockCount: 'По сериям',
blockDuration: 'По времени',
episodes: 'серий',
minutes: 'минут',
preferredHours: 'Часы',
preferredMultiplier: 'Множитель веса',
preferredHoursHint:
'В выбранные часы вес шоу умножается — оно чаще попадает в эфир. Время в UTC.',
preferredNone: 'Окна не заданы — предпочтений по времени нет.',
preferredAddWindow: 'Добавить окно',
preferredBadRange: 'начало ≥ конца',
ads: 'Реклама',
pickAd: 'Выберите ролик',
noAds: 'Пул рекламы пуст',
overrides: 'Марафоны / override',
modes: { Exclusive: 'Эксклюзив', Boost: 'Буст' },
overrideRecurrence: 'Повтор',
recurrenceOneTime: 'Разово',
recurrenceWeekly: 'Еженедельно',
weekday: 'День недели',
weekdays: {
0: 'Вс',
1: 'Пн',
2: 'Вт',
3: 'Ср',
4: 'Чт',
5: 'Пт',
6: 'Сб',
},
from: 'С',
to: 'По',
noOverrides: 'Override не заданы',
schedule: 'Расписание (12 ч)',
noSchedule: 'Расписание ещё не построено — нажмите «Пересобрать»',
},
maintenance: {
title: 'Обслуживание',
warning: 'Операции необратимы — удаляют данные и файлы навсегда.',
clearMedia: 'Очистить все медиа',
clearMediaHint: 'Удалит все загруженные файлы и их нарезку, а также расписание каналов.',
confirmClearMedia: 'Удалить ВСЕ медиа-файлы безвозвратно?',
clearShowMedia: 'Удалить медиа шоу',
clearShowMediaHint: 'Удалит файлы всех серий выбранного шоу и очистит его серии.',
confirmClearShowMedia: 'Удалить все медиа выбранного шоу безвозвратно?',
pickShow: 'Выберите шоу',
deleteShows: 'Удалить все шоу',
deleteShowsHint: 'Удалит все шоу и их серии. Медиа-файлы останутся в библиотеке.',
confirmDeleteShows: 'Удалить ВСЕ шоу безвозвратно?',
doneCount: 'Удалено: {{count}}',
},
settings: {
title: 'Настройки',
registration: 'Регистрация',
registrationHint:
'Когда выключено — новые пользователи не могут регистрироваться сами, учётки заводит только администратор.',
registrationLabel: 'Разрешить регистрацию на сайте',
},
metadata: {
title: 'Метаданные',
pickFromGallery: 'Выбрать из галереи',
pickPoster: 'Из галереи',
name: 'Название',
originalName: 'Оригинальное название (eng)',
originalNamePlaceholder: 'Например: Family Guy',
originalNameHint: 'По нему ищутся метаданные; на экранах показывается обычное название.',
source: 'Источник',
sourceLabel: 'Источник',
searchPlaceholder: 'Название для поиска',
searchBtn: 'Искать',
nothingFound: 'Ничего не найдено',
apply: 'Применить',
applied: 'Метаданные применены',
overview: 'Описание',
year: 'Год',
clear: 'Очистить',
uploadPoster: 'Загрузить постер',
noPoster: 'Нет постера',
refreshEpisodes: 'Обновить серии',
refreshing: 'Обновляем…',
refreshedCount: 'Обновлено серий: {{count}}',
},
},
},
},
en: {
translation: {
appName: 'TeleWave',
nav: {
home: 'Home',
dashboard: 'On Air',
admin: 'Admin',
settings: 'Settings',
login: 'Log in',
register: 'Sign up',
logout: 'Log out',
},
theme: { light: 'Light', dark: 'Dark', system: 'System' },
lang: { ru: 'RU', en: 'EN' },
common: {
save: 'Save',
cancel: 'Cancel',
retry: 'Retry',
delete: 'Delete',
create: 'Create',
loading: 'Loading…',
error: 'Something went wrong',
confirm: 'Confirm',
search: 'Search',
actions: 'Actions',
prevPage: 'Previous page',
nextPage: 'Next page',
yes: 'Yes',
no: 'No',
},
home: {
title: 'TELEWAVE',
subtitle: 'BROADCAST CHANNEL GRID',
tagline: 'Your channels. Your broadcast. Anytime.',
cta: 'Go on air',
ctaRegister: 'Create account',
},
auth: {
userName: 'Username',
password: 'Password',
loginTitle: 'Sign in',
loginSubtitle: 'Enter your credentials to access the channel grid',
registerTitle: 'New viewer',
registerSubtitle: 'Create an account to set up your channel grid',
submitLogin: 'Log in',
submitRegister: 'Sign up',
noAccount: "Don't have an account?",
haveAccount: 'Already have an account?',
registrationClosed: 'Registration is closed',
registrationClosedHint: 'Public registration is disabled. An administrator can create an account for you.',
invalidCredentials: 'Invalid username or password',
userNameTaken: 'This username is already taken',
blocked: 'Account blocked by an administrator',
genericError: 'Could not sign in. Please try again',
},
dashboard: {
welcome: 'On air, {{userName}}',
placeholder: 'The channel list will show up here later — for now, enjoy the test card.',
role: 'Role',
},
air: {
now: 'Now',
next: 'Up next',
ad: 'Ad',
bumper: 'Bumper',
episode: 'Episode',
volume: 'Volume',
live: 'Live',
noChannels: 'No channels available yet. Check back later.',
offline: 'This channel is off the air',
offlineHint: 'No schedule or content yet. Check back later.',
retry: 'Retry',
},
settings: {
title: 'Account settings',
changeUserName: 'Change username',
newUserName: 'New username',
changePassword: 'Change password',
currentPassword: 'Current password',
newPassword: 'New password',
dangerZone: 'Danger zone',
deleteAccount: 'Delete account',
deleteAccountConfirm: 'The account and all its data will be permanently deleted. Continue?',
saved: 'Saved',
},
admin: {
roles: {
title: 'Roles',
name: 'Name',
system: 'System',
create: 'New role',
rename: 'Rename',
cannotModifySystem: 'A system role cannot be modified or deleted',
roleInUse: 'Role is assigned to users',
},
users: {
title: 'Users',
userName: 'Username',
role: 'Role',
status: 'Status',
createdAt: 'Joined',
blocked: 'Blocked',
active: 'Active',
block: 'Block',
unblock: 'Unblock',
resetPassword: 'Password',
resetPasswordFor: 'Reset password: {{name}}',
newPassword: 'New password',
passwordReset: 'Password changed',
filterAll: 'All roles',
createTitle: 'Create user',
password: 'Password',
passwordHint: 'At least 8 characters, including a digit and an uppercase letter.',
create: 'Create',
created: 'User created',
},
media: {
title: 'Media',
upload: 'Upload',
uploadToShow: 'Upload to show',
toShowTitle: 'Upload and add to show',
toShowSubtitle: '{{count}} file(s) selected. After upload they are added as episodes to the chosen show.',
autoDetectLabel: 'Detect show from file name',
autoDetectHint:
'Each file is linked to the show whose original (or display) name appears in the release name, e.g. “The.Simpsons.S33E01…” → The Simpsons.',
toShowShow: 'Show',
toShowFallback: 'For unrecognized',
toShowLibrary: 'To library',
toShowPick: 'Pick a show',
toShowSeason: 'Season (manual)',
toShowAuto: 'auto',
toShowRegex: 'Episode regex',
toShowRegexInvalid: 'invalid regex',
toShowHint:
'Season and regex are optional: numbers are usually detected automatically (see below). Regex: 1 group = episode, 2 groups = season and episode. Example: ^(\\d+) for “01. Title.mkv”.',
toShowPreview: 'What we detect',
toShowRecognized: '{{recognized}} of {{total}} recognized',
toShowMatched: 'show detected for {{matched}} of {{total}}',
applyToAll: 'Set for all…',
toShowUnknown: '—',
toShowConfirm: 'Upload and add',
uploaded: 'File uploaded, processing started',
uploadedCount: 'Uploaded files: {{count}}',
uploadingCount: 'Uploading {{done}}/{{total}}',
cancelAll: 'Cancel all uploads',
skippedDuplicates: 'Skipped duplicates: {{count}}',
filterActive: 'Active',
filterAll: 'All',
name: 'File',
status: 'Status',
duration: 'Duration',
resolution: 'Resolution',
empty: 'No uploaded files yet',
statuses: {
Pending: 'Queued',
Processing: 'Processing',
Ready: 'Ready',
Failed: 'Failed',
},
},
gallery: {
title: 'Gallery',
upload: 'Upload',
empty: 'No images in this category yet',
pickHint: 'Pick an image or upload a new one',
browseHint: 'All app images by category',
categories: {
Library: 'Library',
ShowPoster: 'Show posters',
EpisodeStill: 'Episode stills',
BumperBackground: 'Bumper backgrounds',
},
},
shows: {
title: 'Shows',
name: 'Name',
originalName: 'Original name (eng)',
kind: 'Kind',
kinds: { Series: 'Series', Single: 'Movie' },
seasons: 'Seasons',
loadedSeasons: 'Loaded seasons',
episodes: 'Episodes',
episode: 'Episode',
addEpisode: 'Add episode',
pickAsset: 'Pick a file',
noEpisodes: 'No episodes yet',
filterAssets: 'Filter by name, e.g. Family.Guy.S16',
selectAll: 'Select all',
deselectAll: 'Clear',
addSelected: 'Add selected',
noMatches: 'No matching files',
addedCount: 'Episodes added: {{count}}',
candidatesTruncated:
'Not all files are shown (too many ready assets) — narrow the filter to see the rest.',
},
channels: {
title: 'Channels',
name: 'Name',
slug: 'Slug',
state: 'State',
enabled: 'On air',
disabled: 'Off',
enabledLabel: 'Channel on air',
regenerate: 'Rebuild',
regenerated: 'Schedule rebuilt',
settings: 'Settings',
adPolicy: 'Ads',
betweenBlocks: 'Between blocks',
betweenEpisodes: 'Between episodes',
adsPerBreak: 'Ads per break',
bumpers: 'TV bumpers',
bumpersLabel: 'Transition bumpers',
bumpersHint: 'Short “Now / Next” bumper between different shows',
bumperSelection: 'Block selection',
bumperSelectionRotation: 'Rotation',
bumperSelectionRandom: 'Random',
bumperSelectionWeighted: 'Weighted random',
bumperSelectionAlwaysFirst: 'Always first',
bumperMinInterval: 'Min interval, min',
bumperShowChangeChance: 'Chance on show change',
bumperShowChangeChanceHint: '0..1: 1 — every change, 0 — never',
bumperEpisodeChangeChance: 'Chance between episodes',
bumperEpisodeChangeChanceHint: '0..1: e.g. 0.3 — about 30% of same-show transitions',
bumperFont: 'Font',
bumperFontSans: 'Sans',
bumperFontSerif: 'Serif',
bumperNowLabel: '“Now” label',
bumperNextLabel: '“Next” label',
bumperBg: 'Background (color 1)',
bumperBg2: 'Background (color 2)',
bumperAccent: 'Accent',
bumperText: 'Text',
bumperOnlyDifferent: 'Only on show change (not within a marathon)',
bumperTemplates: 'Bumper blocks',
bumperTemplatesHint:
'Each block has its own sound and style. The first block is the default and cannot be removed. Bumper length follows the sound length.',
bumperAddTemplate: 'Add block',
bumperTemplateName: 'Name',
bumperVariants: 'Sub-blocks (text)',
bumperVariantsHint:
'Different text over the same music and style. Each sub-block has its own show rule.',
bumperAddVariant: 'Add text',
bumperVariantName: 'Name',
bumperTextKind: 'Text mode',
bumperKindNowNext: 'Now / Next',
bumperKindFree: 'Free text',
bumperLine1: 'Line 1',
bumperLine2: 'Line 2',
bumperTrigger: 'Show on',
bumperTriggerOnShowChange: 'Show change',
bumperTriggerBetweenEpisodes: 'Between episodes',
bumperTriggerBoth: 'Both',
bumperVariantWeight: 'Weight',
bumperVariantWeightHint: 'For the “weighted random” strategy: higher = more often (0 — never picked)',
bumperDefault: 'default',
bumperSeconds: 's',
bumperDefaultDuration: '≈8 s (jingle)',
bumperAudio: 'Sound',
bumperAudioHint: 'Bumper sound; otherwise a synthesized jingle',
bumperPreview: 'Render samples',
bumperPreviewRendering: 'Rendering…',
bumperPreviewHint: 'Samples of all sub-blocks with sound and animation (example show names). Uses saved settings.',
bumperBackground: 'Background image',
bumperBackgroundHint: 'Background image; otherwise the show poster or a gradient',
bumperBackgroundPick: 'Pick from gallery',
bumperFileLoaded: 'loaded',
bumperFileDefault: 'default',
bumperUpload: 'Upload',
bumperReset: 'Reset',
filler: 'Filler',
noFiller: 'No filler',
shows: 'Channel shows',
show: 'Show',
weight: 'Weight',
block: 'Block',
on: 'On',
noShows: 'No shows added',
pickShow: 'Pick a show',
blockCount: 'By episodes',
blockDuration: 'By time',
episodes: 'episodes',
minutes: 'minutes',
preferredHours: 'Hours',
preferredMultiplier: 'Weight multiplier',
preferredHoursHint:
'During the selected hours the shows weight is multiplied, so it airs more often. Times are UTC.',
preferredNone: 'No windows set — no time preference.',
preferredAddWindow: 'Add window',
preferredBadRange: 'start ≥ end',
ads: 'Ads',
pickAd: 'Pick an ad',
noAds: 'Ad pool is empty',
overrides: 'Marathons / overrides',
modes: { Exclusive: 'Exclusive', Boost: 'Boost' },
overrideRecurrence: 'Repeat',
recurrenceOneTime: 'One-time',
recurrenceWeekly: 'Weekly',
weekday: 'Weekday',
weekdays: {
0: 'Sun',
1: 'Mon',
2: 'Tue',
3: 'Wed',
4: 'Thu',
5: 'Fri',
6: 'Sat',
},
from: 'From',
to: 'To',
noOverrides: 'No overrides set',
schedule: 'Schedule (12h)',
noSchedule: 'Schedule not built yet — click “Rebuild”',
},
maintenance: {
title: 'Maintenance',
warning: 'These actions are irreversible — data and files are deleted permanently.',
clearMedia: 'Clear all media',
clearMediaHint: 'Deletes all uploaded files and their segments, plus channel schedules.',
confirmClearMedia: 'Permanently delete ALL media files?',
clearShowMedia: 'Delete show media',
clearShowMediaHint: "Deletes files of the selected show's episodes and clears its episodes.",
confirmClearShowMedia: 'Permanently delete all media of the selected show?',
pickShow: 'Pick a show',
deleteShows: 'Delete all shows',
deleteShowsHint: 'Deletes all shows and their episodes. Media files stay in the library.',
confirmDeleteShows: 'Permanently delete ALL shows?',
doneCount: 'Deleted: {{count}}',
},
settings: {
title: 'Settings',
registration: 'Registration',
registrationHint:
'When off, new users cannot sign up themselves — only an administrator can create accounts.',
registrationLabel: 'Allow public registration',
},
metadata: {
title: 'Metadata',
pickFromGallery: 'Pick from gallery',
pickPoster: 'From gallery',
name: 'Name',
originalName: 'Original name (eng)',
originalNamePlaceholder: 'e.g. Family Guy',
originalNameHint: 'Metadata is looked up by this; screens still show the regular name.',
source: 'Source',
sourceLabel: 'Source',
searchPlaceholder: 'Title to search',
searchBtn: 'Search',
nothingFound: 'Nothing found',
apply: 'Apply',
applied: 'Metadata applied',
overview: 'Overview',
year: 'Year',
clear: 'Clear',
uploadPoster: 'Upload poster',
noPoster: 'No poster',
refreshEpisodes: 'Refresh episodes',
refreshing: 'Refreshing…',
refreshedCount: 'Episodes updated: {{count}}',
},
},
},
},
}
const STORAGE_KEY = 'tw-lang'
const stored = typeof localStorage !== 'undefined' ? localStorage.getItem(STORAGE_KEY) : null
void i18n.use(initReactI18next).init({
resources,
lng: stored ?? 'ru',
fallbackLng: 'ru',
interpolation: { escapeValue: false },
})
export function setLanguage(lng: string) {
localStorage.setItem(STORAGE_KEY, lng)
void i18n.changeLanguage(lng)
}
export default i18n