/** Словарь локали «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: 'Загрузить',
      preview: 'Просмотр',
      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}}',
      debugExport: 'Дебаг-экспорт',
      debugExportHint:
        'Архив со снимком канала: сетка, группы, курсоры слотов, лента и сухой прогон — для разбора, почему эфир собрался именно так.',
      airHidePast: 'Скрывать прошедшее',
      airHideBreaks: 'Скрывать врезки',
    },
    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}}.',
    },
  },
}
