Skip to content

Commit f862bbe

Browse files
authored
Merge pull request #724 from ranhengzhang/dev
使用「清理」替换「排序」逻辑,优先使用中文翻译
2 parents c7deba3 + e32663d commit f862bbe

1 file changed

Lines changed: 61 additions & 39 deletions

File tree

src/core/player/LyricManager.ts

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ class LyricManager {
433433
}
434434
if (isStale()) return;
435435
if (!ttmlContent || typeof ttmlContent !== "string") return;
436-
const sorted = this.sortTTMLTranslations(ttmlContent);
436+
const sorted = this.cleanTTMLTranslations(ttmlContent);
437437
const parsed = parseTTML(sorted);
438438
const lines = parsed?.lines || [];
439439
if (!lines.length) return;
@@ -517,7 +517,7 @@ class LyricManager {
517517
if (!lyric) return { lrcData: [], yrcData: [] };
518518
// TTML 直接返回
519519
if (format === "ttml") {
520-
const sorted = this.sortTTMLTranslations(lyric);
520+
const sorted = this.cleanTTMLTranslations(lyric);
521521
const ttml = parseTTML(sorted);
522522
const lines = ttml?.lines || [];
523523
statusStore.usingTTMLLyric = true;
@@ -551,52 +551,74 @@ class LyricManager {
551551
}
552552

553553
/**
554-
* 处理 TTML 内容并排序翻译
554+
* 清洗 TTML 中不需要的翻译
555555
* @param ttmlContent 原始 TTML 内容
556-
* @param translationOrder 翻译排序顺序
557-
* @returns 排序后的 TTML 内容
556+
* @returns 清洗后的 TTML 内容
558557
*/
559-
// 此函数应该在 AMLL 的 TTML 解析器支持多语言翻译后删除
560-
private sortTTMLTranslations(
558+
// 当支持 i18n 之后,需要对其中的部分函数进行修改,使其优选逻辑能够根据用户界面语言变化
559+
private cleanTTMLTranslations(
560+
// 一般没有多种音译,故不对音译部分进行清洗,如果需要请另写处理函数
561561
ttmlContent: string,
562-
translationOrder: string[] = ["zh-CN", "zh-Hans", "zh-TW", "zh-Hant"],
563562
): string {
564-
// 使用 DOMParser 解析 XML 内容
565-
const parser = new DOMParser();
566-
const xmlDoc = parser.parseFromString(ttmlContent, "text/xml");
567-
568-
// 查找所有歌词行元素
569-
const lyricsElements = xmlDoc.querySelectorAll("tt > body > div > p");
570-
571-
lyricsElements.forEach((element: Element) => {
572-
// 获取当前歌词行的所有翻译元素
573-
const translationElements = Array.from(element.children).filter(
574-
(child) =>
575-
child.hasAttribute("ttm:role") && child.getAttribute("ttm:role") === "x-translation",
576-
);
563+
const lang_counter = (ttml_text: string) => {
564+
// 使用正则匹配所有 xml:lang="xx-XX" 格式的字符串
565+
const langRegex = /(?<=<(span|translation)[^<>]+)xml:lang="([^"]+)"/g;
566+
const matches = ttml_text.matchAll(langRegex);
577567

578-
// 按照指定顺序对翻译进行排序
579-
// 按照指定顺序对翻译进行排序
580-
translationElements.sort((a, b) => {
581-
const aLang = (a.getAttribute("xml:lang") || a.getAttribute("lang") || "").toLowerCase();
582-
const bLang = (b.getAttribute("xml:lang") || b.getAttribute("lang") || "").toLowerCase();
568+
// 提取匹配结果并去重
569+
const langSet = new Set<string>();
570+
for (const match of matches) {
571+
if (match[2]) langSet.add(match[2]);
572+
}
583573

584-
const aIndex = translationOrder.findIndex((lang) => aLang.startsWith(lang.toLowerCase()));
585-
const bIndex = translationOrder.findIndex((lang) => bLang.startsWith(lang.toLowerCase()));
574+
return Array.from(langSet);
575+
};
586576

587-
// 如果找不到指定语言,则放在最后
588-
return (aIndex === -1 ? Infinity : aIndex) - (bIndex === -1 ? Infinity : bIndex);
589-
});
577+
const lang_filter = (langs: string[]): string | null => {
578+
if (langs.length <= 1) return null;
590579

591-
// 重新排列翻译元素
592-
translationElements.forEach((translationElement) => {
593-
element.appendChild(translationElement); // 移动到末尾以实现排序
594-
});
595-
});
580+
const lang_matcher = (target: string) => {
581+
return langs.find((lang) => {
582+
try {
583+
return new Intl.Locale(lang).maximize().script === target;
584+
} catch {
585+
return false;
586+
}
587+
});
588+
};
589+
590+
const hans_matched = lang_matcher("Hans");
591+
if (hans_matched) return hans_matched;
592+
593+
const hant_matched = lang_matcher("Hant");
594+
if (hant_matched) return hant_matched;
595+
596+
const major = langs.find((key) => key.startsWith("zh"));
597+
if (major) return major;
598+
599+
return langs[0];
600+
};
601+
602+
const ttml_cleaner = (ttml_text: string, major_lang: string | null): string => {
603+
// 如果没有指定主语言,直接返回原文本(或者根据需求返回空)
604+
if (major_lang === null) return ttml_text;
605+
606+
/**
607+
* 替换逻辑回调函数
608+
* @param match 完整匹配到的标签字符串 (例如 <code><span ...>...<\/span></code>)
609+
* @param lang 正则中第一个捕获组匹配到的语言代码 (例如 "ja-JP")
610+
*/
611+
const replacer = (match: string, lang: string) => (lang === major_lang ? match : "");
612+
const translationRegex = /<translation[^>]+xml:lang="([^"]+)"[^>]*>[\s\S]*?<\/translation>/g;
613+
const spanRegex = /<span[^>]+xml:lang="([^" ]+)"[^>]*>[\s\S]*?<\/span>/g;
614+
return ttml_text.replace(translationRegex, replacer).replace(spanRegex, replacer);
615+
};
616+
617+
const context_lang = lang_counter(ttmlContent);
618+
const major = lang_filter(context_lang);
619+
const cleaned_ttml = ttml_cleaner(ttmlContent, major);
596620

597-
// 序列化回字符串
598-
const serializer = new XMLSerializer();
599-
return serializer.serializeToString(xmlDoc);
621+
return cleaned_ttml;
600622
}
601623

602624
/**

0 commit comments

Comments
 (0)