/** Словарь локали «en». Плоская правка: ключи обеих локалей обязаны совпадать. */
export const en = {
  appName: 'TeleWave',
  nav: {
    dashboard: 'On Air',
    admin: 'Admin',
    settings: 'Settings',
    login: 'Log in',
    register: 'Sign up',
    logout: 'Log out',
  },
  theme: { light: 'Light', dark: 'Dark', system: 'System' },
  lang: { ru: 'RU', en: 'EN' },
  common: {
    save: 'Save',
    cancel: 'Cancel',
    retry: 'Retry',
    close: 'Close',
    delete: 'Delete',
    create: 'Create',
    loading: 'Loading…',
    error: 'Something went wrong',
    search: 'Search',
    actions: 'Actions',
    prevPage: 'Previous page',
    nextPage: 'Next page',
    yes: 'Yes',
    no: 'No',
  },
  home: {
    title: 'TELEWAVE',
    subtitle: 'BROADCAST CHANNEL GRID',
    tagline: 'Your channels. Your broadcast. Anytime.',
    cta: 'Go on air',
    ctaRegister: 'Create account',
  },
  auth: {
    userName: 'Username',
    password: 'Password',
    loginTitle: 'Sign in',
    loginSubtitle: 'Enter your credentials to access the channel grid',
    registerTitle: 'New viewer',
    registerSubtitle: 'Create an account to set up your channel grid',
    submitLogin: 'Log in',
    submitRegister: 'Sign up',
    noAccount: "Don't have an account?",
    haveAccount: 'Already have an account?',
    registrationClosed: 'Registration is closed',
    registrationClosedHint:
      'Public registration is disabled. An administrator can create an account for you.',
    invalidCredentials: 'Invalid username or password',
    userNameTaken: 'This username is already taken',
    blocked: 'Account blocked by an administrator',
    genericError: 'Could not sign in. Please try again',
  },
  air: {
    now: 'Now',
    next: 'Up next',
    ad: 'Ad',
    bumper: 'Bumper',
    fallback: 'Emergency filler',
    signOff: 'Sign-off',
    episode: 'Episode',
    collectionPart: 'part {{part}} of {{total}}',
    volume: 'Volume',
    live: 'Live',
    noChannels: 'No channels available yet. Check back later.',
    offline: 'This channel is off the air',
    offlineHint: 'No schedule or content yet. Check back later.',
    retry: 'Retry',
    numbersHint: '↑ / ↓ — switch channels by number',
    iptv: 'IPTV playlist',
    iptvHint:
      'An M3U with every channel and the programme guide — for VLC, TiviMate and the like. Access to the air is baked into the file itself: do not share it.',
    iptvDone: 'Playlist downloaded. The file grants access to the air — do not pass it on.',
    iptvFailed: 'Could not build the playlist',
  },
  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: {
    groups: {
      title: 'Groups',
      createTitle: 'Create a group',
      hint: 'A group is what may go on air. Grid slots reference it; the strategy picks an element from it.',
      name: 'Name',
      description: 'Description',
      items: 'Items',
      units: 'Units',
      duration: 'Runtime',
      hoursShort: 'h',
      minutesShort: 'min',
      hasFilter: 'has rule',
      composition: 'Composition',
      empty: 'Group is empty',
      orderHint: 'Drag to set the order — sequential strategies follow it.',
      showAdvanced: 'Advanced',
      hideAdvanced: 'Hide',
      find: 'Find matches',
      addFound: 'Add found',
      found: 'Found: {{total}}, new: {{fresh}}',
      added: 'Items added: {{count}}',
      alreadyIn: 'already in group',
      mode: {
        label: 'Composition',
        Static: 'Explicit list',
        Dynamic: 'By rule',
        StaticHint: 'Items are added by hand; the rule only helps to find them.',
        DynamicHint:
          'Composition is computed by the rule on every read — new library items go on air by themselves.',
      },
      pinned: 'pinned',
      byRule: 'by rule',
      excluded: 'Excluded',
      excludeHint: 'Exclude from the composition — the rule will stop adding it back',
      restore: 'Restore',
      dynamicOrderHint:
        'Order: pinned items first (drag to arrange), then rule matches alphabetically.',
      previewComposition: 'With this rule the composition would be: {{count}}',
      pendingFound: 'New matches for this rule: {{count}} —',
      pendingAdd: 'Add all ({{count}})',
      elementKinds: { Show: 'Show', Collection: 'Collection' },
      suggestions: {
        title: 'Suggestions',
        hint: 'Built from the current library. The button creates a group with these items and saves the rule — later additions are then one click away.',
        stats: '{{shows}} shows · {{units}} units',
        exists: 'already exists',
        created: 'Group created',
        allCreated: 'Every suggestion has been created already.',
        showExisting: 'Show already created ({{count}})',
        hideExisting: 'Hide already created',
        kinds: {
          Genre: 'genre',
          ShowKind: 'kind',
          Audience: 'rating',
          BigSeries: 'big series',
        },
      },
      filter: {
        title: 'Selection rule',
        hint: 'In an explicit list the rule only finds candidates; in a rule-based group it is the composition.',
        elementKinds: 'What to search',
        showKinds: 'Show type',
        showKindsHint:
          'Unchecked means films and series. Clips live in their own library and join the selection only when checked explicitly.',
        genres: 'Genres',
        genresHint: 'Any of the checked ones.',
        maxAudience: 'No stricter than',
        minAudience: 'No softer than',
        minAudienceHint:
          'For late-night slots. Unlike the upper bound, unrated shows are excluded — otherwise unrated cartoons would slip into the adult group.',
        audienceHint:
          'Ratings are ordered by strictness: G → PG → PG-13 → R → NC-17. Unrated shows stay in the selection.',
        year: 'Year',
        unitMinutes: 'Unit runtime, min',
        unitMinutesHint:
          'Average episode or film length. Items without ready assets are not filtered out.',
        from: 'from',
        to: 'to',
        any: 'any',
      },
    },
    collections: {
      posters: 'Fill posters',
      postersHint:
        'Takes the first part’s cover for collections that have no poster yet. A hand-picked one is left alone.',
      postersDone: 'Posters filled: {{count}}',
      title: 'Collections',
      createTitle: 'Create a collection',
      hint: 'A franchise is an ordered set of films played as a single unit.',
      name: 'Name',
      description: 'Description',
      parts: 'Parts',
      units: 'Units',
      addShow: 'Add show',
      pickShow: 'Pick a show',
      empty: 'Collection is empty',
      orderHint: 'Drag to set the order of the parts — that is the order they air in.',
      suggestions: {
        title: 'Franchise suggestions',
        hint: 'Built from movies not in any collection yet: what to assemble into a new franchise and what to add to an assembled one. Check the parts — the name-based guess sometimes misses.',
        parts: '{{count}} parts',
        exists: 'already exists',
        additionBadge: 'addition',
        add: 'Add',
        added: 'Added to the collection: {{count}}',
        created: 'Collection created',
        allCreated: 'Every suggestion has been created already.',
        showExisting: 'Show already created ({{count}})',
        hideExisting: 'Hide already created',
        sources: { Metadata: 'from metadata', NameGuess: 'by names' },
      },
    },
    interstitials: {
      title: 'Clips',
      hint: 'Ads, promos and jingles. Drag clips into the block builder or a group on the right — a block is saved as a collection and airs as a whole.',
      upload: 'Upload clips',
      name: 'Name',
      duration: 'Duration',
      empty: 'No clips yet',
      noAsset: 'No file',
      blocks: 'Blocks',
      noBlocks: 'No blocks yet',
      clipsCount: '{{count}} clips',
      blockBuilder: 'Block builder',
      blockName: 'Block name',
      blockTotal: 'Block duration',
      dropHint: 'Drop clips here',
      groups: 'Clip groups',
      pickGroup: 'Pick a group',
      pickGroupFirst: 'Pick a group first',
      dropToGroup: 'Drop a clip or a block here',
      openGroup: 'Open group',
      newGroupName: 'Group name',
      createGroupTitle: 'Create a clip group',
    },
    genres: {
      title: 'Genres',
      hint: 'Genre reference: content groups are built from it, provider metadata is mapped into it.',
      name: 'Name',
      slug: 'Key',
      slugHint: 'Latin letters, no spaces — the genre is matched by it when the reference updates.',
      aliases: 'Spellings',
      aliasesHint: 'Comma-separated. Maps provider genres onto yours: tmdb:28, action, боевик.',
      order: 'Order',
      usage: 'Shows',
      system: 'System',
      create: 'New genre',
      edit: 'Edit genre',
    },
    roles: {
      title: 'Roles',
      name: 'Name',
      system: 'System',
      create: 'New role',
      rename: 'Rename',
    },
    users: {
      title: 'Users',
      userName: 'Username',
      role: 'Role',
      status: 'Status',
      createdAt: 'Joined',
      blocked: 'Blocked',
      active: 'Active',
      block: 'Block',
      unblock: 'Unblock',
      resetPassword: 'Password',
      resetPasswordFor: 'Reset password: {{name}}',
      newPassword: 'New password',
      passwordReset: 'Password changed',
      filterAll: 'All roles',
      createTitle: 'Create user',
      password: 'Password',
      passwordHint: 'At least 8 characters, including a digit and an uppercase letter.',
      create: 'Create',
      created: 'User created',
    },
    movies: {
      title: 'Movie import',
      hint: 'The file name is parsed into a title and a year, and the movie is looked up in the metadata source. Every file becomes its own show; fix the rows where the match is not exact.',
      sources: { manual: 'From manual folder', disk: 'From my disk' },
      pickFiles: 'Pick files',
      scan: 'Parse ({{count}})',
      scanning: 'Searching the source…',
      nothingYet: 'Pick a source and press "Parse".',
      noProvider: 'No metadata source configured — there is nothing to search with.',
      file: 'File',
      parsed: 'Title and year',
      candidate: 'Candidate',
      toggleAll: 'Select all / clear all',
      year: 'Year',
      research: 'Search again',
      empty: 'nothing found',
      noCandidate: 'no metadata',
      import: 'Create ({{count}})',
      imported: 'Movies created: {{created}}, with metadata: {{enriched}}',
      uploadStarted: 'Files are uploading — movies appear as they finish',
      collectionsHint:
        'Franchises turn into collections after the import — the suggestions wait on the Collections tab.',
      statuses: {
        Confident: 'exact match',
        Uncertain: 'check this',
        NotFound: 'not found — type the title',
        AlreadyInLibrary: 'already in the library',
      },
    },
    media: {
      title: 'Media',
      upload: 'Upload',
      preview: 'Preview',
      manualButton: 'From manual folder',
      importButton: 'Import',
      importTitle: 'Media import',
      importHint:
        'Three ways to fill the library: episodes from the manual folder into a show, movies in bulk, and uploading picked files into a show.',
      importTabs: {
        movies: 'Movies',
        manual: 'Episodes from manual',
        toShow: 'Picked files into a show',
      },
      moviesButton: 'Movies',
      manualTitle: 'Manual pick from the manual folder',
      manualHint:
        'Files in manual/ are not picked up by the scanner — select the ones you need and choose a show. Imported files leave the folder, just like from inbox.',
      manualSelectAll: 'Select all',
      manualSelected: 'Selected: {{count}}',
      manualEmpty: 'The manual folder is empty',
      regexPickHint: 'Click a number in the file name — a rule for all files is built from it:',
      regexPickTitle: 'This is the episode number',
      regexPresets: 'Ready-made:',
      regexPresetNames: {
        seriesWord: 'Серия N',
        episodeWord: 'Эпизод N',
        seasonEpisode: 'SxxEyy',
        productionCode: '2ACV01',
        afterDash: 'after a dash',
        firstNumber: 'first number',
      },
      regexClear: 'clear',
      manualAlready: 'already in the library',
      manualRoot: 'manual/ root',
      manualRecognized: 'Recognized: {{count}} of {{total}}',
      manualCleanupHint:
        'Files leave the folder; siblings (subtitles, nfo) and the emptied folder are removed.',
      manualTruncated: 'Showing the first 500 files — there are more in the folder.',
      manualShow: 'Show',
      manualDetected: 'Detected from the release name — check it and change if wrong.',
      manualPickShow: 'Pick a show',
      manualImport: 'Import into show',
      manualImported: 'Files imported: {{count}}',
      uploadToShow: 'Upload to show',
      toShowTitle: 'Upload and add to show',
      toShowPick: 'Pick files',
      toShowEmpty: 'No files picked',
      toShowPicked: 'Files picked: {{count}}',
      autoDetectHint:
        'Each file is linked to the show whose original (or display) name appears in the release name, e.g. “The.Simpsons.S33E01…” → The Simpsons.',
      toShowLibrary: 'To library',
      toShowSeason: 'Season (manual)',
      toShowAuto: 'auto',
      toShowRegex: 'Episode regex',
      toShowRegexInvalid: 'invalid regex',
      toShowHint: String.raw`Season and regex are optional: numbers are usually detected automatically (see below). Regex: 1 group = episode, 2 groups = season and episode. Example: ^(\d+) for “01. Title.mkv”.`,
      toShowPreview: 'What we detect',
      toShowMatched: 'show detected for {{matched}} of {{total}}',
      applyToAll: 'Set for all…',
      toShowUnknown: '—',
      toShowConfirm: 'Upload and add',
      uploadedCount: 'Uploaded files: {{count}}',
      uploadingCount: 'Uploading {{done}}/{{total}}',
      cancelAll: 'Cancel all uploads',
      skippedDuplicates: 'Skipped duplicates: {{count}}',
      queueDrained: 'Processing finished — the queue is empty',
      filterActive: 'Active',
      filterAll: 'All',
      name: 'File',
      status: 'Status',
      duration: 'Duration',
      resolution: 'Resolution',
      processingTime: 'Processing time',
      empty: 'No uploaded files yet',
      statuses: {
        Pending: 'Queued',
        Processing: 'Processing',
        Ready: 'Ready',
        Failed: 'Failed',
      },
      stats: {
        queued: 'Currently queued',
        queuedShort: 'Queued',
        processing: 'Currently processing',
        processingShort: 'Processing',
        average: 'Average processing time (recent)',
        averageShort: 'Avg time',
        eta: 'Estimated time until the queue drains: work left at the average rate, divided by the number of parallel transcodes',
        etaShort: 'Left',
        etaSoon: 'under a minute',
        hoursShort: 'h',
        minutesShort: 'min',
      },
    },
    gallery: {
      title: 'Gallery',
      upload: 'Upload',
      empty: 'No images in this category yet',
      pickHint: 'Pick an image or upload a new one',
      browseHint: 'All app images by category',
      sort: {
        newest: 'Newest first',
        oldest: 'Oldest first',
        nameAsc: 'Name: A–Z',
        nameDesc: 'Name: Z–A',
      },
      categories: {
        Library: 'Library',
        ShowPoster: 'Show posters',
        EpisodeStill: 'Episode stills',
        BumperBackground: 'Bumper backgrounds',
      },
    },
    shows: {
      title: 'Shows',
      createTitle: 'Create a show',
      name: 'Name',
      originalName: 'Original name (eng)',
      kind: 'Kind',
      kinds: { Series: 'Series', Single: 'Movie', Interstitial: 'Clip' },
      pickerPlaceholder: 'Pick a show',
      pickerSearch: 'Search by name…',
      pickerEmpty: 'Nothing found',
      pickerCreate: 'Create series “{{name}}”',
      pickerCreateSingleHint: 'Create as a movie',
      pickerCreated: 'Show created',
      audience: 'Rating',
      audienceUnset: 'Not rated',
      audiences: {
        G: 'G — general audiences',
        PG: 'PG — parental guidance',
        'PG-13': 'PG-13 — over 13',
        R: 'R — under 17 with adult',
        'NC-17': 'NC-17 — adults only',
      },
      seasons: 'Seasons',
      loadedSeasons: 'Loaded seasons',
      genre: 'Genre',
      allGenres: 'All genres',
      allKinds: 'All kinds',
      deleteTitle: 'Delete the show?',
      deleteHint: '“{{name}}” will be removed from the library. This cannot be undone.',
      deleteWithMedia: 'Delete its media too: {{count}} file(s)',
      deleteWithMediaHint:
        'Otherwise the files stay in the media list with no owner and keep taking space.',
      deleteWithSchedule: 'Cut it from the upcoming air too',
      deleteWithScheduleHint:
        'Without this the deletion is refused while the show sits in the feed. Viewers see the filler where it was until the tail is rebuilt.',
      otherGenres: 'Other genres',
      bulk: {
        selected: 'Selected: {{count}}',
        selectPage: 'Select all on this page',
        noGenre: 'Keep genres',
        keepAudience: 'Keep rating',
        apply: 'Apply',
        tagged: 'Shows updated: {{count}}',
        clear: 'Clear selection',
        enrich: 'Metadata from {{provider}}',
        enrichHint:
          'Search by name and apply — only for shows with no source linked. Ambiguous matches are skipped.',
        enriching: 'Searching…',
        enriched: 'Metadata applied: {{count}}',
        moreSkipped: 'Skipped as well: {{count}}',
        toCollection: 'To collection…',
        toCollectionHint:
          'The selected shows go into a franchise. Parts are ordered by release year; drag to rearrange them in the collection card.',
        newCollection: 'New collection',
        addToCollection: 'Add ({{count}})',
        addedToCollection: '“{{name}}” updated: {{count}}',
      },
      genresEmpty: 'No genres set',
      genresEdit: 'Genres',
      genresHint:
        'Pick the genres of the show. The primary one is listed; all of them are used for selection.',
      genrePrimary: 'primary',
      inGroups: 'In groups',
      inChannels: 'Scheduled on',
      inCollections: 'Part of collections',
      episodes: 'Episodes',
      episode: 'Episode',
      noEpisodes: 'No episodes yet',
      addMedia: 'Add media',
      addMediaHint: 'Ready files not yet in this show. The selected ones are added as episodes.',
      filterAssets: 'Filter by name, e.g. Family.Guy.S16',
      selectAll: 'Select all',
      deselectAll: 'Clear',
      addSelected: 'Add selected',
      noMatches: 'No matching files',
      addedCount: 'Episodes added: {{count}}',
      candidatesTruncated:
        'Not all files are shown (too many ready assets) — narrow the filter to see the rest.',
    },
    channels: {
      number: 'Number',
      numberPlaceholder: 'not set',
      utcOffset: 'Time zone, h',
      utcOffsetHint:
        'Whole hours: 3 is Moscow, 0 is UTC, −5 is New York. The whole grid and the schedule are expressed in this time.',
      dayStart: 'Broadcast day starts',
      dayStartHint: 'The night block before this time belongs to the previous day.',
      disabled: 'disabled',
      layers: 'Layers',
      layersHint: 'Higher priority wins. The background layer sits below all and fills gaps.',
      background: 'background',
      newLayerName: 'New layer',
      addSlotHere: 'Add slot',
      newSlot: 'New slot',
      /** Grid auto-build by profile: plan preview and creation. */
      transfer: {
        action: 'Import / export',
        title: 'Grid configuration exchange',
        hint: 'The file references groups and junctions by name, so it moves between channels and installations. The AI request is not sent anywhere — you copy it into your own model and bring the answer back to the import tab.',
        tabs: { export: 'Export', import: 'Import', ai: 'AI request' },
        exportHint:
          'Exports layers, slots, rules and references to groups and junctions. Works both as a backup before an experiment and as a sample for a model.',
        exportAction: 'Download file',
        exported: 'File exported',
        importHint:
          'Load a file or paste JSON — a model answer, for example. The file may bring missing groups and collections with it; shows are never created — those come from media uploads. Unknown references do not break the import: such slots are skipped and listed below.',
        pickFile: 'Pick a file',
        replace: 'Remove existing slots',
        pastePlaceholder: 'Paste the grid configuration JSON',
        importAction: 'Import',
        imported: 'Slots created: {{slots}}, skipped: {{skipped}}',
        importedLibrary: 'Groups created: {{groups}}, collections: {{collections}}',
        badJson: 'That does not look like JSON — check that the whole answer was copied.',
        warnings: 'What could not be resolved',
        aiHint:
          'We build the request: channel settings, your groups and library, your references and notes, plus the answer schema. The model may define the groups it needs — the import creates them.',
        references: 'Reference channels',
        referencesPlaceholder: 'Cartoon Network, Paramount Comedy',
        referencesHint:
          'Comma-separated. The model matches their rhythm — block length, prime, night.',
        notes: 'Notes',
        notesPlaceholder:
          'For example: kids in the morning, films in the evening, adult block at night',
        buildPrompt: 'Build the request',
        copyPrompt: 'Copy',
        copied: 'Request copied',
        aiNext: 'Send this to your model and paste the JSON you get back on the "Import" tab.',
      },
      generate: {
        action: 'Build grid',
        title: 'Grid auto-build',
        hint: 'A profile sets the rhythm of the day: block length, what airs in prime time and what fills the night. Groups are picked from the existing ones by rating, content type and how many units they hold.',
        modes: {
          FillGaps: 'Fill gaps',
          Rebuild: 'Rebuild the week',
        },
        modeHints: {
          FillGaps: 'Existing slots are left alone — only uncovered time is filled.',
          Rebuild:
            'All template slots are removed; the weekend is built as a separate layer on top.',
        },
        summary: 'Slots to create: {{slots}} — that is {{percent}}% of the free week.',
        willRemove: 'Slots to remove: {{count}}.',
        fallback: 'The fallback group will be "{{name}}" — it covers pauses between slots.',
        nothing: 'Nothing to create: there is no free time in the grid.',
        create: 'Build',
        done: 'Created {{created}} slots, removed {{removed}}.',
      },
      editSlot: 'Slot',
      slotTitle: 'Block title',
      slotStart: 'Start',
      slotDuration: 'Budget, min',
      weekday: 'Weekday',
      everyDay: 'every day',
      daypart: 'Daypart',
      slotKind: 'Slot type',
      group: 'Group',
      pickGroup: 'Pick a group',
      strategy: 'Strategy',
      cooldownDays: 'Cooldown, days',
      cooldownHint: 'Skip what already aired within this period.',
      blockMode: 'Block',
      blockValue: 'Value',
      overflow: 'If it does not fit',
      overflowHint: 'What to do with an element that exceeds the slot budget.',
      repeatDaysAgo: 'Days ago',
      repeatTime: 'Time',
      repeatDuration: 'Duration, min',
      anchor: 'Anchor',
      anchorHint:
        'An anchor keeps the start hard: nothing that would cross it is started. Snapping is soft — it shifts the start to a round time if that fits the allowance.',
      maxDrift: 'Allowance, min',
      snap: 'Snap',
      snapOff: 'off',
      resizeSlot: 'Drag the edge to change duration',
      copyDay: 'Copy day',
      copyDayFrom: 'Copy {{day}} to:',
      copy: 'Copy',
      layerVisible: 'Show layer',
      layerName: 'Layer name',
      layerApplicability: 'When it applies',
      applicabilityHint:
        'Sections are OR-ed: the layer applies when the date matches at least one condition. Nothing filled in — it always applies.',
      applicabilityWeekdays: 'Weekdays',
      applicabilityDateRanges: 'Date ranges',
      applicabilityAnnual: 'Yearly (month / day)',
      applicabilityDates: 'Specific dates',
      applicabilityNone: 'not set',
      showForDate: 'Grid for date',
      allDates: 'All layers',
      tabs: {
        grid: 'Grid',
        rules: 'Rules',
        junctions: 'Junctions',
        viewer: 'Viewer',
        settings: 'Settings',
        air: 'On air',
      },
      noTemplate: 'The channel grid failed to load',
      createTemplate: 'Create the grid',
      createTemplateHint:
        'An empty grid with a background layer appears — then add layers and slots.',
      rules: 'Candidate rules',
      rulesHint:
        'Hard filters: they cut out what is not allowed before the draw. Like grid edits, they do not move the air — apply to take effect.',
      audienceWindows: 'Family hours',
      noAudienceWindows: 'No windows — the age is not limited.',
      audienceWindowsHint:
        'Inside a window only content no stricter than the chosen category airs. A window may cross midnight. Content with no category is never dropped.',
      from: 'From',
      to: 'To',
      maxAudience: 'No stricter than',
      repeatLimit: 'Repeat cap',
      repeatWindowDays: 'Window, days',
      repeatMax: 'At most, times',
      repeatLimitHint:
        'Counted against the already recorded tape. If every candidate hits the cap the slot is still filled: empty air is worse than an early repeat.',
      preview: 'Preview',
      previewHide: 'Hide preview',
      previewHint: 'A run against the current rules: nothing is written, slot cursors do not move.',
      previewDays_one: '{{count}} day',
      previewDays_other: '{{count}} days',
      previewTabs: { programme: 'Programme', tape: 'Tape', problems: 'Problems' },
      noProblems: 'No problems',
      andMore: 'and {{count}} more',
      heatmap: 'Repeats: show × day',
      heatmapTotal: 'total',
      issues: 'Checks: {{errors}} errors, {{warnings}} warnings',
      goToSlot: 'to slot',
      issueKinds: {
        GroupEmpty: 'Empty group',
        GroupTooSmall: 'Too little content',
        GridGap: 'Gap in the grid',
        SlotOverlap: 'Slots overlap',
        CooldownUnreachable: 'Unreachable cooldown',
        AudienceConflict: 'Age conflict',
        GroupMissing: 'No group selected',
      },
      viewer: 'How viewers see it',
      viewerHint:
        'Overlays are drawn on the client on top of the picture — the video is not re-encoded. Everything is off by default.',
      logo: 'Logo',
      noLogo: 'none',
      pickLogo: 'Pick a logo',
      logoCorner: 'Corner',
      logoOpacity: 'Opacity',
      corners: {
        TopLeft: 'Top left',
        TopRight: 'Top right',
        BottomLeft: 'Bottom left',
        BottomRight: 'Bottom right',
      },
      showClock: 'Show a clock',
      analogFilter: 'Analog filter',
      analogFilterHint: 'Strength 0..1. Zero is off; it is very easy to overdo.',
      whyHere: 'Why is this here',
      priority: 'priority',
      traceLayer: 'Layer',
      traceSlot: 'Slot',
      traceGroup: 'Group',
      traceCollection: 'Collection',
      traceStrategy: 'Strategy',
      traceJunction: 'Breaks',
      traceDrift: 'drift {{minutes}} min',
      traceSnapped: 'start snapped',
      traceCooldown: 'cooldown {{days}} d.',
      traceCandidates: 'candidates after cooldown: {{count}}',
      diffSummary: 'Affects {{total}} entries, {{changed}} will change',
      diffSoon: 'Entries changing within 24 hours: {{count}}',
      diffNoChanges: 'The air will not change',
      copyTemplate: 'Copy a grid from a channel',
      pickSourceChannel: 'Pick the source channel',
      copyHere: 'Copy here',
      copyTemplateHint:
        'Layers, slots, junctions and rules of the chosen channel replace this channel grid. Groups are shared and not copied.',
      copyTemplateConfirm:
        'This channel grid will be replaced with the chosen channel grid. Continue?',
      templateCopied: 'Copied: {{layers}} layers, {{slots}} slots',
      copyDroppedBumpers: 'Breaks left without a bumper block: {{count}} — set them up by hand',
      // Not «Reset»: that is already the revert-to-aired action (the restore key).
      reset: 'Clear grid',
      resetConfirm:
        'The whole grid — every layer and slot — will be removed, and the future air built from it deleted. The past and the currently playing entry stay. Continue?',
      resetDone: 'Reset: {{layers}} layers, {{slots}} slots, {{entries}} air entries',
      resetHint:
        'While the channel is on, the tape rebuilds itself from the empty grid — with the fallback group. To keep the air empty, clear it in the rules or turn the channel off.',
      fallbackGroup: 'Fallback group',
      fallbackGroupNone: 'None — channel filler only',
      fallbackGroupHint:
        'What fills a pause when even the background layer is empty. An empty grid plays it for the whole air. Set by grid auto-build unless you pick your own.',
      postChecks: 'Post-checks',
      breakLimit: 'Breaks per hour cap, min',
      genreShare: 'Genre share per day cap, %',
      fallbackShare: 'Background share cap, %',
      postChecksHint:
        'Post-checks run against the finished tape and only warn — nothing is replanned.',
      previewKinds: {
        Program: 'Programme',
        Fallback: 'Background',
        SignOff: 'Sign-off',
        Ad: 'Ad',
        Promo: 'Promo',
        Bumper: 'Bumper',
      },
      previewLoad: 'Breaks per hour, peak — {{peak}} min',
      junctions: 'Junctions',
      junctionsHint:
        'What plays between programmes: ads, promos, bumpers. A slot may pick its own junction, otherwise the default one is used.',
      defaultJunction: 'Default junction',
      noJunction: 'no junction',
      junctionBetween: 'Junction inside the slot',
      junctionAfter: 'Junction after the slot',
      junctionDefault: 'default',
      junctionsUsed: 'Used by this channel',
      junctionsNoneUsed: 'The channel references no junction — nothing plays between programmes.',
      openJunctionEditor: 'Junction editor',
      pendingChanges: 'Rules changed — the air still follows the old ones.',
      restore: 'Discard changes',
      restoreConfirm:
        'Roll the grid back to what is on air? Everything changed since the last apply will be lost.',
      restored: 'The grid is back to the on-air state: {{count}} slots.',
      restoreDropped:
        'References not restored: {{count}} — the group or junction was deleted after the apply.',
      apply: 'Apply',
      applied: 'Air rebuilt, entries: {{count}}',
      applyReport: 'Apply report',
      applyReportWarnings:
        'The planner could not honour every rule — {{count}} warnings. The air is built, but these spots are worth a look.',
      weekdays: {
        0: 'Sun',
        1: 'Mon',
        2: 'Tue',
        3: 'Wed',
        4: 'Thu',
        5: 'Fri',
        6: 'Sat',
      },
      dayparts: { Morning: 'Morning', Day: 'Day', Prime: 'Prime', Night: 'Night' },
      slotKinds: { Content: 'Content', Repeat: 'Repeat', SignOff: 'Sign-off' },
      blockModes: { Count: 'Units', Duration: 'Minutes', FillSlot: 'Whole slot' },
      overflows: {
        ContinueNext: 'Continue next time',
        ExtendSlot: 'Extend the slot',
        SkipIfNotFits: 'Do not start',
      },
      strategies: {
        Sequential: 'In order',
        RandomWithCooldown: 'Random with cooldown',
        Fixed: 'Fixed element',
      },
      warnings: {
        SlotEmpty: 'Slot produced no content',
        DriftExceeded: 'Start drifted past the allowance',
        CooldownExhausted: 'Cooldown ruled out every candidate',
        RepeatSourceEmpty: 'Nothing to repeat',
        FallbackEmpty: 'Nothing to fill pauses with',
        CandidatesFiltered: 'The age cap ruled out every candidate',
        BreakLimitExceeded: 'Breaks in an hour exceed the cap',
        GenreShareExceeded: 'Genre share above the norm',
        FallbackShareExceeded: 'Emergency filler share above the norm',
      },

      title: 'Channels',
      createTitle: 'Create a channel',
      name: 'Name',
      slug: 'Slug',
      state: 'State',
      enabled: 'On air',
      enabledLabel: 'Channel on air',
      settings: 'Settings',
      filler: 'Filler',
      noFiller: 'No filler',
      noSchedule: 'Schedule not built yet',
      airToday: 'Today',
      airCount: 'Entries for the day: {{count}}',
      debugExport: 'Debug export',
      debugExportHint:
        'An archive with a snapshot of the channel: grid, groups, slot cursors, tape and a dry run — to work out why the schedule came out the way it did.',
      airHidePast: 'Hide past entries',
      airHideBreaks: 'Hide breaks',
    },
    junctions: {
      title: 'Junctions',
      hint: 'What plays between programmes: ads, promos, bumpers. A junction is shared across channels — a channel only picks which one a slot uses.',
      newName: 'New junction',
      empty0: 'No junctions yet.',
      empty: 'empty',
      from: 'end',
      to: 'start',
      fork: 'fork',
      addElement: '+ break',
      maxTotal: 'Cap, s',
      usedInChannels: 'in channels: {{count}}',
      cannotDeleteUsed: 'The junction is used by channels — drop the references first.',
      splitOut: 'Take out of the fork',
      mergeIntoFork: 'Merge with the previous one into a fork',
      element: 'Break',
      kind: 'Type',
      kinds: {
        Ad: 'Ad',
        Promo: 'Promo',
        Bumper: 'Bumper',
        Filler: 'Filler',
      },
      group: 'Group',
      pickGroup: 'pick a group',
      bumperTemplate: 'Bumper block',
      pickBumper: 'pick a block',
      bumperVariant: 'Text variant',
      bumperVariantAuto: 'by trigger and weights',
      bumperVariantHint:
        'Keep "by trigger and weights" so show changes and episode breaks get different texts.',
      amountMode: 'Measured in',
      amountModes: { Count: 'Units', Duration: 'Minutes' },
      count: 'How many units',
      minutes: 'How many minutes',
      minutesShort: ' min',
      amountHint:
        'For a mixed group (spots and ready-made blocks) count in minutes: one unit there is either a spot or a whole block.',
      required: 'Required',
      requiredHint:
        'Required only decides what gets dropped when time runs short. It does not affect the order — that is the position in the chain.',
      choiceWeight: 'Weight in the fork',
      choiceWeightHint: 'The higher, the more often this break plays (0 — never picked).',
      conditions: 'Conditions',
      onlyOnChange: 'Only on show change',
      chance: 'Chance, %',
      chanceHint:
        'The interval is per break. A fork rolls once for all its options: the picked variant rolls, and its chance is the chance of the whole fork. The roll comes from the generation seed, so rebuilds do not reshuffle breaks.',
      minInterval: 'No more often than once per, min',
      dayparts: 'Dayparts',
      daypartsHint: 'Nothing selected — the break plays in any daypart.',
      timeWindow: 'Channel time window',
      clearWindow: 'Clear',
      timeWindowHint: 'Empty — any time. The window may cross midnight.',
      nearHour: 'Near the round hour',
      nearHourUnit: 'min before and after :00',
      nearHourHint:
        '0 — no anchoring. Time signals and jingles work this way: the break only plays around :00, not somewhere within the hour.',
      badgeNearHour: '±{{minutes}} min off the hour',
      badgeOnChange: 'on change',
      badgeInterval: 'once per {{minutes}} min',
    },
    bumpers: {
      title: 'Bumpers',
      hint: 'Bumper blocks are shared across channels: look and sound belong to the block, text to its variants. A bumper reaches air as a junction break.',
      empty: 'No bumper blocks yet.',
      newName: 'Block name',
      createTitle: 'Create a bumper block',
      newNamePlaceholder: 'Block name',
      newVariantName: 'New text',
      sampleChannel: 'Preview as channel',
      sampleChannelNone: 'no channel',
      sampleChannelHint: 'The block is shared, but sample values come from the selected channel.',
      name: 'Name',
      font: 'Font',
      fontSans: 'Sans',
      fontSerif: 'Serif',
      colorBg: 'Background (colour 1)',
      colorBg2: 'Background (colour 2)',
      colorAccent: 'Accent',
      colorText: 'Text',
      saveStyle: 'Save look',
      seconds: 's',
      defaultDuration: '≈8 s (jingle)',
      variantsCount: 'texts: {{count}}',
      usedInJunctions: 'in breaks: {{count}}',
      cannotDeleteUsed: 'The block is used by junction breaks — drop the references first.',
      audio: 'Sound',
      audioHint: 'The sound length sets the bumper duration; without it a jingle is synthesised.',
      background: 'Background',
      backgroundFieldHint: 'Block background image; otherwise a palette gradient or a show poster.',
      backgroundPick: 'Pick from gallery',
      backgrounds: {
        Template: 'Block background',
        NextPoster: 'Next show poster',
        NowPoster: 'Previous show poster',
        Clip: 'Existing clip',
      },
      backgroundClip: 'Backing clip',
      pickClip: 'Pick a clip',
      backgroundClipHint:
        'The text is drawn over the video. The clip sets the bumper length; the block’s audio is trimmed to it, and without audio the clip’s own track plays.',
      fileLoaded: 'loaded',
      fileDefault: 'default',
      upload: 'Upload',
      reset: 'Reset',
      variants: 'Text variants',
      variantsHint:
        'Different text over the same music and look. Trigger and background are per variant.',
      addVariant: 'Add text',
      variantName: 'Name',
      trigger: 'Show on',
      triggers: {
        OnShowChange: 'Show change',
        BetweenEpisodes: 'Between episodes',
        Both: 'Both',
      },
      weight: 'Weight',
      lineStyles: { Label: 'Label', Title: 'Title', Caption: 'Caption' },
      lineColors: { Accent: 'Accent', Text: 'Text' },
      addLine: 'Line',
      presets: 'Presets',
      preset_nowNext: 'Now / Next',
      preset_nextAt: 'Next at …',
      preset_channel: 'Channel ident',
      presetNow: 'NOW',
      presetNext: 'NEXT',
      presetNextAt: 'NEXT AT',
      framePreview: 'Frame',
      framePreviewHint: 'Sample substitution: this is how the line looks on air.',
      unknownPlaceholder: 'Unknown placeholder: {{tokens}}',
      volatileHint: 'every airing is unique — the render cache stops working',
      render: 'Render samples',
      rendering: 'Rendering…',
      renderHint: 'A real ffmpeg render of every variant with sound — takes a few seconds.',
      tokens: {
        channel: 'Channel name',
        'channel.number': 'Channel number',
        'now.title': 'Current show',
        'next.title': 'Next show',
        'now.episode': 'Current episode',
        'next.episode': 'Next episode',
        'next.year': 'Next show year',
        'next.genre': 'Next show genre',
        'next.time': 'Next start time',
        'tonight.title': 'Tonight’s programme',
        'tonight.time': 'Tonight’s start time',
        'tomorrow.title': 'Tomorrow’s programme',
        'tomorrow.time': 'Tomorrow’s start time',
        time: 'Bumper airing time',
        date: 'Date',
        weekday: 'Weekday',
        slot: 'Slot title',
      },
    },
    storage: {
      title: 'Storage',
      volume: 'Storage volume',
      volumeUnknown:
        'The file system did not report the volume size — only the storage usage is shown.',
      totalVolume: 'Volume total',
      ours: 'Used by TeleWave',
      foreign: 'Other on the volume',
      foreignHint: 'database, system, foreign files',
      free: 'Free',
      lowSpace: 'Free space is below the threshold ({{threshold}}) — new uploads are rejected.',
      recount: 'Recount',
      computedAt: 'computed at {{time}}',
      breakdown: 'What takes the space',
      filesTotal: 'files: {{count}}',
      files: 'files: {{count}}',
      empty: 'The storage is empty.',
      areas: {
        Assets: 'Programme segments',
        BumperAssets: 'Bumper segments',
        Originals: 'Originals',
        Inbox: 'Inbox',
        ManualInbox: 'Manual inbox',
        Uploads: 'Unfinished uploads',
        BumperSources: 'Bumper sound and backgrounds',
        Images: 'Images',
        Other: 'Other under the root',
      },
      areaHints: {
        Assets: 'HLS segments of the library — usually almost everything',
        BumperAssets: 'rendered bumpers; cleaned up along with the schedule',
        Originals: 'kept only when Storage:KeepOriginals is on',
        Inbox: 'not picked up by the scanner yet',
        ManualInbox: 'taken into shows by hand',
        Uploads: 'staging directory; leftovers mean an aborted upload',
        BumperSources: 'raw block files, not the segments',
        Images: 'posters, stills, logos',
        Other: 'files outside the known directories — usually zero',
      },
    },
    maintenance: {
      title: 'Maintenance',
      warning: 'These actions are irreversible — data and files are deleted permanently.',
      clearMedia: 'Clear all media',
      clearMediaHint: 'Deletes all uploaded files and their segments, plus channel schedules.',
      confirmClearMedia: 'Permanently delete ALL media files?',
      clearShowMedia: 'Delete show media',
      clearShowMediaHint: "Deletes files of the selected show's episodes and clears its episodes.",
      confirmClearShowMedia: 'Permanently delete all media of the selected show?',
      pickShow: 'Pick a show',
      deleteShows: 'Delete all shows',
      deleteShowsHint: 'Deletes all shows and their episodes. Media files stay in the library.',
      confirmDeleteShows: 'Permanently delete ALL shows?',
      doneCount: 'Deleted: {{count}}',
    },
    settings: {
      title: 'Settings',
      registration: 'Registration',
      registrationHint:
        'When off, new users cannot sign up themselves — only an administrator can create accounts.',
      registrationLabel: 'Allow public registration',
      channelNumbers: 'Switch channels by number',
      channelNumbersHint:
        'Viewers switch channels with the arrow keys, like on a TV set. The channel grid stays available regardless.',
      preferredAudio: 'Preferred audio tracks',
      preferredAudioHint:
        'Comma-separated language codes in priority order (e.g. "rus, eng"). If a file has a track in one of these languages, it is picked during processing (by order); otherwise ffmpeg default. Applies to new processing.',
    },
    metadata: {
      title: 'Metadata',
      pickPoster: 'From gallery',
      name: 'Name',
      originalName: 'Original name (eng)',
      originalNamePlaceholder: 'e.g. Family Guy',
      originalNameHint: 'Metadata is looked up by this; screens still show the regular name.',
      sourceLabel: 'Source',
      searchBtn: 'Search',
      nothingFound: 'Nothing found',
      apply: 'Apply',
      applied: 'Metadata applied',
      overview: 'Overview',
      year: 'Year',
      clear: 'Clear',
      noPoster: 'No poster',
      refreshEpisodes: 'Refresh episodes',
      refreshing: 'Refreshing…',
      refreshedCount: 'Episodes updated: {{count}}',
      findMissing: 'Find missing episodes',
      missingTitle: 'Missing episodes',
      missingNoSeasons: 'No loaded episodes with recognized numbers.',
      seasonN: 'Season {{n}}',
      loadedOf: 'loaded {{loaded}} of {{total}}',
      missingUnknown: 'The source did not return the episode count for this season.',
      missingNone: 'All episodes present.',
      missingList: 'Missing',
      findMissingSeasons: 'Find missing seasons',
      missingSeasonsTitle: 'Missing seasons',
      missingSeasonsUnknownTotal: 'The source did not return the season list for this series.',
      missingSeasonsNone: 'All seasons present.',
      missingSeasonsList: 'Seasons with no loaded episodes',
      missingSeasonsUnknown: 'The source does not know these loaded seasons: {{list}}.',
    },
  },
}
