Merge branch 'main' into copy/c6

This commit is contained in:
root committed 2026-10-07 02:05:16 +08:00
commit 8a9cbaff6a
146 files changed
+949 -577

No files matched your search

+3
View File
@@ -66,6 +66,8 @@ export interface TmdbRichDetail {
overview: string | null;
overviewLanguage: string | null;
tagline: string | null;
/** 标语实际取到的语言(链内语言标签;落空为 null) */
taglineLanguage: string | null;
year: number | null;
releaseDate: string | null;
lastAirDate: string | null;
@@ -275,6 +277,7 @@ export async function fetchRichDetail(
overview: overview?.value ?? null,
overviewLanguage: overview?.language ?? null,
tagline: tagline?.value ?? null,
taglineLanguage: tagline?.language ?? null,
year: yearOf(date),
releaseDate: date,
lastAirDate: s(j.last_air_date),
@@ -7,6 +7,7 @@ import {
chineseScriptOf,
createTmdbClient,
fetchRichDetail,
simplifiedChineseTagline,
textMatchesLanguage,
untaggedTextAllowed,
} from "./index.ts";
@@ -144,3 +145,52 @@ describe("多语言保留规则:TMDB 详情", () => {
expect(rich.titleLanguage).toBe("zh-CN");
});
});
describe("详情页标语只显示简体中文", () => {
it("取值语言是简中(zh-CN / zh-SG / zh)显示;繁中(zh-TW / zh-HK)和非中文不显示,不退到繁中", () => {
expect(simplifiedChineseTagline("人类的勇气就是人类的赞歌", "zh-CN", "en")).toBe("人类的勇气就是人类的赞歌");
expect(simplifiedChineseTagline("一场冒险", "zh-SG", "en")).toBe("一场冒险");
expect(simplifiedChineseTagline("一场冒险", "zh", "en")).toBe("一场冒险");
expect(simplifiedChineseTagline("一場冒險", "zh-TW", "en")).toBeNull();
expect(simplifiedChineseTagline("一场冒险", "zh-HK", "en")).toBeNull();
expect(simplifiedChineseTagline("Mankind was born on Earth.", "en-US", "en")).toBeNull();
});
it("语言未知(媒体库存的标语)按文字判断:繁体特有字、假名、谚文、没有汉字的都不显示", () => {
expect(simplifiedChineseTagline("我们注定要离开地球", null, "en")).toBe("我们注定要离开地球");
expect(simplifiedChineseTagline("我們註定要離開地球", null, "en")).toBeNull();
expect(simplifiedChineseTagline("沙丘", null, "en")).toBe("沙丘");
expect(simplifiedChineseTagline("君の名は。", null, "ja")).toBeNull();
expect(simplifiedChineseTagline("Mankind was born on Earth.", null, "en")).toBeNull();
expect(simplifiedChineseTagline(" ", null, "en")).toBeNull();
expect(simplifiedChineseTagline(null, "zh-CN", "en")).toBeNull();
});
it("原语言为中文(zh / 粤语 cn)的作品按简中处理", () => {
expect(simplifiedChineseTagline("我們註定要離開地球", null, "zh")).toBe("我們註定要離開地球");
expect(simplifiedChineseTagline("一場冒險", "zh-HK", "cn")).toBe("一場冒險");
});
it("富详情带回标语的取值语言:简中没有、回退到繁中时取值不变(存储照旧),展示层丢掉", async () => {
const client = {
getJson: async <T>() =>
({
id: 1,
name: "冒险",
original_name: "Adventure",
original_language: "en",
origin_country: ["US"],
tagline: "",
translations: {
translations: [
{ iso_639_1: "zh", iso_3166_1: "CN", data: { name: "冒险", overview: "简介", tagline: "" } },
{ iso_639_1: "zh", iso_3166_1: "TW", data: { name: "冒險", overview: "簡介", tagline: "一場冒險" } },
],
},
}) as T,
};
const rich = await fetchRichDetail(client, "tv", 1, ["zh-CN", "zh-TW", "en-US"]);
expect(rich).toMatchObject({ tagline: "一場冒險", taglineLanguage: "zh-TW" });
expect(simplifiedChineseTagline(rich.tagline, rich.taglineLanguage, rich.originalLanguage)).toBeNull();
});
});
@@ -147,6 +147,32 @@ export function chineseScriptOf(text: string): "zh-Hans" | "zh-Hant" | null {
return null;
}
/** 原语言是否中文(TMDB 用 zh,粤语作品是 cn) */
const isChineseOriginal = (lang: string | null | undefined) => {
const p = primaryOf(lang);
return p === "zh" || p === "cn";
};
/**
* 详情页头部展示的标语:只要简体中文,没有就不显示(不退到繁中或其它语言)。只决定展示,不改存储的取值。
* - language 是取值语言(TMDB 富详情的 taglineLanguage;媒体库存的标语语言多为 null,只能看文字);
* - 原语言为中文的作品按简中处理(TMDB 主记录就是中文原文);
* - 要求含汉字且不含假名 / 谚文;语言标为繁中、非中文,或文字明显是繁体特有字的,不显示。
*/
export function simplifiedChineseTagline(
text: string | null | undefined,
language: string | null | undefined,
originalLanguage: string | null | undefined,
): string | null {
const t = text?.trim();
if (!t || !HAN.test(t) || KANA.test(t) || HANGUL.test(t)) return null;
if (isChineseOriginal(originalLanguage)) return t;
if (language) {
if (primaryOf(language) !== "zh" || chineseVariant(language) === "zh-Hant") return null;
}
return chineseScriptOf(t) === "zh-Hant" ? null : t;
}
/**
* 标题文字与语言标签是否相符:zh 要求汉字且不含假名 / 谚文(带地区或 Hans / Hant 时再看简繁特有字,明显是另一种的不符);
* ja 要求假名或汉字;ko 要求谚文;泰 / 俄 / 阿 / 希伯来 / 印地等按各自文字;`-Latn` 与其余语言按拉丁字母且不含非拉丁强文字。
+2
View File
@@ -553,6 +553,8 @@ export const LibraryDetail = z.object({
.optional(),
/** SCR-1:最近一次刮削(写 NFO 与图片);没刮削过为 null */
scrape: LibraryScrapeState.nullable().optional(),
/** 头部展示的标语:只取简体中文(原语言为中文的按简中),没有为 null;存储的 metadata.tagline 不变 */
displayTagline: z.string().nullable().optional(),
});
export type LibraryDetail = z.infer<typeof LibraryDetail>;
+1
View File
@@ -426,6 +426,7 @@ export const TmdbMediaDetail = z.object({
// MD-1:按语言回退链取的详情字段(媒体详情页与媒体库详情共用展示)
/** 中文名:回退链中第一个中文值(zh-CN 优先);没有为 null */
titleZh: z.string().nullable().optional(),
/** 标语(展示用):只取简体中文(原语言为中文的按简中),没有为 null,不退到繁中 */
tagline: z.string().nullable().optional(),
genres: z.array(z.string()).optional(),
voteAverage: z.number().nullable().optional(),