381 lines
14 KiB
TypeScript
381 lines
14 KiB
TypeScript
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: 'Отмена',
|
||
delete: 'Удалить',
|
||
create: 'Создать',
|
||
loading: 'Загрузка…',
|
||
error: 'Что-то пошло не так',
|
||
confirm: 'Подтвердить',
|
||
search: 'Поиск',
|
||
actions: 'Действия',
|
||
yes: 'Да',
|
||
no: 'Нет',
|
||
},
|
||
home: {
|
||
title: 'TELEWAVE',
|
||
subtitle: 'ЭФИРНАЯ СЕТКА КАНАЛОВ',
|
||
tagline: 'Твои каналы. Твой эфир. В любое время.',
|
||
cta: 'Войти в эфир',
|
||
ctaRegister: 'Создать аккаунт',
|
||
},
|
||
auth: {
|
||
userName: 'Имя пользователя',
|
||
password: 'Пароль',
|
||
loginTitle: 'Вход в эфир',
|
||
loginSubtitle: 'Введите учётные данные для доступа к сетке каналов',
|
||
registerTitle: 'Новый зритель',
|
||
registerSubtitle: 'Создайте аккаунт, чтобы настроить свою сетку каналов',
|
||
submitLogin: 'Войти',
|
||
submitRegister: 'Зарегистрироваться',
|
||
noAccount: 'Нет аккаунта?',
|
||
haveAccount: 'Уже есть аккаунт?',
|
||
invalidCredentials: 'Неверное имя пользователя или пароль',
|
||
userNameTaken: 'Это имя пользователя уже занято',
|
||
blocked: 'Аккаунт заблокирован администратором',
|
||
genericError: 'Не удалось выполнить вход. Попробуйте ещё раз',
|
||
},
|
||
dashboard: {
|
||
welcome: 'На связи, {{userName}}',
|
||
placeholder: 'Список каналов появится здесь позже — пока в эфире только тестовая заставка.',
|
||
role: 'Роль',
|
||
},
|
||
air: {
|
||
now: 'Сейчас',
|
||
next: 'Далее',
|
||
ad: 'Реклама',
|
||
episode: 'Серия',
|
||
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: 'Разблокировать',
|
||
filterAll: 'Все роли',
|
||
},
|
||
media: {
|
||
title: 'Медиа',
|
||
upload: 'Загрузить',
|
||
uploaded: 'Файл загружен, идёт обработка',
|
||
name: 'Файл',
|
||
status: 'Статус',
|
||
duration: 'Длительность',
|
||
resolution: 'Разрешение',
|
||
empty: 'Пока нет загруженных файлов',
|
||
statuses: {
|
||
Pending: 'В очереди',
|
||
Processing: 'Обработка',
|
||
Ready: 'Готов',
|
||
Failed: 'Ошибка',
|
||
},
|
||
},
|
||
downloads: {
|
||
title: 'Загрузки',
|
||
empty: 'Папка загрузок пуста. Добавьте торрент в клиенте.',
|
||
import: 'Импортировать',
|
||
imported: 'Файл импортирован, идёт обработка',
|
||
size: 'Размер',
|
||
path: 'Файл',
|
||
refresh: 'Обновить',
|
||
openClient: 'Открыть qBittorrent',
|
||
hint: 'Торрент-клиент качает в downloads/. Выберите готовый файл и импортируйте — он скопируется в библиотеку и уйдёт на обработку, торрент продолжит сидироваться.',
|
||
},
|
||
shows: {
|
||
title: 'Шоу',
|
||
name: 'Название',
|
||
kind: 'Тип',
|
||
kinds: { Series: 'Сериал', Single: 'Полнометражка' },
|
||
episodes: 'Серии',
|
||
episode: 'Серия',
|
||
addEpisode: 'Добавить серию',
|
||
pickAsset: 'Выберите файл',
|
||
noEpisodes: 'Серий пока нет',
|
||
},
|
||
channels: {
|
||
title: 'Каналы',
|
||
name: 'Название',
|
||
slug: 'Slug',
|
||
state: 'Состояние',
|
||
enabled: 'В эфире',
|
||
disabled: 'Выключен',
|
||
enabledLabel: 'Канал в эфире',
|
||
regenerate: 'Пересобрать',
|
||
regenerated: 'Расписание пересобрано',
|
||
settings: 'Настройки',
|
||
adPolicy: 'Реклама',
|
||
betweenBlocks: 'Между блоками',
|
||
betweenEpisodes: 'Между сериями',
|
||
adsPerBreak: 'Роликов подряд',
|
||
filler: 'Заглушка',
|
||
noFiller: 'Без заглушки',
|
||
shows: 'Шоу канала',
|
||
show: 'Шоу',
|
||
weight: 'Вес',
|
||
block: 'Блок',
|
||
on: 'Вкл',
|
||
noShows: 'Шоу не добавлены',
|
||
pickShow: 'Выберите шоу',
|
||
blockCount: 'По сериям',
|
||
blockDuration: 'По времени',
|
||
episodes: 'серий',
|
||
minutes: 'минут',
|
||
ads: 'Реклама',
|
||
pickAd: 'Выберите ролик',
|
||
noAds: 'Пул рекламы пуст',
|
||
overrides: 'Марафоны / override',
|
||
modes: { Exclusive: 'Эксклюзив', Boost: 'Буст' },
|
||
from: 'С',
|
||
to: 'По',
|
||
noOverrides: 'Override не заданы',
|
||
schedule: 'Расписание (12 ч)',
|
||
noSchedule: 'Расписание ещё не построено — нажмите «Пересобрать»',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
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',
|
||
delete: 'Delete',
|
||
create: 'Create',
|
||
loading: 'Loading…',
|
||
error: 'Something went wrong',
|
||
confirm: 'Confirm',
|
||
search: 'Search',
|
||
actions: 'Actions',
|
||
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?',
|
||
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',
|
||
episode: 'Episode',
|
||
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',
|
||
filterAll: 'All roles',
|
||
},
|
||
media: {
|
||
title: 'Media',
|
||
upload: 'Upload',
|
||
uploaded: 'File uploaded, processing started',
|
||
name: 'File',
|
||
status: 'Status',
|
||
duration: 'Duration',
|
||
resolution: 'Resolution',
|
||
empty: 'No uploaded files yet',
|
||
statuses: {
|
||
Pending: 'Queued',
|
||
Processing: 'Processing',
|
||
Ready: 'Ready',
|
||
Failed: 'Failed',
|
||
},
|
||
},
|
||
downloads: {
|
||
title: 'Downloads',
|
||
empty: 'Downloads folder is empty. Add a torrent in the client.',
|
||
import: 'Import',
|
||
imported: 'File imported, processing started',
|
||
size: 'Size',
|
||
path: 'File',
|
||
refresh: 'Refresh',
|
||
openClient: 'Open qBittorrent',
|
||
hint: 'The torrent client downloads into downloads/. Pick a finished file and import it — it is copied into the library and queued for processing, the torrent keeps seeding.',
|
||
},
|
||
shows: {
|
||
title: 'Shows',
|
||
name: 'Name',
|
||
kind: 'Kind',
|
||
kinds: { Series: 'Series', Single: 'Movie' },
|
||
episodes: 'Episodes',
|
||
episode: 'Episode',
|
||
addEpisode: 'Add episode',
|
||
pickAsset: 'Pick a file',
|
||
noEpisodes: 'No episodes yet',
|
||
},
|
||
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',
|
||
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',
|
||
ads: 'Ads',
|
||
pickAd: 'Pick an ad',
|
||
noAds: 'Ad pool is empty',
|
||
overrides: 'Marathons / overrides',
|
||
modes: { Exclusive: 'Exclusive', Boost: 'Boost' },
|
||
from: 'From',
|
||
to: 'To',
|
||
noOverrides: 'No overrides set',
|
||
schedule: 'Schedule (12h)',
|
||
noSchedule: 'Schedule not built yet — click “Rebuild”',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
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
|