Translate Chaturbate tag pages

This commit is contained in:
2026-06-03 13:33:16 +08:00
parent 893fe47ace
commit bde00f27d3
2 changed files with 1156 additions and 10 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -184,6 +184,8 @@ assert.match(source, /setCardHeightVar/, "script should apply card height scalin
assert.match(source, /setDiscoverStackHeightVars/, "discover carousel stack heights should be derived from scaled card rows");
assert.match(source, /setCardHeightFromThumbVar\("--tm-thumb-discover-card-height"/, "discover card height should scale the thumbnail area and keep metadata height");
assert.match(source, /stackHeight == null\) return true/, "discover should not require every carousel row type to exist before enabling");
assert.match(source, /TAG_TRANSLATIONS/, "script should include a centralized tag translation table");
assert.match(source, /translateTagsPage/, "script should translate the Chaturbate tags page");
assert.doesNotMatch(source, /localStorage\.setItem/, "script should not write detected sizes to localStorage");
assert.match(source, /LEGACY_CACHE_PREFIX/, "script should only keep legacy cache cleanup support");
assert.match(source, /scheduleMeasure\("after-800ms"\)/, "script should delay the first page measurement until original layout can render");
@@ -238,6 +240,144 @@ assert.equal(earlyAppendedTarget, null, "GM_addStyle should avoid manual documen
assert.equal(typeof earlyDomContentLoadedHandler, "function", "script should reinject after head becomes available");
assert.equal(earlyObservedTarget, null, "script should not observe a missing head");
const tagAnchors = [
{
href: "https://zh-hans.chaturbate.com/tag/asian/",
textContent: "#asian",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
{
href: "https://zh-hans.chaturbate.com/tag/bigboobs/",
textContent: "#bigboobs",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
{
href: "https://zh-hans.chaturbate.com/tag/ebony/female/",
textContent: "#ebony",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
{
href: "https://zh-hans.chaturbate.com/tag/not-in-table/",
textContent: "Not In Table",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
{
href: "https://zh-hans.chaturbate.com/tag/new-custom-tag/",
textContent: "#new-custom-tag",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
{
href: "https://zh-hans.chaturbate.com/tag/colombiana/female/",
textContent: "#colombiana",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
{
href: "https://zh-hans.chaturbate.com/tag/untranslated-example/",
textContent: "#untranslated-example",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
{
href: "https://zh-hans.chaturbate.com/tags/female/?page=2",
textContent: "2",
dataset: {},
title: "",
getAttribute(name) {
return name === "href" ? this.href : "";
},
},
];
const tagFakeDocument = {
documentElement: {
setAttribute() {},
},
head: {
appendChild() {},
},
body: {},
createElement() {
return { id: "", textContent: "", setAttribute() {} };
},
getElementById() {
return null;
},
querySelectorAll(selector) {
return selector.includes("/tag") ? tagAnchors : [];
},
addEventListener() {},
};
const runTagsPageTranslationTest = (pathname = "/tags/") => vm.runInNewContext(source, {
console,
document: tagFakeDocument,
location: {
href: `https://zh-hans.chaturbate.com${pathname}`,
pathname,
},
URL,
GM_addStyle() {
return { id: "", textContent: "", setAttribute() {} };
},
GM_registerMenuCommand() {},
MutationObserver: class {
observe() {}
},
});
runTagsPageTranslationTest();
assert.equal(tagAnchors[0].textContent, "亚洲", "tags page should translate known tag link text");
assert.equal(tagAnchors[0].dataset.tmTagOriginal, "#asian", "translated tags should keep the original label in dataset");
assert.equal(tagAnchors[0].title, "#asian", "translated tags should expose the original label in title");
assert.equal(tagAnchors[1].textContent, "大胸", "tags page should translate compact multi-word tag slugs");
assert.equal(tagAnchors[2].textContent, "黑人", "gender-scoped tag links should translate by the first slug segment");
assert.equal(tagAnchors[3].textContent, "Not In Table", "unknown tags should remain unchanged");
assert.equal(tagAnchors[4].textContent, "新自定义标签", "untranslated hashtag-style tags should use slug word translation");
assert.equal(tagAnchors[5].textContent, "哥伦比亚女性", "known gendered nationality tags should be translated");
assert.equal(tagAnchors[6].textContent, "#untranslated-example", "untranslated hashtag-style tags should keep the original slug");
assert.equal(tagAnchors[7].textContent, "2", "tags pagination links should not be translated");
tagAnchors.forEach((anchor, index) => {
anchor.textContent = ["#asian", "#bigboobs", "#ebony", "Not In Table", "#new-custom-tag", "#colombiana", "#untranslated-example", "2"][index];
anchor.dataset = {};
anchor.title = "";
});
runTagsPageTranslationTest("/tags/female/");
assert.equal(tagAnchors[0].textContent, "亚洲", "tag category pages under /tags/ should also translate links");
assert.equal(tagAnchors[1].textContent, "大胸", "tag category pages should translate compact slugs too");
assert.equal(tagAnchors[2].textContent, "黑人", "tag category pages should translate /tag/slug/gender/ links");
assert.equal(tagAnchors[5].textContent, "哥伦比亚女性", "tag category pages should translate gendered nationality links");
assert.equal(tagAnchors[6].textContent, "#untranslated-example", "tag category pages should preserve unknown hashtag-style tags");
assert.equal(tagAnchors[7].textContent, "2", "tag category page pagination should keep page numbers");
const followingVars = new Map();
const followingAttrs = new Map();
const followingFakeDocument = {