diff --git a/package.json b/package.json
index 7d2a014a69..f3bd2d56d0 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
"bgutils-js": "3.2.0",
"butterchurn": "3.0.0-beta.5",
"butterchurn-presets": "3.0.0-beta.4",
+ "chinese-conv": "^4.0.0",
"color": "5.0.0",
"conf": "14.0.0",
"custom-electron-prompt": "1.5.8",
@@ -121,6 +122,7 @@
"node-html-parser": "7.0.1",
"node-id3": "0.2.9",
"peerjs": "1.5.5",
+ "pinyin-pro": "^3.27.0",
"semver": "7.7.2",
"serve": "14.2.5",
"socks": "2.8.7",
@@ -129,7 +131,6 @@
"solid-js": "1.9.9",
"solid-styled-components": "0.28.5",
"solid-transition-group": "0.3.0",
- "tiny-pinyin": "1.3.2",
"tinyld": "1.3.4",
"virtua": "0.42.3",
"vudio": "2.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 649e5b18bd..b0d26e1a9b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -111,6 +111,9 @@ importers:
butterchurn-presets:
specifier: 3.0.0-beta.4
version: 3.0.0-beta.4
+ chinese-conv:
+ specifier: ^4.0.0
+ version: 4.0.0
color:
specifier: 5.0.0
version: 5.0.0
@@ -210,6 +213,9 @@ importers:
peerjs:
specifier: 1.5.5
version: 1.5.5
+ pinyin-pro:
+ specifier: ^3.27.0
+ version: 3.27.0
semver:
specifier: 7.7.2
version: 7.7.2
@@ -234,9 +240,6 @@ importers:
solid-transition-group:
specifier: 0.3.0
version: 0.3.0(solid-js@1.9.9)
- tiny-pinyin:
- specifier: 1.3.2
- version: 1.3.2
tinyld:
specifier: 1.3.4
version: 1.3.4
@@ -1902,6 +1905,10 @@ packages:
resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+ chinese-conv@4.0.0:
+ resolution: {integrity: sha512-PVBMzvv6CtX1cubaDXfxYscIdbOAHPuY/E2vnfJIzOACX+xIW4NRKRlNsZVI2p5KxGsXyUp7tVHfvQlqZ4yx/w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@@ -3890,6 +3897,9 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
+ pinyin-pro@3.27.0:
+ resolution: {integrity: sha512-Osdgjwe7Rm17N2paDMM47yW+jUIUH3+0RGo8QP39ZTLpTaJVDK0T58hOLaMQJbcMmAebVuK2ePunTEVEx1clNQ==}
+
pixelmatch@5.3.0:
resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==}
hasBin: true
@@ -4472,9 +4482,6 @@ packages:
tiny-async-pool@1.3.0:
resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==}
- tiny-pinyin@1.3.2:
- resolution: {integrity: sha512-uHNGu4evFt/8eNLldazeAM1M8JrMc1jshhJJfVRARTN3yT8HEEibofeQ7QETWQ5ISBjd6fKtTVBCC/+mGS6FpA==}
-
tiny-typed-emitter@2.1.0:
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
@@ -6642,6 +6649,8 @@ snapshots:
chalk@5.0.1: {}
+ chinese-conv@4.0.0: {}
+
chownr@2.0.0: {}
chownr@3.0.0: {}
@@ -8852,6 +8861,8 @@ snapshots:
picomatch@4.0.3: {}
+ pinyin-pro@3.27.0: {}
+
pixelmatch@5.3.0:
dependencies:
pngjs: 6.0.0
@@ -9482,8 +9493,6 @@ snapshots:
dependencies:
semver: 5.7.2
- tiny-pinyin@1.3.2: {}
-
tiny-typed-emitter@2.1.0: {}
tinycolor2@1.6.0: {}
diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json
index 4cf482aa3b..3abf709f3a 100644
--- a/src/i18n/resources/en.json
+++ b/src/i18n/resources/en.json
@@ -862,6 +862,24 @@
"show-time-codes": {
"label": "Show time codes",
"tooltip": "Show the time codes next to the lyrics"
+ },
+ "convert-chinese-character": {
+ "label": "Convert Chinese character",
+ "submenu": {
+ "disabled": {
+ "label": "Disabled",
+ "tooltip": "Disable Chinese character conversion"
+ },
+ "simplified-to-traditional": {
+ "label": "Simplified to Traditional",
+ "tooltip": "Convert Simplified Chinese to Traditional Chinese"
+ },
+ "traditional-to-simplified": {
+ "label": "Traditional to Simplified",
+ "tooltip": "Convert Traditional Chinese to Simplified Chinese"
+ }
+ },
+ "tooltip": "Convert Chinese character to Traditional or Simplified"
}
},
"name": "Synced Lyrics",
diff --git a/src/menu.ts b/src/menu.ts
index 00f2b5f154..0d63a8e481 100644
--- a/src/menu.ts
+++ b/src/menu.ts
@@ -481,7 +481,12 @@ export const mainMenuTemplate = async (
availableLanguages
.map(
(lang): Electron.MenuItemConstructorOptions => ({
- label: `${langResources[lang].translation.language?.name ?? 'Unknown'} (${langResources[lang].translation.language?.['local-name'] ?? 'Unknown'})`,
+ label: `${
+ langResources[lang].translation.language?.name ?? 'Unknown'
+ } (${
+ langResources[lang].translation.language?.['local-name'] ??
+ 'Unknown'
+ })`,
type: 'checkbox',
checked: (config.get('options.language') ?? 'en') === lang,
click() {
diff --git a/src/plugins/synced-lyrics/menu.ts b/src/plugins/synced-lyrics/menu.ts
index d1b9e12f41..9f4fcc6d93 100644
--- a/src/plugins/synced-lyrics/menu.ts
+++ b/src/plugins/synced-lyrics/menu.ts
@@ -153,6 +153,62 @@ export const menu = async (
});
},
},
+ {
+ label: t('plugins.synced-lyrics.menu.convert-chinese-character.label'),
+ toolTip: t(
+ 'plugins.synced-lyrics.menu.convert-chinese-character.tooltip',
+ ),
+ type: 'submenu',
+ submenu: [
+ {
+ label: t(
+ 'plugins.synced-lyrics.menu.convert-chinese-character.submenu.disabled.label',
+ ),
+ toolTip: t(
+ 'plugins.synced-lyrics.menu.convert-chinese-character.submenu.disabled.tooltip',
+ ),
+ type: 'radio',
+ checked:
+ config.convertChineseCharacter === 'disabled' ||
+ config.convertChineseCharacter === undefined,
+ click() {
+ ctx.setConfig({
+ convertChineseCharacter: 'disabled',
+ });
+ },
+ },
+ {
+ label: t(
+ 'plugins.synced-lyrics.menu.convert-chinese-character.submenu.simplified-to-traditional.label',
+ ),
+ toolTip: t(
+ 'plugins.synced-lyrics.menu.convert-chinese-character.submenu.simplified-to-traditional.tooltip',
+ ),
+ type: 'radio',
+ checked: config.convertChineseCharacter === 'simplifiedToTraditional',
+ click() {
+ ctx.setConfig({
+ convertChineseCharacter: 'simplifiedToTraditional',
+ });
+ },
+ },
+ {
+ label: t(
+ 'plugins.synced-lyrics.menu.convert-chinese-character.submenu.traditional-to-simplified.label',
+ ),
+ toolTip: t(
+ 'plugins.synced-lyrics.menu.convert-chinese-character.submenu.traditional-to-simplified.tooltip',
+ ),
+ type: 'radio',
+ checked: config.convertChineseCharacter === 'traditionalToSimplified',
+ click() {
+ ctx.setConfig({
+ convertChineseCharacter: 'traditionalToSimplified',
+ });
+ },
+ },
+ ],
+ },
{
label: t('plugins.synced-lyrics.menu.show-time-codes.label'),
toolTip: t('plugins.synced-lyrics.menu.show-time-codes.tooltip'),
diff --git a/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx b/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx
index 94315949cd..ea83c6d35b 100644
--- a/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx
+++ b/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx
@@ -1,6 +1,11 @@
-import { createEffect, createSignal, Show } from 'solid-js';
+import { createEffect, createMemo, createSignal, Show } from 'solid-js';
-import { canonicalize, romanize, simplifyUnicode } from '../utils';
+import {
+ canonicalize,
+ convertChineseCharacter,
+ romanize,
+ simplifyUnicode,
+} from '../utils';
import { config } from '../renderer';
interface PlainLyricsProps {
@@ -9,11 +14,19 @@ interface PlainLyricsProps {
export const PlainLyrics = (props: PlainLyricsProps) => {
const [romanization, setRomanization] = createSignal('');
+ const text = createMemo(() => {
+ let line = props.line;
+ const convertChineseText = config()?.convertChineseCharacter;
+ if (convertChineseText && convertChineseText !== 'disabled') {
+ line = convertChineseCharacter(line, convertChineseText);
+ }
+ return line;
+ });
createEffect(() => {
if (!config()?.romanization) return;
- const input = canonicalize(props.line);
+ const input = canonicalize(text());
romanize(input).then((result) => {
setRomanization(canonicalize(result));
});
@@ -31,13 +44,13 @@ export const PlainLyrics = (props: PlainLyricsProps) => {
>
{
};
export const SyncedLine = (props: SyncedLineProps) => {
- const text = createMemo(() => props.line.text.trim());
+ const text = createMemo(() => {
+ let line = props.line.text;
+ const convertChineseText = config()?.convertChineseCharacter;
+ if (convertChineseText && convertChineseText !== 'disabled') {
+ line = convertChineseCharacter(line, convertChineseText);
+ }
+ return line;
+ });
const [romanization, setRomanization] = createSignal('');
createEffect(() => {
diff --git a/src/plugins/synced-lyrics/renderer/utils.tsx b/src/plugins/synced-lyrics/renderer/utils.tsx
index 1c6a410bd2..ab44ca7ea9 100644
--- a/src/plugins/synced-lyrics/renderer/utils.tsx
+++ b/src/plugins/synced-lyrics/renderer/utils.tsx
@@ -3,10 +3,11 @@ import KuromojiAnalyzer from 'kuroshiro-analyzer-kuromoji';
import Kuroshiro from 'kuroshiro';
import { romanize as esHangulRomanize } from 'es-hangul';
import hanja from 'hanja';
-import * as pinyin from 'tiny-pinyin';
+import { pinyin } from 'pinyin-pro';
import { romanize as romanizeThaiFrag } from '@dehoist/romanize-thai';
import { lazy } from 'lazy-var';
import { detect } from 'tinyld';
+import { sify, tify } from 'chinese-conv';
import { waitForElement } from '@/utils/wait-for-element';
import { LyricsRenderer, setIsVisible } from './renderer';
@@ -84,6 +85,22 @@ export const canonicalize = (text: string) => {
);
};
+export const convertChineseCharacter = (
+ text: string,
+ mode: 'simplifiedToTraditional' | 'traditionalToSimplified',
+) => {
+ if (!hasChinese([text])) return text;
+
+ switch (mode) {
+ case 'simplifiedToTraditional':
+ return tify(text);
+ case 'traditionalToSimplified':
+ return sify(text);
+ default:
+ return text;
+ }
+};
+
export const simplifyUnicode = (text?: string) =>
text
? text
@@ -165,9 +182,7 @@ export const romanizeHangul = (line: string) =>
esHangulRomanize(hanja.translate(line, 'SUBSTITUTION'));
export const romanizeChinese = (line: string) => {
- return line.replaceAll(/[\u4E00-\u9FFF]+/g, (match) =>
- pinyin.convertToPinyin(match, ' ', true),
- );
+ return line.replaceAll(/[\u4E00-\u9FFF]+/g, (match) => pinyin(match));
};
const thaiSegmenter = Intl.Segmenter.supportedLocalesOf('th').includes('th')
diff --git a/src/plugins/synced-lyrics/types.ts b/src/plugins/synced-lyrics/types.ts
index dab1edf9ba..4d824cb871 100644
--- a/src/plugins/synced-lyrics/types.ts
+++ b/src/plugins/synced-lyrics/types.ts
@@ -10,6 +10,10 @@ export type SyncedLyricsPluginConfig = {
showLyricsEvenIfInexact: boolean;
lineEffect: LineEffect;
romanization: boolean;
+ convertChineseCharacter:
+ | 'simplifiedToTraditional'
+ | 'traditionalToSimplified'
+ | 'disabled';
};
export type LineLyricsStatus = 'previous' | 'current' | 'upcoming';