feat: support overriding html language code with language code in metadata, closes #2222 (#2227)

This commit is contained in:
Huang Xin
2025-10-14 19:09:23 +08:00
committed by GitHub
parent e2291ed5b1
commit 96cc182a8c
5 changed files with 35 additions and 6 deletions
@@ -43,6 +43,7 @@ import { getMaxInlineSize } from '@/utils/config';
import { getDirFromUILanguage } from '@/utils/rtl';
import { isCJKLang } from '@/utils/lang';
import { isTauriAppPlatform } from '@/services/environment';
import { TransformContext } from '@/services/transformers/types';
import { transformContent } from '@/services/transformService';
import { lockScreenOrientation } from '@/utils/bridge';
import { useTextTranslation } from '../hooks/useTextTranslation';
@@ -118,15 +119,17 @@ const FoliateViewer: React.FC<{
detail.data = Promise.resolve(detail.data)
.then((data) => {
const viewSettings = getViewSettings(bookKey);
const bookData = getBookData(bookKey);
if (viewSettings && detail.type === 'text/css')
return transformStylesheet(width, height, data);
if (viewSettings && detail.type === 'application/xhtml+xml') {
if (viewSettings && bookData && detail.type === 'application/xhtml+xml') {
const ctx = {
bookKey,
viewSettings,
primaryLanguage: bookData.book?.primaryLanguage,
content: data,
transformers: ['punctuation', 'footnote', 'language'],
};
transformers: ['punctuation', 'footnote', 'whitespace', 'language'],
} as TransformContext;
return Promise.resolve(transformContent(ctx));
}
return data;
@@ -2,10 +2,12 @@ import type { Transformer } from './types';
import { footnoteTransformer } from './footnote';
import { languageTransformer } from './language';
import { punctuationTransformer } from './punctuation';
import { whitespaceTransformer } from './whitespace';
export const availableTransformers: Transformer[] = [
punctuationTransformer,
footnoteTransformer,
languageTransformer,
whitespaceTransformer,
// Add more transformers here
];
@@ -1,10 +1,11 @@
import { detectLanguage, isValidLang } from '@/utils/lang';
import { detectLanguage, isSameLang, isValidLang } from '@/utils/lang';
import type { Transformer } from './types';
export const languageTransformer: Transformer = {
name: 'language',
transform: async (ctx) => {
const primaryLanguage = ctx.primaryLanguage;
let result = ctx.content;
const attrsMatch = result.match(/<html\b([^>]*)>/i);
if (attrsMatch) {
@@ -13,9 +14,10 @@ export const languageTransformer: Transformer = {
const xmlLangRegex = / xml:lang="([^"]*)"/i;
const xmlLangMatch = attrs.match(xmlLangRegex);
const langMatch = attrs.match(langRegex);
if (!isValidLang(langMatch?.[1] || xmlLangMatch?.[1])) {
const docLang = langMatch?.[1] || xmlLangMatch?.[1];
if (!isValidLang(docLang) || !isSameLang(docLang, primaryLanguage)) {
const mainContent = result.replace(/<[^>]+>/g, ' ');
const lang = detectLanguage(mainContent);
const lang = isValidLang(primaryLanguage) ? primaryLanguage : detectLanguage(mainContent);
const newLangAttr = ` lang="${lang}"`;
const newXmlLangAttr = ` xml:lang="${lang}"`;
attrs = langMatch ? attrs.replace(langRegex, newLangAttr) : attrs + newLangAttr;
@@ -3,6 +3,7 @@ import { ViewSettings } from '@/types/book';
export type TransformContext = {
bookKey: string;
viewSettings: ViewSettings;
primaryLanguage?: string;
content: string;
transformers: string[];
reversePunctuationTransform?: boolean;
@@ -0,0 +1,21 @@
import type { Transformer } from './types';
export const whitespaceTransformer: Transformer = {
name: 'whitespace',
transform: async (ctx) => {
const viewSettings = ctx.viewSettings;
if (viewSettings.overrideLayout) {
const cleaned = ctx.content
// Replace &nbsp; but skip literal "&amp;nbsp;"
.replace(/(?<!&amp;)&nbsp;/g, ' ')
// Replace literal non-breaking space characters (U+00A0) with normal spaces
.replace(/\u00A0/g, ' ')
// Collapse consecutive spaces into one
.replace(/ {2,}/g, ' ');
return cleaned;
} else {
return ctx.content;
}
},
};