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 show’s 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