Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
147 lines
8.0 KiB
TypeScript
147 lines
8.0 KiB
TypeScript
import { createMockTmdb, type MockTmdb, TMDB_FIXTURES } from "@umt/testkit";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
allowedLanguage,
|
|
allowedLanguageChain,
|
|
altTitleLanguage,
|
|
chineseScriptOf,
|
|
createTmdbClient,
|
|
fetchRichDetail,
|
|
textMatchesLanguage,
|
|
untaggedTextAllowed,
|
|
} from "./index.ts";
|
|
|
|
const JA = { originalLanguage: "ja", originCountry: ["JP"] };
|
|
const KO = { originalLanguage: "ko", originCountry: ["KR"] };
|
|
const TH = { originalLanguage: "th", originCountry: ["TH"] };
|
|
const ZH = { originalLanguage: "zh", originCountry: ["CN"] };
|
|
const EN = { originalLanguage: "en", originCountry: ["US"] };
|
|
|
|
describe("多语言保留规则:语言标签", () => {
|
|
it("简中 / 繁中 / 英文总是保留,规范化大小写", () => {
|
|
for (const tag of ["zh-CN", "zh-SG", "zh-Hans", "zh-TW", "zh-HK", "zh-MO", "zh-Hant", "zh", "en-US", "en-GB", "en"])
|
|
expect(allowedLanguage(tag, EN)).toBe(tag);
|
|
expect(allowedLanguage("zh-cn", EN)).toBe("zh-CN");
|
|
expect(allowedLanguage("zh_hant", EN)).toBe("zh-Hant");
|
|
expect(allowedLanguage("EN-au", EN)).toBe("en-AU");
|
|
});
|
|
|
|
it("其它语言只在是作品原语言时保留", () => {
|
|
for (const tag of ["ru-RU", "pt-BR", "vi-VN", "es-ES", "th-TH", "bg-BG", "he-IL", "fr-FR", "ja-JP", "ko-KR"])
|
|
expect(allowedLanguage(tag, EN)).toBeNull();
|
|
expect(allowedLanguage("ja-JP", JA)).toBe("ja-JP");
|
|
expect(allowedLanguage("ja", JA)).toBe("ja");
|
|
expect(allowedLanguage("ko-KR", JA)).toBeNull();
|
|
expect(allowedLanguage("ko-KR", KO)).toBe("ko-KR");
|
|
expect(allowedLanguage("th-TH", TH)).toBe("th-TH");
|
|
// 原语言是中文:中文的其它地区也算原语言;不额外增加别的语言
|
|
expect(allowedLanguage("zh-MY", ZH)).toBe("zh-MY");
|
|
expect(allowedLanguage("zh-MY", EN)).toBeNull();
|
|
expect(allowedLanguage("ja-JP", ZH)).toBeNull();
|
|
// 没有原语言信息:只有中英文
|
|
expect(allowedLanguage("ja-JP", {})).toBeNull();
|
|
expect(allowedLanguage(null, JA)).toBeNull();
|
|
expect(allowedLanguage("", JA)).toBeNull();
|
|
});
|
|
|
|
it("语言链按作品收敛:去掉不保留的语言,保持顺序", () => {
|
|
const chain = ["ja-JP", "zh-CN", "fr-FR", "zh-TW", "en-US"];
|
|
expect(allowedLanguageChain(chain, JA)).toEqual(chain.filter((x) => x !== "fr-FR"));
|
|
expect(allowedLanguageChain(chain, EN)).toEqual(["zh-CN", "zh-TW", "en-US"]);
|
|
expect(allowedLanguageChain(["fr-FR"], EN)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("多语言保留规则:文字校验", () => {
|
|
it("简繁特有字", () => {
|
|
expect(chineseScriptOf("人生切割术")).toBe("zh-Hans");
|
|
expect(chineseScriptOf("人生切割術")).toBe("zh-Hant");
|
|
expect(chineseScriptOf("沙丘")).toBeNull();
|
|
});
|
|
|
|
it("各语言的文字", () => {
|
|
expect(textMatchesLanguage("zh-CN", "奥本海默传")).toBe(true);
|
|
expect(textMatchesLanguage("zh-CN", "奧本海默傳")).toBe(false); // 明显繁体
|
|
expect(textMatchesLanguage("zh-TW", "最後生還者")).toBe(true);
|
|
expect(textMatchesLanguage("zh-TW", "最后的生还者")).toBe(false);
|
|
expect(textMatchesLanguage("zh", "最後生還者")).toBe(true); // 不带地区不看简繁
|
|
expect(textMatchesLanguage("zh-CN", "Severance CN")).toBe(false);
|
|
expect(textMatchesLanguage("zh-CN", "進撃の巨人")).toBe(false);
|
|
expect(textMatchesLanguage("ja-JP", "進撃の巨人")).toBe(true);
|
|
expect(textMatchesLanguage("ja-JP", "Shingeki no Kyojin")).toBe(false);
|
|
expect(textMatchesLanguage("ko-KR", "오징어 게임")).toBe(true);
|
|
expect(textMatchesLanguage("ko-KR", "Squid Game")).toBe(false);
|
|
expect(textMatchesLanguage("th-TH", "ฉลาดเกมส์โกง")).toBe(true);
|
|
expect(textMatchesLanguage("th-TH", "Bad Genius")).toBe(false);
|
|
expect(textMatchesLanguage("en-US", "Bad Genius")).toBe(true);
|
|
expect(textMatchesLanguage("en-US", "Очень странные дела")).toBe(false);
|
|
expect(textMatchesLanguage("ru-RU", "Очень странные дела")).toBe(true);
|
|
expect(textMatchesLanguage("ja-Latn", "Shingeki no Kyojin")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("多语言保留规则:alternative_titles 按地区推断语言", () => {
|
|
it("CN / SG → 简中,TW / HK / MO → 繁中;文字不符丢弃", () => {
|
|
expect(altTitleLanguage({ title: "人生切割术", region: "CN" }, EN)).toBe("zh-CN");
|
|
expect(altTitleLanguage({ title: "生活割離術", region: "HK" }, EN)).toBe("zh-HK");
|
|
expect(altTitleLanguage({ title: "人生切割術", region: "CN" }, EN)).toBeNull();
|
|
expect(altTitleLanguage({ title: "Severance CN", region: "CN" }, EN)).toBeNull();
|
|
expect(altTitleLanguage({ title: "奧本海默", region: "TW" }, EN)).toBe("zh-TW");
|
|
});
|
|
|
|
it("英语区 → 英文;其它地区只在是原产地区时 → 原语言", () => {
|
|
expect(altTitleLanguage({ title: "Dune 2", region: "US" }, EN)).toBe("en-US");
|
|
expect(altTitleLanguage({ title: "Attack on Titan", region: "GB" }, JA)).toBe("en-GB");
|
|
expect(altTitleLanguage({ title: "Дюна 2", region: "RU" }, EN)).toBeNull();
|
|
expect(altTitleLanguage({ title: "Duna: Parte Dois", region: "BR" }, EN)).toBeNull();
|
|
expect(altTitleLanguage({ title: "進撃の巨人", region: "JP" }, JA)).toBe("ja-JP");
|
|
expect(altTitleLanguage({ title: "進撃の巨人", region: "JP" }, EN)).toBeNull();
|
|
expect(altTitleLanguage({ title: "오징어 게임", region: "KR" }, KO)).toBe("ko-KR");
|
|
expect(altTitleLanguage({ title: "ฉลาดเกมส์โกง", region: "TH" }, TH)).toBe("th-TH");
|
|
expect(altTitleLanguage({ title: "Bad Genius", region: "TH" }, TH)).toBeNull();
|
|
expect(altTitleLanguage({ title: "流浪地球 2", region: "CN" }, ZH)).toBe("zh-CN");
|
|
expect(altTitleLanguage({ title: "The Wandering Earth 2", region: "US" }, ZH)).toBe("en-US");
|
|
expect(altTitleLanguage({ title: "无地区", region: null }, ZH)).toBeNull();
|
|
});
|
|
|
|
it("原产地区的罗马字别名记原语言拉丁转写", () => {
|
|
expect(altTitleLanguage({ title: "Shingeki no Kyojin", region: "JP", type: "Romaji" }, JA)).toBe("ja-Latn");
|
|
expect(altTitleLanguage({ title: "Shingeki no Kyojin", region: "JP", type: "" }, JA)).toBeNull();
|
|
expect(altTitleLanguage({ title: "Shingeki no Kyojin", region: "FR", type: "Romaji" }, JA)).toBeNull();
|
|
});
|
|
|
|
it("没有语言标记的旧别名按文字判断", () => {
|
|
expect(untaggedTextAllowed("人生切割术", EN)).toBe(true);
|
|
expect(untaggedTextAllowed("Severance", EN)).toBe(true);
|
|
expect(untaggedTextAllowed("Разделение", EN)).toBe(false);
|
|
expect(untaggedTextAllowed("세브란스", EN)).toBe(false);
|
|
expect(untaggedTextAllowed("세브란스", KO)).toBe(true);
|
|
expect(untaggedTextAllowed("ฉลาดเกมส์โกง", TH)).toBe(true);
|
|
expect(untaggedTextAllowed("2049", EN)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("多语言保留规则:TMDB 详情", () => {
|
|
let mock: MockTmdb | null = null;
|
|
afterEach(async () => {
|
|
await mock?.close();
|
|
mock = null;
|
|
});
|
|
|
|
it("details() 与富详情只留保留语言的译名与别名(奥本海默的 ja-JP 译名不要)", async () => {
|
|
mock = await createMockTmdb({ fixtures: TMDB_FIXTURES });
|
|
const tmdb = createTmdbClient({ baseUrl: `${mock.url}/3`, apiKey: "test-tmdb-key", retries: 0, timeoutMs: 1000 });
|
|
const d = await tmdb.details("movie", 872585);
|
|
expect(d.aliases).not.toContain("オッペンハイマー");
|
|
expect(d.aliases).toEqual(expect.arrayContaining(["奥本海默传", "奧本海默"]));
|
|
expect(d.titles.find((t) => t.text === "奥本海默传")).toMatchObject({ kind: "alias", lang: "zh-CN" });
|
|
const rich = await fetchRichDetail(tmdb, "movie", 872585, ["ja-JP", "zh-CN", "en-US"]);
|
|
expect(rich.translations.map((t) => t.lang).sort()).toEqual(["en-US", "zh-CN"]);
|
|
expect(rich.alternativeTitles.map((a) => a.language).sort()).toEqual(["zh-CN", "zh-TW"]);
|
|
// 请求语言 ja-JP 不在保留范围:语言链收敛,不用 ja-JP 的值兜底
|
|
expect(rich.languages).toEqual(["zh-CN", "en-US"]);
|
|
expect(rich.title).toBe("奥本海默");
|
|
expect(rich.titleLanguage).toBe("zh-CN");
|
|
});
|
|
});
|