## ADDED Requirements

### Requirement: Локализованное название кандидата TVDB

Кандидат TVDB SHALL нести локализованное название в поле `Title` и название на
языке оригинала в поле `OriginalTitle`. Язык локализации задаёт та же глобальная
настройка `language`, что и для TMDB, — правило единственного источника языка
живёт в требовании «Локаль запроса к TMDB» и здесь не переписывается.

Источник локализованного названия — блок переводов в ответе поиска TVDB (карта
«код языка → название»). `OriginalTitle` SHALL брать primary name записи — это
название на языке оригинала и ось сравнения при матче.

Фолбэк SHALL быть тотальным: если перевода на нужный язык нет, его значение
пусто после обрезки пробелов или блока переводов нет вовсе, `Title` SHALL быть
равен primary name. Пустого `Title` при непустом primary name быть SHALL NOT.
В `Title` SHALL попадать значение перевода после обрезки пробелов.

Ключ языка SHALL искаться регистронезависимо: молчаливый фолбэк из-за регистра
ключа неотличим от отсутствия перевода и в эксплуатации не диагностируется. Если
условию отвечает несколько ключей, выбор SHALL быть детерминированным — один и
тот же ответ провайдера обязан давать один и тот же `Title`.

Локаль TVDB SHALL влиять только на разбор ответа и SHALL NOT сужать выдачу
поиска: параметр языка в запрос поиска не передаётся. Причина — в
`docs/research/tvdb-search-translations.md`; здесь заказано поведение, а не
устройство чужого API. Тем самым разница с TMDB намеренна: у TMDB локаль едет в
запрос, у TVDB читается из ответа.

Негодная форма блока переводов (блок пришёл не картой, значения не строки) SHALL
приводить к тому же тотальному фолбэку, а не проваливать разбор ответа поиска
целиком: ответ метабазы — недоверенный вход.

Подозрение на иную форму ответа SHALL оставлять диагностический след. Признаком
служит **отсутствие во всей выдаче хотя бы одного ключа языка ожидаемого вида**,
а не неудача разбора блока: `null`, пустая карта и словарь кодов другого вида
разбираются без ошибки и потому признаком быть SHALL NOT. Штатное «перевода на
этот язык нет» (ключи ожидаемого вида есть, нужного среди них нет) следа
оставлять SHALL NOT — иначе сигнал неотличим от рутины. Уровень следа задают
конвенции логирования проекта и здесь не нормируются; требуется наблюдаемость,
а не конкретный уровень.

Настоящее требование не изменяет условий подтверждения матча — они заданы
требованиями «Подтверждение матча и каноническое имя» и «Безгодовой второй
проход сверки как fallback». Заполнение `OriginalTitle` расширяет множество
названий кандидата, по которым идёт сравнение, с одного до двух. Исход гейта при
этом монотонным SHALL NOT считаться: там, где сильный кандидат был один, их
может стать двое, и тогда подтверждённого матча нет, а записи уходят кандидатами
в review — по действующему требованию, без исключений для TVDB.

#### Scenario: Перевод на язык настройки есть

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `哪吒之魔童降世` и переводом `rus` = «Нэчжа»
- **THEN** `Candidate.Title` = «Нэчжа»
- **AND** `Candidate.OriginalTitle` = `哪吒之魔童降世`

#### Scenario: Перевода на язык настройки нет — фолбэк на оригинал

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `Fargo` и переводами без ключа `rus`
- **THEN** `Candidate.Title` = `Fargo`
- **AND** `Candidate.OriginalTitle` = `Fargo`

#### Scenario: Перевод есть, но пустой

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `Fargo` и переводом `rus` из одних пробелов
- **THEN** `Candidate.Title` = `Fargo`
- **AND** `Candidate.OriginalTitle` = `Fargo`

#### Scenario: Язык по умолчанию — английский

- **GIVEN** TVDB включён, глобальный `language` не задан в конфиге
- **WHEN** поиск возвращает запись с переводами `eng` и `rus`
- **THEN** `Candidate.Title` берётся из перевода `eng`

#### Scenario: Ключ перевода в другом регистре

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с переводом под ключом `RUS` вместо `rus`
- **THEN** `Candidate.Title` берётся из этого перевода

#### Scenario: Блок переводов отсутствует или пришёл негодной формой

- **GIVEN** TVDB включён с любым значением `language`
- **WHEN** поиск возвращает записи без блока переводов либо с блоком, который не разбирается картой
- **THEN** `Candidate.Title` = primary name записи
- **AND** `Candidate.OriginalTitle` = primary name записи
- **AND** разбор ответа поиска не проваливается, кандидаты возвращаются
- **AND** остаётся диагностический след о подозрении на иную форму ответа

#### Scenario: Коды языка не того вида — след остаётся

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** вся выдача поиска несёт блоки переводов с ключами другого вида (например, двухбуквенными), либо `null`, либо пустые
- **THEN** `Candidate.Title` = primary name у каждой записи
- **AND** остаётся диагностический след о подозрении на иную форму ответа

#### Scenario: Перевода на язык нет — следа не остаётся

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** выдача несёт блоки переводов с ключами ожидаемого вида, но без нужного языка
- **THEN** `Candidate.Title` = primary name у каждой записи
- **AND** диагностического следа не остаётся: это штатный исход, а не подозрение

#### Scenario: Запрос поиска не сужается языком

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** выполняется поиск
- **THEN** строка запроса поиска не содержит параметра языка
- **AND** строка запроса совпадает с той, что уходит при `language` = `en`

#### Scenario: Перевод сделал сильных кандидатов двумя — матч не подтверждён

- **GIVEN** план с `title` «Нэчжа» и `original_title` `Ne Zha`, год известен
- **WHEN** поиск TVDB возвращает две записи в пределах года ±1: одну с primary
  name `哪吒之魔童降世` и переводом `rus` «Нэчжа», другую с primary name `Ne Zha`
- **THEN** гейт даёт двух сильных кандидатов вместо одного
- **AND** подтверждённого матча нет, обе записи уходят кандидатами в review
