feat(metadata): overview falls back to Chinese variants only (MD-2)

- adapters: overviewLanguages() limits overview/tagline chain to Chinese
  variants (zh-CN→zh-SG→zh-TW→zh-HK); titles keep full chain; season
  supplement fetch skips languages useless for overview
- setting metadata.overviewNonChineseFallback (default off) + UI switch
- media detail shows "暂无简介" for empty entity/episode overview
- scripts/overview-recompute.ts: recompute stored non-Chinese overviews
  from kv_cache only (no TMDB requests), dry-run by default, --apply
- tests: chain unit tests, Heart Signal (103147) fixture, server
  recompute test, MP differential for episode overview
- runbook + CHANGELOG

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 18:04:23 +08:00
1 parent 081db4651c
commit f9901b514b
22 files changed
+887 -38

No files matched your search

+23 -2
View File
@@ -147,7 +147,26 @@ export function pickChineseTitle(chain: readonly string[], src: TitleSources): P
return null;
}
/** 简介 / 标语:按链取 translations 的非空值;全部落空时用 primary(请求语言下的原值) */
/** 语言标签是否中文(zh / zh-CN / zh-TW …) */
export const isChineseTag = (tag: string): boolean => splitTag(tag).lang === "zh";
/**
* MD-2 简介 / 标语用的语言链:缺省只回退到中文变体,不回退到非中文(TMDB 上不少剧集没有中文简介,
* 英文"简介"常是标题英译,中文界面里显得不对)。规则:
* - allowNonChinese = true(设置 metadata.overviewNonChineseFallback):原链不变;
* - 链里没有中文:原链不变(用户本来就选了非中文);
* - 否则:第一个中文之前的语言(用户排在中文前面、是首选而不是回退)+ 链中的全部中文,之后的非中文去掉。
* 缺省链 zh-CN → zh-SG → zh-TW → zh-HK → en-US 得到 zh-CN → zh-SG → zh-TW → zh-HK。
* 标题 / 别名不受影响,仍按完整链回退。MP 对照:tmdbapi 每集信息只按配置语言取,简介为空就为空。
*/
export function overviewLanguages(chain: readonly string[], allowNonChinese = false): string[] {
if (allowNonChinese) return [...chain];
const firstZh = chain.findIndex(isChineseTag);
if (firstZh < 0) return [...chain];
return chain.filter((t, i) => i < firstZh || isChineseTag(t));
}
/** 简介 / 标语:按链取 translations 的非空值;全部落空时用 primary(请求语言下的原值,等于链外语言译文时不用) */
export function pickText(
chain: readonly string[],
translations: readonly TmdbTranslation[],
@@ -158,7 +177,9 @@ export function pickText(
const tr = translations.find((t) => tagMatches(tag, t) && t[key]);
if (tr?.[key]) return { value: tr[key]!, language: tag, via: "translation" };
}
if (primary?.value) return { value: primary.value, language: primary.language, via: "primary" };
// 请求语言下的原值若正是链外某语言的译文(TMDB / 反代把缺译名的字段回退成了别的语言),不当作链内的值
const foreign = translations.some((t) => t[key] === primary?.value && !chain.some((tag) => tagMatches(tag, t)));
if (primary?.value && !foreign) return { value: primary.value, language: primary.language, via: "primary" };
return null;
}