- новое поле [general].language (ru|en, дефолт en) — единый источник языка локализованного title детектора и локали запросов к метабазам; original_title всегда на языке оригинала - локаль TMDB (поиск + credits) выводится из него, [metadata.tmdb].language убран - промпт LLM явно задаёт язык title с fallback на оригинал
108 lines
9.1 KiB
Markdown
108 lines
9.1 KiB
Markdown
## Context
|
||
|
||
Язык локализованных полей вывода (`title`, режиссёр) сейчас задаётся неявно и в
|
||
двух местах: промпт LLM говорит модели вернуть «каноническое название» без
|
||
указания языка (модель решает сама), а клиент TMDB жёстко берёт локаль из
|
||
`[metadata.tmdb].language` с дефолтом `ru-RU`. Это два несогласованных рычага
|
||
для одного решения. Требуется один явный переключатель, единый для детектора и
|
||
метабаз.
|
||
|
||
Затрагиваемые пакеты: `internal/config` (новое поле), `internal/recognize`
|
||
(промпт), `internal/metadata` (TMDB-клиент), проброс из `cmd/jellybit` в
|
||
recognizer и TMDB. Конвенция конфига — `docs/conventions/config.md`
|
||
(самодокументируемый `config.example.toml`, валидация на старте).
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- Одно top-level поле `language` (`ru`|`en`, дефолт `en`) — единственный
|
||
источник языка локализованного вывода.
|
||
- `title` от LLM и локаль TMDB (поиск + credits) следуют этому полю.
|
||
- `original_title` не затрагивается ни при каком значении.
|
||
|
||
**Non-Goals:**
|
||
|
||
- Гарантированная локализация имён режиссёров (провайдеры их почти не
|
||
переводят — best-effort).
|
||
- Языки помимо `ru`/`en`, автоопределение языка по контенту, per-раздача
|
||
override.
|
||
- Локаль TVDB/TVMaze: TVDB extended не параметризуем языком в текущем клиенте,
|
||
TVMaze — англоязычный; вне охвата этого изменения (охват — LLM и TMDB).
|
||
|
||
## Decisions
|
||
|
||
**1. Место конфига — `[general].language`, значения `ru`|`en`, дефолт `en`.**
|
||
Поле кросс-каттинг (и recognition, и metadata-match) и по классу — это
|
||
presentation-настройка отображения, ровно как уже живущий в `[general]`
|
||
`timezone` («таймзона отображения времени»). Селим рядом с ним — единый дом
|
||
таких настроек, не плодим второй паттерн места. Значения — короткие языковые
|
||
коды, а не локали (`ru-RU`): локаль — диалект конкретного провайдера, его
|
||
выводит сам провайдер (см. решение 2). Значение хранится голой строкой, как
|
||
остальные enum-подобные поля конфига (`llm.type`, `log.format`) — проект
|
||
осознанно не типизирует их. Валидация: значение вне `ru`/`en` → ошибка старта
|
||
(fail-fast, **по образцу `llm.type`**, а не permissive `log.level`), форма
|
||
сообщения — `unsupported language %q (supported: ru, en)`. Нормализацию «пусто →
|
||
`en`» держим НЕ в `validate()` (он чист — только собирает ошибки), а в аксессоре
|
||
`Config` по образцу `DisplayLocation()`; `Default()` при этом сеет `"en"`.
|
||
Альтернативы отклонены: `[recognition].language` назвала бы поле по одному из
|
||
двух потребителей; top-level рядом с `timezone` не согласуется с уже сложившимся
|
||
домом presentation-настроек.
|
||
|
||
**2. `[metadata.tmdb].language` удаляется; локаль выводит провайдер.** В общий
|
||
слой (config/cmd) едет абстрактный `ru`/`en`; диалект TMDB `ru-RU`/`en-US`
|
||
выводит сам `internal/metadata/tmdb.go` — тотальным `switch` с default-веткой
|
||
`en-US` (defense in depth: невозможный вход не роняет запрос без локали). Так
|
||
знание диалекта принадлежит тому, кто на нём говорит: второй локализуемый
|
||
провайдер (TVDB с иным синтаксисом локали) добавит свой маппинг у себя, а не
|
||
расширит общий слой. Остаточный `tmdbDefaultLanguage = "ru-RU"` и его fallback в
|
||
`NewTMDB` **удаляются** — это скрытый второй дефолт, противоречащий заявленному
|
||
`en-US`. Второго способа задать язык не остаётся (нет рассинхрона). Ломающее
|
||
изменение конфига переворачивает дефолт локали TMDB `ru-RU`→`en-US`; фиксируем в
|
||
proposal и миграции. Альтернатива «оставить `[metadata.tmdb].language` как
|
||
override» отклонена: две ручки для одного решения противоречат «второго способа
|
||
быть не должно».
|
||
|
||
**3. Директива языка в промпте — только для `title`, с fallback на оригинал.**
|
||
Добавляем в промпт распознавания явную строку: вернуть `title` на языке
|
||
`language`, при отсутствии перевода — на языке оригинала (не выдумывать).
|
||
Семантически совпадает с поведением TMDB `language` (локализованное поле с
|
||
fallback на оригинал), поэтому обе стороны согласованы. Язык прокидывается в
|
||
`buildMessages`/`systemPrompt` из конфига через `Recognizer`.
|
||
|
||
**4. Режиссёр — та же локаль в запрос credits, best-effort.** Клиент TMDB уже
|
||
передаёт `language` в поиск; тем же значением параметризуем запрос credits
|
||
(`Director`). Имена людей TMDB локализует не всегда — где перевода нет, приходит
|
||
оригинал; это не ошибка и не проваливает выборку (best-effort уже закреплён
|
||
спекой metadata-match).
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- [Имена режиссёров редко локализованы у провайдеров] → best-effort по спеке:
|
||
при отсутствии перевода — оригинал; ожидание зафиксировано в proposal, отказа
|
||
не вызывает.
|
||
- [Ломающее изменение конфига: удаление `[metadata.tmdb].language` + флип
|
||
дефолта на `en-US`] → go-toml молча игнорирует неизвестный ключ, поэтому
|
||
деплой с оставленным `[metadata.tmdb].language = "ru-RU"` тихо переключится на
|
||
английский. Решение: НЕ вводим отклонение неизвестных ключей (лишняя машинерия
|
||
ради единственного деплоя), а прописываем обязательный ручной шаг в миграции
|
||
ниже. Существующие «русские» деплои выставляют `[general].language = "ru"` и
|
||
убирают старый ключ.
|
||
- [Дефолт `en` переименует имена папок медиатеки на английский] → косметика:
|
||
Jellyfin идентифицирует контент по id-тегу метабазы (TVDB/IMDb в имени папки),
|
||
а не по языку названия, поэтому распознавание не ломается. `original_title`
|
||
(ось поиска в базах) от `language` не зависит. Осознанный дефолт.
|
||
- [LLM может проигнорировать языковую директиву на слабой модели] → директива
|
||
best-effort, как и весь недоверенный вывод LLM; безопасность по-прежнему на
|
||
валидации и гейте матча, не на языке `title`.
|
||
|
||
## Migration Plan
|
||
|
||
1. Выкатка бинаря с новым полем; дефолт `en` активен сразу.
|
||
2. Деплой, где ожидались русские названия, добавляет `[general].language = "ru"`
|
||
в конфиг и удаляет `[metadata.tmdb].language` (обязательный ручной шаг —
|
||
валидатор про удалённый ключ не предупреждает).
|
||
3. Откат — вернуть прежний бинарь; `language` в конфиге игнорируется старой
|
||
версией (unknown-поле go-toml не роняет парсинг), `[metadata.tmdb].language`
|
||
при откате нужно вернуть, если он был.
|