feat: also convert quotation marks between Chinese variants (#2578)

This commit is contained in:
Huang Xin
2025-12-01 16:27:22 +08:00
committed by GitHub
parent 02e885fbb7
commit 33ef781e21
5 changed files with 79 additions and 22 deletions
@@ -27,6 +27,7 @@ import { getPopupPosition, getPosition, Position, TextSelection } from '@/utils/
import { eventDispatcher } from '@/utils/event';
import { findTocItemBS } from '@/utils/toc';
import { throttle } from '@/utils/throttle';
import { runSimpleCC } from '@/utils/simplecc';
import { HIGHLIGHT_COLOR_HEX } from '@/services/constants';
import AnnotationPopup from './AnnotationPopup';
import WiktionaryPopup from './WiktionaryPopup';
@@ -485,7 +486,13 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const handleSearch = () => {
if (!selection || !selection.text) return;
handleDismissPopupAndSelection();
eventDispatcher.dispatch('search', { term: selection.text });
let term = selection.text;
const convertChineseVariant = viewSettings.convertChineseVariant;
if (convertChineseVariant && convertChineseVariant !== 'none') {
term = runSimpleCC(term, convertChineseVariant, true);
}
eventDispatcher.dispatch('search', { term });
};
const handleDictionary = () => {
@@ -1,40 +1,73 @@
import type { Transformer } from './types';
const punctuationMapHans: Record<string, string> = {
const verticalQuotationsMapHans: Record<string, string> = {
'“': '﹃',
'”': '﹄',
'': '﹁',
'': '﹂',
'「': '﹁',
'」': '﹂',
'『': '﹃',
'』': '﹄',
};
const punctuationMapHant: Record<string, string> = {
const verticalQuotationsMapHant: Record<string, string> = {
'“': '﹁',
'”': '﹂',
'': '﹃',
'': '﹄',
'「': '﹁',
'」': '﹂',
'『': '﹃',
'』': '﹄',
};
const quotationsMapHans2Hant = {
'“': '「',
'”': '」',
'': '『',
'': '』',
'﹃': '﹁',
'﹄': '﹂',
'﹁': '﹃',
'﹂': '﹄',
};
export const punctuationTransformer: Transformer = {
name: 'punctuation',
transform: async (ctx) => {
const shouldTransform = ctx.viewSettings.vertical && ctx.viewSettings.replaceQuotationMarks;
if (!shouldTransform) return ctx.content;
if (!ctx.viewSettings.replaceQuotationMarks) return ctx.content;
let result = ctx.content;
const traditionalChineseLocales = ['zh-Hant', 'zh-TW', 'zh_TW'];
let punctuationMap: Record<string, string> = punctuationMapHans;
if (
traditionalChineseLocales.includes(ctx.primaryLanguage || '') ||
traditionalChineseLocales.includes(ctx.userLocale)
) {
punctuationMap = punctuationMapHant;
const convertChineseVariant = ctx.viewSettings.convertChineseVariant;
const reversePunctuationTransform = ctx.reversePunctuationTransform || false;
const shouldConvertChineseVariants = convertChineseVariant && convertChineseVariant !== 'none';
if (shouldConvertChineseVariants) {
for (const [s, t] of Object.entries(quotationsMapHans2Hant)) {
const shouldReverse = reversePunctuationTransform !== convertChineseVariant.includes('2s');
const [from, to] = shouldReverse ? [t, s] : [s, t];
result = result.replace(new RegExp(from, 'g'), to);
}
}
for (const [original, vertical] of Object.entries(punctuationMap)) {
if (ctx.reversePunctuationTransform) {
result = result.replace(new RegExp(vertical, 'g'), original);
} else {
result = result.replace(new RegExp(original, 'g'), vertical);
const shouldTransformVertical = ctx.viewSettings.vertical;
if (shouldTransformVertical) {
const traditionalChineseLocales = ['zh-Hant', 'zh-TW', 'zh_TW'];
let punctuationMap: Record<string, string> = verticalQuotationsMapHans;
if (
traditionalChineseLocales.includes(ctx.primaryLanguage || '') ||
traditionalChineseLocales.includes(ctx.userLocale)
) {
punctuationMap = verticalQuotationsMapHant;
}
for (const [original, vertical] of Object.entries(punctuationMap)) {
if (ctx.reversePunctuationTransform) {
result = result.replace(new RegExp(vertical, 'g'), original);
} else {
result = result.replace(new RegExp(original, 'g'), vertical);
}
}
}
@@ -1,5 +1,5 @@
import type { Transformer } from './types';
import { simplecc, initSimpleCC } from '@/utils/simplecc';
import { initSimpleCC, runSimpleCC } from '@/utils/simplecc';
export const simpleccTransformer: Transformer = {
name: 'simplecc',
@@ -36,7 +36,7 @@ export const simpleccTransformer: Transformer = {
for (const textNode of textNodes) {
if (textNode.textContent) {
textNode.textContent = simplecc(textNode.textContent, convertChineseVariant);
textNode.textContent = runSimpleCC(textNode.textContent, convertChineseVariant);
}
}
+18 -1
View File
@@ -1,4 +1,5 @@
import init, { simplecc } from '@simplecc/simplecc_wasm';
import { ConvertChineseVariant } from '@/types/book';
let initialized = false;
@@ -9,4 +10,20 @@ const initSimpleCC = async () => {
initialized = true;
};
export { simplecc, initSimpleCC };
const convertReverseMap: Record<ConvertChineseVariant, ConvertChineseVariant> = {
none: 'none',
s2t: 't2s',
t2s: 's2t',
s2tw: 'tw2s',
s2hk: 'hk2s',
s2twp: 'tw2sp',
tw2s: 's2tw',
hk2s: 's2hk',
tw2sp: 's2twp',
};
const runSimpleCC = (text: string, variant: ConvertChineseVariant, reverse = false): string => {
return reverse ? simplecc(text, convertReverseMap[variant]) : simplecc(text, variant);
};
export { initSimpleCC, runSimpleCC };
+2 -2
View File
@@ -1,6 +1,6 @@
import { ConvertChineseVariant } from '@/types/book';
import { SectionItem, TOCItem, CFI, BookDoc } from '@/libs/document';
import { simplecc, initSimpleCC } from '@/utils/simplecc';
import { initSimpleCC, runSimpleCC } from '@/utils/simplecc';
export const findParentPath = (toc: TOCItem[], href: string): TOCItem[] => {
for (const item of toc) {
@@ -87,7 +87,7 @@ export const updateToc = async (
const convertTocLabels = (items: TOCItem[], convertChineseVariant: ConvertChineseVariant) => {
items.forEach((item) => {
if (item.label) {
item.label = simplecc(item.label, convertChineseVariant);
item.label = runSimpleCC(item.label, convertChineseVariant);
}
if (item.subitems) {
convertTocLabels(item.subitems, convertChineseVariant);