/** Словарь локали «ru». Плоская правка: ключи обеих локалей обязаны совпадать. */ export const ru = { appName: 'TeleWave', nav: { dashboard: 'Эфир', admin: 'Админка', settings: 'Настройки', login: 'Войти', register: 'Регистрация', logout: 'Выйти', }, theme: { light: 'Светлая', dark: 'Тёмная', system: 'Системная' }, lang: { ru: 'RU', en: 'EN' }, common: { save: 'Сохранить', cancel: 'Отмена', retry: 'Повторить', close: 'Закрыть', delete: 'Удалить', create: 'Создать', loading: 'Загрузка…', error: 'Что-то пошло не так', 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: 'Не удалось выполнить вход. Попробуйте ещё раз', }, air: { now: 'Сейчас', next: 'Далее', ad: 'Реклама', bumper: 'Заставка', fallback: 'Аварийный запас', signOff: 'Конец вещания', episode: 'Серия', collectionPart: 'часть {{part}} из {{total}}', live: 'В эфире', volume: 'Громкость', noChannels: 'Пока нет доступных каналов. Загляните позже.', offline: 'Канал сейчас не в эфире', offlineHint: 'Нет расписания или контента. Загляните позже.', numbersHint: '↑ / ↓ — переключение каналов по номерам', iptv: 'Плейлист IPTV', iptvHint: 'M3U со всеми каналами и программой передач — для VLC, TiviMate и подобных. Доступ к эфиру зашит в сам файл: делиться им нельзя.', iptvDone: 'Плейлист скачан. Файл открывает доступ к эфиру — не передавайте его никому.', iptvFailed: 'Не удалось собрать плейлист', retry: 'Повторить', }, settings: { title: 'Настройки аккаунта', changeUserName: 'Смена имени пользователя', newUserName: 'Новое имя пользователя', changePassword: 'Смена пароля', currentPassword: 'Текущий пароль', newPassword: 'Новый пароль', dangerZone: 'Опасная зона', deleteAccount: 'Удалить аккаунт', deleteAccountConfirm: 'Аккаунт и все данные будут удалены безвозвратно. Продолжить?', saved: 'Сохранено', }, admin: { groups: { title: 'Группы', createTitle: 'Создать группу', hint: 'Группа — что может попасть в эфир. На неё ссылается слот сетки, из неё стратегия выбирает элемент.', name: 'Название', description: 'Описание', items: 'Позиций', units: 'Единиц', duration: 'Объём', hoursShort: 'ч', minutesShort: 'мин', hasFilter: 'с правилом', composition: 'Состав', empty: 'Группа пуста', orderHint: 'Порядок задаётся перетаскиванием — по нему идут последовательные стратегии.', showAdvanced: 'Дополнительно', hideAdvanced: 'Скрыть', find: 'Подобрать', addFound: 'Добавить найденное', found: 'Найдено: {{total}}, новых: {{fresh}}', added: 'Добавлено позиций: {{count}}', alreadyIn: 'уже в группе', mode: { label: 'Состав', Static: 'Явный список', Dynamic: 'По правилу', StaticHint: 'Позиции добавляются вручную; правило только помогает их найти.', DynamicHint: 'Состав считается правилом при каждом обращении — новое из библиотеки попадает в эфир само.', }, pinned: 'закреплено', byRule: 'по правилу', excluded: 'Исключено', excludeHint: 'Исключить из состава — правило больше не будет его подставлять', restore: 'Вернуть', dynamicOrderHint: 'Порядок: сначала закреплённое (перетаскиванием), затем найденное правилом по алфавиту.', previewComposition: 'При этом правиле в составе будет: {{count}}', pendingFound: 'Под правило группы подходит нового: {{count}} —', pendingAdd: 'Добавить все ({{count}})', elementKinds: { Show: 'Шоу', Collection: 'Коллекция' }, suggestions: { title: 'Предложения', hint: 'Собрано по текущей библиотеке. Кнопка создаёт группу с этим составом и сохраняет правило набора — новое из библиотеки потом добавится в один клик.', stats: '{{shows}} шоу · {{units}} ед.', exists: 'уже есть', created: 'Группа создана', allCreated: 'Все предложения уже созданы.', showExisting: 'Показать уже созданные ({{count}})', hideExisting: 'Скрыть уже созданные', kinds: { Genre: 'жанр', ShowKind: 'тип', Audience: 'рейтинг', BigSeries: 'крупный сериал', }, }, filter: { title: 'Правило набора', hint: 'В явном списке правило только ищет кандидатов; в группе «по правилу» оно и есть состав.', elementKinds: 'Что искать', showKinds: 'Тип шоу', showKindsHint: 'Не отмечено — кино и сериалы. Ролики живут отдельной библиотекой и попадают в набор, только если отметить их явно.', genres: 'Жанры', genresHint: 'Любой из отмеченных.', maxAudience: 'Возраст не строже', minAudience: 'Возраст не мягче', minAudienceHint: 'Для ночных слотов. В отличие от верхней границы, шоу без рейтинга сюда не попадают — иначе в «взрослое» просочились бы непроставленные мультики.', audienceHint: 'Рейтинги упорядочены по строгости: G → PG → PG-13 → R → NC-17. Шоу без рейтинга остаются в выборке.', year: 'Год', unitMinutes: 'Длительность единицы, мин', unitMinutesHint: 'Средняя длина серии или фильма. Позиции без готовых ассетов фильтр не отбрасывает.', from: 'от', to: 'до', any: 'любой', }, }, collections: { posters: 'Проставить постеры', postersHint: 'Берёт обложку первой части у коллекций, у которых постера ещё нет. Выбранный руками не трогает.', postersDone: 'Постеры проставлены: {{count}}', title: 'Коллекции', createTitle: 'Создать коллекцию', hint: 'Франшиза — упорядоченный набор фильмов, который играется как одно целое.', name: 'Название', description: 'Описание', parts: 'Части', units: 'Единиц', addShow: 'Добавить шоу', pickShow: 'Выберите шоу', empty: 'Коллекция пуста', orderHint: 'Порядок частей задаётся перетаскиванием — в нём они и пойдут в эфир.', suggestions: { title: 'Предложения франшиз', hint: 'Собрано из полнометражек, не входящих ни в одну коллекцию: что собрать в новую франшизу и чем дополнить уже собранную. Проверьте состав — по названиям догадка иногда промахивается.', parts: '{{count}} частей', exists: 'уже есть', additionBadge: 'дополнение', add: 'Добавить', added: 'Добавлено в коллекцию: {{count}}', created: 'Коллекция создана', allCreated: 'Все предложения уже созданы.', showExisting: 'Показать уже созданные ({{count}})', hideExisting: 'Скрыть уже созданные', sources: { Metadata: 'из метаданных', NameGuess: 'по названиям' }, }, }, interstitials: { title: 'Ролики', hint: 'Реклама, промо и джинглы. Перетащите ролики в сборку блока или в группу справа — блок сохранится коллекцией и пойдёт в эфир целиком.', upload: 'Загрузить ролики', name: 'Название', duration: 'Длительность', empty: 'Роликов пока нет', noAsset: 'Без файла', blocks: 'Блоки', noBlocks: 'Блоков пока нет', clipsCount: '{{count}} рол.', blockBuilder: 'Сборка блока', blockName: 'Название блока', blockTotal: 'Длительность блока', dropHint: 'Перетащите сюда ролики', groups: 'Группы роликов', pickGroup: 'Выберите группу', pickGroupFirst: 'Сначала выберите группу', dropToGroup: 'Перетащите сюда ролик или блок', openGroup: 'Открыть группу', newGroupName: 'Название группы', createGroupTitle: 'Создать группу роликов', }, genres: { title: 'Жанры', hint: 'Справочник жанров: по нему собираются группы контента, в него сводятся жанры из метаданных.', name: 'Название', slug: 'Ключ', slugHint: 'Латиницей, без пробелов — по нему жанр опознаётся при обновлении справочника.', aliases: 'Варианты написания', aliasesHint: 'Через запятую. Так жанры провайдеров сводятся к вашему: tmdb:28, action, боевик.', order: 'Порядок', usage: 'Шоу', system: 'Системный', create: 'Новый жанр', edit: 'Изменить жанр', }, roles: { title: 'Роли', name: 'Название', system: 'Системная', create: 'Новая роль', rename: 'Переименовать', }, users: { title: 'Пользователи', userName: 'Имя пользователя', role: 'Роль', status: 'Статус', createdAt: 'Регистрация', blocked: 'Заблокирован', active: 'Активен', block: 'Заблокировать', unblock: 'Разблокировать', resetPassword: 'Пароль', resetPasswordFor: 'Сменить пароль: {{name}}', newPassword: 'Новый пароль', passwordReset: 'Пароль изменён', filterAll: 'Все роли', createTitle: 'Создать пользователя', password: 'Пароль', passwordHint: 'Минимум 8 символов, хотя бы одна цифра и заглавная буква.', create: 'Создать', created: 'Пользователь создан', }, movies: { title: 'Разбор фильмов', hint: 'Имя файла разбирается на название и год, по ним ищется фильм в источнике метаданных. Каждый файл становится отдельным шоу; строки, где совпадение неточное, поправьте руками.', sources: { manual: 'Из папки manual', disk: 'С моего диска' }, pickFiles: 'Выбрать файлы', scan: 'Разобрать ({{count}})', scanning: 'Ищем в источнике…', nothingYet: 'Выберите источник и нажмите «Разобрать».', noProvider: 'Источник метаданных не настроен — без него искать нечем.', file: 'Файл', parsed: 'Название и год', candidate: 'Кандидат', toggleAll: 'Отметить все / снять все', year: 'Год', research: 'Искать заново', empty: 'ничего не нашлось', noCandidate: 'без метаданных', import: 'Завести ({{count}})', imported: 'Заведено фильмов: {{created}}, с метаданными: {{enriched}}', uploadStarted: 'Файлы пошли на загрузку — фильмы заведутся по мере готовности', collectionsHint: 'Франшизы после импорта соберутся в коллекции — предложения ждут на вкладке «Коллекции».', statuses: { Confident: 'совпадение точное', Uncertain: 'проверьте', NotFound: 'не найдено — впишите название', AlreadyInLibrary: 'уже в библиотеке', }, }, media: { title: 'Медиа', upload: 'Загрузить', manualButton: 'Из папки manual', importButton: 'Импорт', importTitle: 'Импорт медиа', importHint: 'Три способа пополнить библиотеку: серии из папки manual в шоу, фильмы пачкой и загрузка выбранных файлов в шоу.', importTabs: { movies: 'Фильмы', manual: 'Серии из manual', toShow: 'Файлы в шоу' }, moviesButton: 'Фильмы', manualTitle: 'Ручной разбор папки manual', manualHint: 'Файлы из manual/ не разбираются сканером — выберите нужные и укажите шоу. Импортированные файлы уходят из папки, как и из inbox.', manualSelectAll: 'Выбрать все', manualSelected: 'Выбрано: {{count}}', manualEmpty: 'В папке manual пусто', regexPickHint: 'Кликните число в имени файла — по нему соберётся правило для всех файлов:', regexPickTitle: 'Это номер серии', regexPresets: 'Готовые:', regexPresetNames: { seriesWord: 'Серия N', episodeWord: 'Эпизод N', seasonEpisode: 'SxxEyy', productionCode: '2ACV01', afterDash: 'после тире', firstNumber: 'первое число', }, regexClear: 'сбросить', manualAlready: 'уже в библиотеке', manualRoot: 'корень manual/', manualRecognized: 'Распознано: {{count}} из {{total}}', manualCleanupHint: 'Файлы уйдут из папки, спутники (субтитры, nfo) и опустевший каталог будут удалены.', manualTruncated: 'Показаны первые 500 файлов — в папке есть ещё.', manualShow: 'Шоу', manualDetected: 'Определено по имени релиза — проверьте и поправьте, если не то.', manualPickShow: 'Выберите шоу', manualImport: 'Забрать в шоу', manualImported: 'Импортировано файлов: {{count}}', uploadToShow: 'Загрузить в шоу', toShowTitle: 'Загрузить и добавить в шоу', toShowPick: 'Выбрать файлы', toShowEmpty: 'Файлы не выбраны', toShowPicked: 'Выбрано файлов: {{count}}', autoDetectHint: 'Каждый файл привяжется к шоу, чьё оригинальное (или отображаемое) название есть в имени релиза, напр. «The.Simpsons.S33E01…» → The Simpsons.', toShowLibrary: 'В библиотеку', toShowSeason: 'Сезон (вручную)', toShowAuto: 'авто', toShowRegex: 'Regex серии', toShowRegexInvalid: 'некорректный regex', toShowHint: String.raw`Сезон и regex — необязательны: обычно номера распознаются сами (см. ниже). Regex: 1 группа = серия, 2 группы = сезон и серия. Пример: ^(\d+) для «01. Название.mkv».`, toShowPreview: 'Что распознаем', toShowMatched: 'шоу распознано у {{matched}} из {{total}}', applyToAll: 'Задать всем…', toShowUnknown: '—', toShowConfirm: 'Загрузить и добавить', uploadedCount: 'Загружено файлов: {{count}}', uploadingCount: 'Загрузка {{done}}/{{total}}', cancelAll: 'Отменить все загрузки', skippedDuplicates: 'Пропущено дубликатов: {{count}}', queueDrained: 'Обработка завершена — очередь пуста', filterActive: 'Активные', filterAll: 'Все', name: 'Файл', status: 'Статус', duration: 'Длительность', resolution: 'Разрешение', processingTime: 'Время обработки', empty: 'Пока нет загруженных файлов', statuses: { Pending: 'В очереди', Processing: 'Обработка', Ready: 'Готов', Failed: 'Ошибка', }, stats: { queued: 'Сейчас в очереди', queuedShort: 'В очереди', processing: 'Сейчас в обработке', processingShort: 'В обработке', average: 'Среднее время обработки (по недавним)', averageShort: 'Ср. время', eta: 'Примерное время до конца очереди: оставшаяся работа по среднему, делённая на число параллельных транскодов', etaShort: 'Осталось', etaSoon: 'меньше минуты', hoursShort: 'ч', minutesShort: 'мин', }, }, gallery: { title: 'Галерея', upload: 'Загрузить', empty: 'В этой категории пока нет изображений', pickHint: 'Выберите изображение или загрузите новое', browseHint: 'Все изображения приложения по категориям', sort: { newest: 'Сначала новые', oldest: 'Сначала старые', nameAsc: 'Имя: А–Я', nameDesc: 'Имя: Я–А', }, categories: { Library: 'Библиотека', ShowPoster: 'Постеры шоу', EpisodeStill: 'Кадры серий', BumperBackground: 'Фоны заставок', }, }, shows: { title: 'Шоу', createTitle: 'Создать шоу', name: 'Название', originalName: 'Оригинальное название (eng)', kind: 'Тип', kinds: { Series: 'Сериал', Single: 'Полнометражка', Interstitial: 'Ролик' }, pickerPlaceholder: 'Выберите шоу', pickerSearch: 'Поиск по названию…', pickerEmpty: 'Ничего не найдено', pickerCreate: 'Создать сериал «{{name}}»', pickerCreateSingleHint: 'Создать как полнометражку', pickerCreated: 'Шоу создано', audience: 'Рейтинг', audienceUnset: 'Не проставлен', audiences: { G: 'G — без ограничений', PG: 'PG — с родителями', 'PG-13': 'PG-13 — с 13 лет', R: 'R — с 17 со взрослым', 'NC-17': 'NC-17 — только взрослым', }, seasons: 'Сезоны', loadedSeasons: 'Загружены сезоны', genre: 'Жанр', allGenres: 'Все жанры', allKinds: 'Все типы', deleteTitle: 'Удалить шоу?', deleteHint: '«{{name}}» будет удалено из библиотеки. Отменить это нельзя.', deleteWithMedia: 'Также удалить медиа: файлов {{count}}', deleteWithMediaHint: 'Иначе файлы останутся в списке медиа ничьими и продолжат занимать место.', deleteWithSchedule: 'Также вырезать из будущего эфира', deleteWithScheduleHint: 'Без этого удаление отклонят, пока шоу стоит в ленте. На месте вырезанного зритель увидит фон, пока хвост не пересоберут.', otherGenres: 'Остальные жанры', bulk: { selected: 'Отмечено: {{count}}', selectPage: 'Отметить все на странице', noGenre: 'Жанр не менять', keepAudience: 'Рейтинг не менять', apply: 'Применить', tagged: 'Обновлено шоу: {{count}}', clear: 'Снять выбор', enrich: 'Метаданные из {{provider}}', enrichHint: 'Найти по названию и применить — только тем, у кого источник ещё не привязан. Неоднозначные совпадения пропускаются.', enriching: 'Ищем…', enriched: 'Метаданные применены: {{count}}', moreSkipped: 'Ещё пропущено: {{count}}', toCollection: 'В коллекцию…', toCollectionHint: 'Отмеченные шоу уйдут во франшизу. Порядок частей — по году выпуска; поправить его можно перетаскиванием в карточке коллекции.', newCollection: 'Новая коллекция', addToCollection: 'Добавить ({{count}})', addedToCollection: '«{{name}}» пополнена: {{count}}', }, genresEmpty: 'Жанры не проставлены', genresEdit: 'Жанры', genresHint: 'Отметьте жанры шоу. Основной показывается в списке; в отборе контента участвуют все.', genrePrimary: 'основной', inGroups: 'В группах', inChannels: 'В эфире каналов', inCollections: 'Входит в коллекции', episodes: 'Серии', episode: 'Серия', noEpisodes: 'Серий пока нет', addMedia: 'Добавить медиа', addMediaHint: 'Готовые файлы, ещё не добавленные в это шоу. Выбранные добавятся сериями.', filterAssets: 'Фильтр по имени, напр. Family.Guy.S16', selectAll: 'Выбрать все', deselectAll: 'Снять все', addSelected: 'Добавить выбранные', noMatches: 'Нет подходящих файлов', addedCount: 'Добавлено серий: {{count}}', candidatesTruncated: 'Показаны не все файлы (слишком много готовых ассетов) — уточните фильтр, чтобы увидеть остальные.', }, channels: { number: 'Номер', numberPlaceholder: 'не задан', utcOffset: 'Часовой пояс, ч', utcOffsetHint: 'Целыми часами: 3 — Москва, 0 — UTC, −5 — Нью-Йорк. В этом времени задаётся вся сетка и показывается расписание.', dayStart: 'Начало вещательных суток', dayStartHint: 'Ночной блок до этого времени относится к предыдущему дню.', disabled: 'выключен', layers: 'Слои', layersHint: 'Побеждает слой с большим приоритетом. Фоновый лежит под всеми и закрывает дыры.', background: 'фон', newLayerName: 'Новый слой', addSlotHere: 'Добавить слот', newSlot: 'Новый слот', /** Автосборка сетки по профилю: предпросмотр плана и его создание. */ transfer: { action: 'Импорт / экспорт', title: 'Обмен конфигурацией сетки', hint: 'Файл ссылается на группы и стыки по именам, поэтому переносится между каналами и установками. Запрос к ИИ никуда не отправляется — вы копируете его в свою модель и приносите ответ назад вкладкой импорта.', tabs: { export: 'Экспорт', import: 'Импорт', ai: 'Запрос к ИИ' }, exportHint: 'Выгружает слои, слоты, правила и ссылки на группы и стыки. Годится и как бэкап перед экспериментом, и как образец для модели.', exportAction: 'Скачать файл', exported: 'Файл выгружен', importHint: 'Загрузите файл или вставьте JSON — например ответ модели. Недостающие группы и коллекции файл может принести с собой; шоу не создаются никогда — их приносит загрузка медиа. Незнакомые ссылки не валят импорт: такие слоты пропускаются, и вы увидите их списком.', pickFile: 'Выбрать файл', replace: 'Снести существующие слоты', pastePlaceholder: 'Вставьте JSON конфигурации сетки', importAction: 'Загрузить', imported: 'Создано слотов: {{slots}}, пропущено: {{skipped}}', importedLibrary: 'Заведено групп: {{groups}}, коллекций: {{collections}}', badJson: 'Это не похоже на JSON — проверьте, что скопирован весь ответ целиком.', warnings: 'Что не удалось разобрать', aiHint: 'Соберём запрос: параметры канала, ваши группы и библиотека, ваши референсы и пожелания, плюс схема ответа. Модель может собрать нужные ей группы сама — они заведутся при импорте.', references: 'Референс-каналы', referencesPlaceholder: '2×2, Paramount Comedy', referencesHint: 'Через запятую. На их ритм модель будет равняться — длина блоков, прайм, ночь.', notes: 'Пожелания', notesPlaceholder: 'Например: утром детское, вечером кино, ночью — взрослый блок', buildPrompt: 'Собрать запрос', copyPrompt: 'Скопировать', copied: 'Запрос скопирован', aiNext: 'Отправьте это в свою модель, а полученный JSON вставьте во вкладке «Импорт».', }, generate: { action: 'Собрать сетку', title: 'Автосборка сетки', hint: 'Профиль задаёт ритм суток: длину блоков, что стоит в прайме и чем закрыта ночь. Группы под каждую полосу подбираются из существующих — по рейтингу, типу контента и запасу серий.', modes: { FillGaps: 'Заполнить дыры', Rebuild: 'Собрать неделю с нуля', }, modeHints: { FillGaps: 'Существующие слоты не трогаются — застраивается только незакрытое время.', Rebuild: 'Все слоты шаблона сносятся; выходные строятся отдельным слоем поверх будних.', }, summary: 'Будет создано слотов: {{slots}} — это {{percent}}% свободного времени недели.', willRemove: 'Будет снесено слотов: {{count}}.', fallback: 'Аварийной группой станет «{{name}}» — ею закрываются паузы между слотами.', nothing: 'Создавать нечего: свободного времени в сетке нет.', create: 'Собрать', done: 'Создано слотов: {{created}}, снесено: {{removed}}.', }, editSlot: 'Слот', slotTitle: 'Название блока', slotStart: 'Начало', slotDuration: 'Бюджет, мин', weekday: 'День недели', everyDay: 'каждый день', daypart: 'Дейпарт', slotKind: 'Тип слота', group: 'Группа', pickGroup: 'Выберите группу', strategy: 'Стратегия', cooldownDays: 'Остывание, дней', cooldownHint: 'Не брать то, что уже выходило за этот срок.', blockMode: 'Блок', blockValue: 'Значение', overflow: 'Если не помещается', overflowHint: 'Как поступить с элементом, который не влезает в бюджет слота.', repeatDaysAgo: 'Суток назад', repeatTime: 'Время', repeatDuration: 'Длительность, мин', anchor: 'Якорь', anchorHint: 'Якорь держит старт жёстко: через него не переносят то, что не влезает. Округление мягкое — сдвигает старт до круглого времени, если это укладывается в допуск.', maxDrift: 'Допуск, мин', snap: 'Округление', snapOff: 'нет', resizeSlot: 'Потянуть за край — длительность', copyDay: 'Копировать день', copyDayFrom: 'Копировать {{day}} в:', copy: 'Копировать', layerVisible: 'Показывать слой', layerName: 'Название слоя', layerApplicability: 'Когда действует', applicabilityHint: 'Разделы объединяются по ИЛИ: слой действует, если дата подходит хотя бы под одно условие. Ничего не заполнено — действует всегда.', applicabilityWeekdays: 'Дни недели', applicabilityDateRanges: 'Диапазоны дат', applicabilityAnnual: 'Ежегодно (месяц / день)', applicabilityDates: 'Конкретные даты', applicabilityNone: 'не задано', showForDate: 'Сетка на дату', allDates: 'Все слои', tabs: { grid: 'Сетка', rules: 'Правила', junctions: 'Стыки', viewer: 'Зритель', settings: 'Настройки', air: 'Эфир', }, noTemplate: 'Сетка канала не загрузилась', createTemplate: 'Создать сетку', createTemplateHint: 'Появится пустая сетка с фоновым слоем — дальше добавляйте слои и слоты.', rules: 'Правила отбора', rulesHint: 'Жёсткие фильтры: отсекают неподходящее до жребия. Как и правка сетки, эфир не двигают — нужно применить.', audienceWindows: 'Детское время', noAudienceWindows: 'Окон нет — возраст ничем не ограничен.', audienceWindowsHint: 'В окне в эфир идёт контент не строже выбранной категории. Окно может переходить через полночь. Контент без категории не отсекается.', from: 'С', to: 'До', maxAudience: 'Не строже', repeatLimit: 'Потолок повторов', repeatWindowDays: 'Окно, суток', repeatMax: 'Не чаще, раз', repeatLimitHint: 'Считается по уже записанной ленте. Если потолка достигли все кандидаты, слот всё равно заполняется: пустой эфир хуже раннего повтора.', preview: 'Предпросмотр', previewHide: 'Свернуть предпросмотр', previewHint: 'Прогон по текущим правилам: ничего не пишется, курсоры слотов не двигаются.', previewDays_one: '{{count}} сутки', previewDays_few: '{{count}} суток', previewDays_many: '{{count}} суток', previewTabs: { programme: 'Программа', tape: 'Лента', problems: 'Проблемы' }, noProblems: 'Проблем нет', andMore: 'и ещё {{count}}', heatmap: 'Повторы: шоу × сутки', heatmapTotal: 'всего', issues: 'Проверки: ошибок {{errors}}, предупреждений {{warnings}}', goToSlot: 'к слоту', issueKinds: { GroupEmpty: 'Пустая группа', GroupTooSmall: 'Мало контента', GridGap: 'Дыра в сетке', SlotOverlap: 'Слоты пересекаются', CooldownUnreachable: 'Недостижимое остывание', AudienceConflict: 'Возрастной конфликт', GroupMissing: 'Группа не выбрана', }, viewer: 'Как выглядит у зрителя', viewerHint: 'Оверлеи рисуются поверх картинки на клиенте — видео не перекодируется. Всё по умолчанию выключено.', logo: 'Логотип', noLogo: 'нет', pickLogo: 'Выбрать логотип', logoCorner: 'Угол', logoOpacity: 'Прозрачность', corners: { TopLeft: 'Слева вверху', TopRight: 'Справа вверху', BottomLeft: 'Слева внизу', BottomRight: 'Справа внизу', }, showClock: 'Показывать часы', analogFilter: 'Аналоговый фильтр', analogFilterHint: 'Сила 0..1. Ноль — выключен; переборщить очень легко.', whyHere: 'Почему это здесь', priority: 'приоритет', traceLayer: 'Слой', traceSlot: 'Слот', traceGroup: 'Группа', traceCollection: 'Коллекция', traceStrategy: 'Стратегия', traceJunction: 'Врезки', traceDrift: 'дрейф {{minutes}} мин', traceSnapped: 'старт округлён', traceCooldown: 'остывание {{days}} дн.', traceCandidates: 'кандидатов после остывания: {{count}}', diffSummary: 'Затронет {{total}} записей, изменятся {{changed}}', diffSoon: 'В ближайшие сутки изменится записей: {{count}}', diffNoChanges: 'Эфир не изменится', copyTemplate: 'Скопировать сетку с канала', pickSourceChannel: 'Выберите канал-источник', copyHere: 'Скопировать сюда', copyTemplateHint: 'Слои, слоты, стыки и правила выбранного канала заменят сетку этого канала. Группы общие и не копируются.', copyTemplateConfirm: 'Текущая сетка этого канала будет заменена сеткой выбранного канала. Продолжить?', templateCopied: 'Скопировано: слоёв {{layers}}, слотов {{slots}}', copyDroppedBumpers: 'Врезок без блока заставки: {{count}} — донастройте руками', // Не «Сбросить»: так уже называется откат правок к эфирному состоянию (ключ restore). reset: 'Очистить сетку', resetConfirm: 'Сетка канала будет снята полностью — все слои и слоты, — а будущий эфир, собранный по ней, удалён. Прошлое и идущая сейчас запись останутся. Продолжить?', resetDone: 'Сброшено: слоёв {{layers}}, слотов {{slots}}, записей эфира {{entries}}', resetHint: 'Пока канал включён, лента отстроится заново по пустой сетке — аварийной группой. Чтобы эфир остался пустым, снимите её в правилах или выключите канал.', fallbackGroup: 'Аварийная группа', fallbackGroupNone: 'Нет — только филлер канала', fallbackGroupHint: 'Чем закрывается пауза, если пуст и фоновый слой. Её же играет пустая сетка — весь эфир, пока слотов нет. Проставляется автосборкой сетки, если своя не выбрана.', postChecks: 'Пост-проверки', breakLimit: 'Потолок врезок в час, мин', genreShare: 'Потолок доли жанра за сутки, %', fallbackShare: 'Потолок доли фона, %', postChecksHint: 'Пост-проверки считаются по готовой ленте и только предупреждают — ничего не переигрывается.', previewKinds: { Program: 'Программа', Fallback: 'Фон', SignOff: 'Конец вещания', Ad: 'Реклама', Promo: 'Анонс', Bumper: 'Заставка', }, previewLoad: 'Врезки по часам, пик — {{peak}} мин', junctions: 'Стыки', junctionsHint: 'Что играет между программами: реклама, анонсы, заставки. Слот может взять свой стык, иначе берётся стык по умолчанию.', defaultJunction: 'Стык по умолчанию', noJunction: 'без стыка', junctionBetween: 'Стык внутри слота', junctionAfter: 'Стык после слота', junctionDefault: 'по умолчанию', junctionsUsed: 'Используются в этом канале', junctionsNoneUsed: 'Канал не ссылается ни на один стык — между программами ничего не играет.', openJunctionEditor: 'Редактор стыков', pendingChanges: 'Правила изменены — эфир идёт по старым.', restore: 'Сбросить изменения', restoreConfirm: 'Вернуть сетку к тому состоянию, по которому идёт эфир? Всё, что изменено после последнего применения, будет потеряно.', restored: 'Сетка возвращена к эфирной: слотов — {{count}}.', restoreDropped: 'Ссылок не восстановлено: {{count}} — группа или стык были удалены после применения.', apply: 'Применить', applied: 'Эфир пересобран, записей: {{count}}', applyReport: 'Отчёт о применении', applyReportWarnings: 'Планировщик выполнил не все правила — предупреждений: {{count}}. Эфир собран, но эти места стоит посмотреть.', weekdays: { 0: 'Вс', 1: 'Пн', 2: 'Вт', 3: 'Ср', 4: 'Чт', 5: 'Пт', 6: 'Сб', }, dayparts: { Morning: 'Утро', Day: 'День', Prime: 'Прайм', Night: 'Ночь' }, slotKinds: { Content: 'Контент', Repeat: 'Повтор', SignOff: 'Конец вещания' }, blockModes: { Count: 'Единиц', Duration: 'Минут', FillSlot: 'Весь слот' }, overflows: { ContinueNext: 'Доиграть в следующий раз', ExtendSlot: 'Растянуть слот', SkipIfNotFits: 'Не начинать', }, strategies: { Sequential: 'По порядку', RandomWithCooldown: 'Случайно с остыванием', Fixed: 'Фиксированный элемент', }, warnings: { SlotEmpty: 'Слот не дал контента', DriftExceeded: 'Старт ушёл за допуск', CooldownExhausted: 'Остывание отсекло всех', RepeatSourceEmpty: 'Нечего повторять', FallbackEmpty: 'Нечем закрыть паузы', CandidatesFiltered: 'Возрастной потолок отсёк всех', BreakLimitExceeded: 'Врезок в часе больше потолка', GenreShareExceeded: 'Доля жанра выше нормы', FallbackShareExceeded: 'Аварийного запаса в эфире больше нормы', }, title: 'Каналы', createTitle: 'Создать канал', name: 'Название', slug: 'Slug', state: 'Состояние', enabled: 'В эфире', enabledLabel: 'Канал в эфире', settings: 'Настройки', filler: 'Заглушка', noFiller: 'Без заглушки', noSchedule: 'Расписание ещё не построено', airToday: 'Сегодня', airCount: 'Записей за сутки: {{count}}', }, junctions: { title: 'Стыки', hint: 'Что играет между программами: реклама, анонсы, заставки. Стык общий для всех каналов — канал только выбирает, какой поставить в слот.', newName: 'Новый стык', empty0: 'Стыков пока нет.', empty: 'пусто', from: 'конец', to: 'начало', fork: 'развилка', addElement: '+ врезка', maxTotal: 'Потолок, с', usedInChannels: 'в каналах: {{count}}', cannotDeleteUsed: 'Стык используется каналами — сначала снимите ссылки.', splitOut: 'Вынести из развилки', mergeIntoFork: 'Объединить с предыдущей в развилку', element: 'Врезка', kind: 'Тип', kinds: { Ad: 'Реклама', Promo: 'Анонс', Bumper: 'Заставка', Filler: 'Заполнитель', }, group: 'Группа', pickGroup: 'выберите группу', bumperTemplate: 'Блок заставки', pickBumper: 'выберите блок', bumperVariant: 'Подблок', bumperVariantAuto: 'по триггеру и весам', bumperVariantHint: 'Оставьте «по триггеру и весам», чтобы на смене шоу и между сериями играли разные тексты.', amountMode: 'Чем меряется', amountModes: { Count: 'Единиц', Duration: 'Минут' }, count: 'Сколько единиц', minutes: 'Сколько минут', minutesShort: ' мин', amountHint: 'В смешанной группе (ролики и готовые блоки) считайте минутами: одна «единица» там — то ли ролик, то ли блок.', required: 'Обязательная', requiredHint: 'Обязательность решает, кого выбросить при нехватке времени. На порядок показа она не влияет — он задаётся местом в цепочке.', choiceWeight: 'Вес в развилке', choiceWeightHint: 'Чем больше — тем чаще играет именно эта врезка (0 — не выбирается).', conditions: 'Условия показа', onlyOnChange: 'Только при смене шоу', chance: 'Вероятность, %', chanceHint: 'Вероятность и интервал считаются по этой врезке отдельно; жребий берётся из seed генерации, поэтому пересборка не тасует врезки.', minInterval: 'Не чаще, чем раз в, мин', dayparts: 'Дейпарты', daypartsHint: 'Ничего не выбрано — врезка идёт в любых дейпартах.', timeWindow: 'Окно суток канала', clearWindow: 'Сбросить', timeWindowHint: 'Пусто — в любое время. Окно может переходить через полночь.', nearHour: 'Рядом с круглым часом', nearHourUnit: 'мин до и после :00', nearHourHint: '0 — без привязки. Сигнал точного времени и джингл ставятся так: врезка играет только в окрестности :00, а не когда-нибудь в этом часе.', badgeNearHour: '±{{minutes}} мин от часа', badgeOnChange: 'на смене', badgeInterval: 'раз в {{minutes}} мин', }, bumpers: { title: 'Заставки', hint: 'Блоки заставок общие для всех каналов: оформление и звук — у блока, текст — у подблоков. В эфир заставка попадает врезкой стыка.', empty: 'Блоков заставок пока нет.', newName: 'Название блока', createTitle: 'Создать блок заставки', newNamePlaceholder: 'Название блока', newVariantName: 'Новый текст', sampleChannel: 'Смотреть глазами канала', sampleChannelNone: 'без канала', sampleChannelHint: 'Блок общий, но образцы подстановки берутся у выбранного канала.', name: 'Название', font: 'Шрифт', fontSans: 'Гротеск', fontSerif: 'Антиква', colorBg: 'Фон (цвет 1)', colorBg2: 'Фон (цвет 2)', colorAccent: 'Акцент', colorText: 'Текст', saveStyle: 'Сохранить оформление', seconds: 'с', defaultDuration: '≈8 с (джингл)', variantsCount: 'текстов: {{count}}', usedInJunctions: 'во врезках: {{count}}', cannotDeleteUsed: 'Блок используется во врезках стыков — сначала снимите ссылки.', audio: 'Звук', audioHint: 'Длина звука задаёт длительность заставки; без него — синтезированный джингл.', background: 'Фон', backgroundFieldHint: 'Картинка фона блока; иначе — градиент палитры или постер шоу.', backgroundPick: 'Выбрать из галереи', backgrounds: { Template: 'Фон блока', NextPoster: 'Постер следующего шоу', NowPoster: 'Постер предыдущего шоу', Clip: 'Готовый ролик', }, backgroundClip: 'Ролик-подложка', pickClip: 'Выберите ролик', backgroundClipHint: 'Текст пишется поверх видео. Длину заставки задаёт сам ролик; звук блока подрезается под неё, а без звука играет оригинальная дорожка ролика.', fileLoaded: 'загружено', fileDefault: 'по умолчанию', upload: 'Загрузить', reset: 'Сбросить', variants: 'Подблоки (тексты)', variantsHint: 'Разный текст на одной музыке и оформлении. Правило показа и фон — у каждого подблока свои.', addVariant: 'Добавить текст', variantName: 'Название', trigger: 'Показывать', triggers: { OnShowChange: 'При смене шоу', BetweenEpisodes: 'Между сериями', Both: 'Оба', }, weight: 'Вес', lineStyles: { Label: 'Подпись', Title: 'Название', Caption: 'Мелкая' }, lineColors: { Accent: 'Акцент', Text: 'Текст' }, addLine: 'Строка', presets: 'Пресеты', preset_nowNext: 'Сейчас / Далее', preset_nextAt: 'Далее в …', preset_channel: 'Логотип канала', presetNow: 'СЕЙЧАС', presetNext: 'ДАЛЕЕ', presetNextAt: 'ДАЛЕЕ В', framePreview: 'Кадр', framePreviewHint: 'Подстановка образцами: так строка будет выглядеть в эфире.', unknownPlaceholder: 'Неизвестный плейсхолдер: {{tokens}}', volatileHint: 'каждый показ уникален — кэш рендера не работает', render: 'Отрендерить примеры', rendering: 'Рендерим…', renderHint: 'Настоящий ffmpeg-рендер всех подблоков со звуком — несколько секунд.', tokens: { channel: 'Название канала', 'channel.number': 'Номер канала', 'now.title': 'Текущее шоу', 'next.title': 'Следующее шоу', 'now.episode': 'Серия текущего', 'next.episode': 'Серия следующего', 'next.year': 'Год следующего', 'next.genre': 'Жанр следующего', 'next.time': 'Время старта следующего', 'tonight.title': 'Вечерняя программа', 'tonight.time': 'Время вечерней программы', 'tomorrow.title': 'Программа завтра вечером', 'tomorrow.time': 'Время программы завтра', time: 'Время показа заставки', date: 'Дата', weekday: 'День недели', slot: 'Название слота', }, }, storage: { title: 'Хранилище', volume: 'Том хранилища', volumeUnknown: 'Файловая система не отдала размер тома — показано только занятое хранилищем.', totalVolume: 'Всего на томе', ours: 'Занято TeleWave', foreign: 'Прочее на томе', foreignHint: 'база, система, чужие файлы', free: 'Свободно', lowSpace: 'Свободного места меньше порога ({{threshold}}) — загрузка новых файлов отклоняется.', recount: 'Пересчитать', computedAt: 'посчитано в {{time}}', breakdown: 'Из чего складывается', filesTotal: 'файлов: {{count}}', files: 'файлов: {{count}}', empty: 'Хранилище пустое.', areas: { Assets: 'Сегменты программ', BumperAssets: 'Сегменты заставок', Originals: 'Исходники', Inbox: 'Inbox', ManualInbox: 'Ручной inbox', Uploads: 'Незавершённые загрузки', BumperSources: 'Звук и фоны заставок', Images: 'Изображения', Other: 'Прочее под корнем', }, areaHints: { Assets: 'HLS-нарезка библиотеки — обычно почти весь объём', BumperAssets: 'отрендеренные заставки; чистятся вместе с расписанием', Originals: 'остаются, только если включён Storage:KeepOriginals', Inbox: 'то, что ещё не разобрал сканер', ManualInbox: 'то, что забирается в шоу вручную', Uploads: 'перевалочный каталог; залежавшееся — след оборванной заливки', BumperSources: 'сырые файлы блоков, не нарезка', Images: 'постеры, кадры, логотипы', Other: 'файлы мимо известных каталогов — обычно ноль', }, }, maintenance: { title: 'Обслуживание', warning: 'Операции необратимы — удаляют данные и файлы навсегда.', clearMedia: 'Очистить все медиа', clearMediaHint: 'Удалит все загруженные файлы и их нарезку, а также расписание каналов.', confirmClearMedia: 'Удалить ВСЕ медиа-файлы безвозвратно?', clearShowMedia: 'Удалить медиа шоу', clearShowMediaHint: 'Удалит файлы всех серий выбранного шоу и очистит его серии.', confirmClearShowMedia: 'Удалить все медиа выбранного шоу безвозвратно?', pickShow: 'Выберите шоу', deleteShows: 'Удалить все шоу', deleteShowsHint: 'Удалит все шоу и их серии. Медиа-файлы останутся в библиотеке.', confirmDeleteShows: 'Удалить ВСЕ шоу безвозвратно?', doneCount: 'Удалено: {{count}}', }, settings: { title: 'Настройки', registration: 'Регистрация', registrationHint: 'Когда выключено — новые пользователи не могут регистрироваться сами, учётки заводит только администратор.', registrationLabel: 'Разрешить регистрацию на сайте', preferredAudio: 'Предпочитаемые озвучки', channelNumbers: 'Переключение каналов по номерам', channelNumbersHint: 'Зритель переключает каналы стрелками, как на телевизоре. Сетка каналов остаётся всегда.', preferredAudioHint: 'Коды языков через запятую в порядке приоритета (напр. «rus, eng»). Если в файле есть дорожка с таким языком — при обработке выбирается она (по порядку); иначе — выбор ffmpeg по умолчанию. Применяется к новым обработкам.', }, metadata: { title: 'Метаданные', pickPoster: 'Из галереи', name: 'Название', originalName: 'Оригинальное название (eng)', originalNamePlaceholder: 'Например: Family Guy', originalNameHint: 'По нему ищутся метаданные; на экранах показывается обычное название.', sourceLabel: 'Источник', searchBtn: 'Искать', nothingFound: 'Ничего не найдено', apply: 'Применить', applied: 'Метаданные применены', overview: 'Описание', year: 'Год', clear: 'Очистить', noPoster: 'Нет постера', refreshEpisodes: 'Обновить серии', refreshing: 'Обновляем…', refreshedCount: 'Обновлено серий: {{count}}', findMissing: 'Найти отсутствующие серии', missingTitle: 'Отсутствующие серии', missingNoSeasons: 'В шоу нет загруженных серий с распознанными номерами.', seasonN: 'Сезон {{n}}', loadedOf: 'загружено {{loaded}} из {{total}}', missingUnknown: 'Источник не отдал число серий этого сезона.', missingNone: 'Все серии на месте.', missingList: 'Не хватает', findMissingSeasons: 'Найти отсутствующие сезоны', missingSeasonsTitle: 'Отсутствующие сезоны', missingSeasonsUnknownTotal: 'Источник не отдал список сезонов этого сериала.', missingSeasonsNone: 'Все сезоны на месте.', missingSeasonsList: 'Не загружено ни одной серии из сезонов', missingSeasonsUnknown: 'Источник не знает загруженные сезоны: {{list}}.', }, }, }