Compare commits

...

6 Commits

Author SHA1 Message Date
5dd8a5fe05 fix(deps): update dependency semver to v7.7.4 2026-02-12 16:29:53 +00:00
208e57bdd3 feat(synced-lyrics): Add Simplified/Traditional Chinese converter and improve Romanization to display tone (#4111)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Angelos Bouklis <me@arjix.dev>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2026-02-13 01:27:48 +09:00
e2f1ff50dd feat(synced-lyrics): Added Hindi & Bengali romanization support for lyrics (#3933)
Co-authored-by: pranjol <pranjol@pranjols-iMac-Pro.local>
2026-02-11 08:00:36 +09:00
3f1ceca39c chore(i18n): Translated using Weblate (Telugu)
Currently translated at 2.7% (13 of 469 strings)

Translation: pear-devs/pear-desktop/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/te/
2026-02-10 10:09:44 +00:00
37277b2764 chore(i18n): Added translation using Weblate (Telugu) 2026-02-09 11:03:37 +01:00
ba89772b09 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (469 of 469 strings)

Translation: pear-devs/pear-desktop/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2026-02-07 19:09:44 +00:00
10 changed files with 274 additions and 55 deletions

View File

@ -77,6 +77,7 @@
"@hono/swagger-ui": "0.5.3",
"@hono/zod-openapi": "1.2.1",
"@hono/zod-validator": "0.7.6",
"@indic-transliteration/sanscript": "1.3.3",
"@jellybrick/dbus-next": "0.10.3",
"@jellybrick/electron-better-web-request": "2.0.0",
"@jellybrick/mpris-service": "2.1.5",
@ -88,6 +89,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.3",
"conf": "15.0.2",
"custom-electron-prompt": "1.6.1",
@ -120,7 +122,8 @@
"node-html-parser": "7.0.2",
"node-id3": "0.2.9",
"peerjs": "1.5.5",
"semver": "7.7.3",
"pinyin-pro": "^3.27.0",
"semver": "7.7.4",
"serve": "14.2.5",
"socks": "2.8.7",
"solid-element": "1.9.1",
@ -128,7 +131,6 @@
"solid-js": "1.9.11",
"solid-styled-components": "0.28.5",
"solid-transition-group": "0.3.0",
"tiny-pinyin": "1.3.2",
"tinyld": "1.3.4",
"virtua": "0.48.5",
"vudio": "2.1.1",

99
pnpm-lock.yaml generated
View File

@ -78,6 +78,9 @@ importers:
'@hono/zod-validator':
specifier: 0.7.6
version: 0.7.6(hono@4.11.7)(zod@4.3.6)
'@indic-transliteration/sanscript':
specifier: 1.3.3
version: 1.3.3
'@jellybrick/dbus-next':
specifier: 0.10.3
version: 0.10.3
@ -111,6 +114,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.3
version: 5.0.3
@ -207,9 +213,12 @@ importers:
peerjs:
specifier: 1.5.5
version: 1.5.5
pinyin-pro:
specifier: ^3.27.0
version: 3.27.0
semver:
specifier: 7.7.3
version: 7.7.3
specifier: 7.7.4
version: 7.7.4
serve:
specifier: 14.2.5
version: 14.2.5
@ -231,9 +240,6 @@ importers:
solid-transition-group:
specifier: 0.3.0
version: 0.3.0(solid-js@1.9.11)
tiny-pinyin:
specifier: 1.3.2
version: 1.3.2
tinyld:
specifier: 1.3.4
version: 1.3.4
@ -843,6 +849,12 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@indic-transliteration/common_maps@1.0.5':
resolution: {integrity: sha512-XbWDA5AXGE+Nh4uGr/yN9ZM8avRBy4F1KQL+DLgQGOdsQ390lcW4fga0NSjg4C/rOpMd0rHZv2YFV3Bq3UbpkQ==}
'@indic-transliteration/sanscript@1.3.3':
resolution: {integrity: sha512-zNGeARmQTPIlubwgEhl/JumpwTPHrdT/cNsQeCL+G67SQmjJe3qRnMIYghXiVt7+KDso/pU1Ky2ZfD/RBISfJQ==}
'@isaacs/balanced-match@4.0.1':
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
engines: {node: 20 || >=22}
@ -1297,9 +1309,6 @@ packages:
'@types/node@24.10.9':
resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==}
'@types/node@25.1.0':
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
'@types/node@25.2.0':
resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==}
@ -1821,6 +1830,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@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
@ -2692,16 +2705,16 @@ packages:
glob@11.1.0:
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
engines: {node: 20 || >=22}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@13.0.0:
resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
engines: {node: 20 || >=22}
hasBin: true
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
@ -3731,6 +3744,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
@ -4020,8 +4036,8 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
semver@7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
@ -4299,9 +4315,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==}
@ -4338,6 +4351,9 @@ packages:
resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==}
engines: {node: '>=10'}
toml@2.3.6:
resolution: {integrity: sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@ -4938,7 +4954,7 @@ snapshots:
node-gyp: 12.2.0
ora: 5.4.1
read-binary-file-arch: 1.0.6
semver: 7.7.3
semver: 7.7.4
tar: 7.5.7
yargs: 17.7.2
transitivePeerDependencies:
@ -5206,6 +5222,13 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@indic-transliteration/common_maps@1.0.5': {}
'@indic-transliteration/sanscript@1.3.3':
dependencies:
'@indic-transliteration/common_maps': 1.0.5
toml: 2.3.6
'@isaacs/balanced-match@4.0.1': {}
'@isaacs/brace-expansion@5.0.0':
@ -5539,7 +5562,7 @@ snapshots:
'@npmcli/fs@5.0.0':
dependencies:
semver: 7.7.3
semver: 7.7.4
'@oxc-project/runtime@0.101.0': {}
@ -5697,7 +5720,7 @@ snapshots:
dependencies:
'@types/http-cache-semantics': 4.2.0
'@types/keyv': 3.1.4
'@types/node': 25.1.0
'@types/node': 25.2.0
'@types/responselike': 1.0.3
'@types/debug@4.1.12':
@ -5728,7 +5751,7 @@ snapshots:
'@types/keyv@3.1.4':
dependencies:
'@types/node': 25.1.0
'@types/node': 25.2.0
'@types/ms@2.1.0': {}
@ -5738,10 +5761,6 @@ snapshots:
dependencies:
undici-types: 7.16.0
'@types/node@25.1.0':
dependencies:
undici-types: 7.16.0
'@types/node@25.2.0':
dependencies:
undici-types: 7.16.0
@ -5754,7 +5773,7 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
'@types/node': 25.1.0
'@types/node': 25.2.0
'@types/semver@7.7.1': {}
@ -5771,7 +5790,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 25.1.0
'@types/node': 25.2.0
optional: true
'@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
@ -5842,7 +5861,7 @@ snapshots:
'@typescript-eslint/visitor-keys': 8.54.0
debug: 4.4.3
minimatch: 9.0.5
semver: 7.7.3
semver: 7.7.4
tinyglobby: 0.2.15
ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3
@ -6038,7 +6057,7 @@ snapshots:
plist: 3.1.0
proper-lockfile: 4.1.2
resedit: 1.7.2
semver: 7.7.3
semver: 7.7.4
tar: 7.5.7
temp-file: 3.4.0
tiny-async-pool: 1.3.0
@ -6340,6 +6359,8 @@ snapshots:
chalk@5.0.1: {}
chinese-conv@4.0.0: {}
chownr@3.0.0: {}
chromium-pickle-js@0.2.0: {}
@ -6451,7 +6472,7 @@ snapshots:
dot-prop: 10.1.0
env-paths: 3.0.0
json-schema-typed: 8.0.2
semver: 7.7.3
semver: 7.7.4
uint8array-extras: 1.5.0
content-disposition@0.5.2: {}
@ -6793,7 +6814,7 @@ snapshots:
lazy-val: 1.0.5
lodash.escaperegexp: 4.1.2
lodash.isequal: 4.5.0
semver: 7.7.3
semver: 7.7.4
tiny-typed-emitter: 2.1.0
transitivePeerDependencies:
- supports-color
@ -7448,7 +7469,7 @@ snapshots:
es6-error: 4.1.1
matcher: 3.0.0
roarr: 2.15.4
semver: 7.7.3
semver: 7.7.4
serialize-error: 7.0.1
optional: true
@ -7666,7 +7687,7 @@ snapshots:
is-bun-module@2.0.0:
dependencies:
semver: 7.7.3
semver: 7.7.4
is-callable@1.2.7: {}
@ -8198,14 +8219,14 @@ snapshots:
node-abi@4.26.0:
dependencies:
semver: 7.7.3
semver: 7.7.4
node-addon-api@1.7.2:
optional: true
node-api-version@0.2.1:
dependencies:
semver: 7.7.3
semver: 7.7.4
node-domexception@1.0.0: {}
@ -8225,7 +8246,7 @@ snapshots:
make-fetch-happen: 15.0.3
nopt: 9.0.0
proc-log: 6.1.0
semver: 7.7.3
semver: 7.7.4
tar: 7.5.7
tinyglobby: 0.2.15
which: 6.0.0
@ -8439,6 +8460,8 @@ snapshots:
picomatch@4.0.3: {}
pinyin-pro@3.27.0: {}
pixelmatch@5.3.0:
dependencies:
pngjs: 6.0.0
@ -8721,7 +8744,7 @@ snapshots:
semver@6.3.1: {}
semver@7.7.3: {}
semver@7.7.4: {}
serialize-error@11.0.3:
dependencies:
@ -8828,7 +8851,7 @@ snapshots:
simple-update-notifier@2.0.0:
dependencies:
semver: 7.7.3
semver: 7.7.4
simple-xml-to-json@1.2.3: {}
@ -9051,8 +9074,6 @@ snapshots:
dependencies:
semver: 5.7.2
tiny-pinyin@1.3.2: {}
tiny-typed-emitter@2.1.0: {}
tinycolor2@1.6.0: {}
@ -9085,6 +9106,8 @@ snapshots:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
toml@2.3.6: {}
totalist@3.0.1: {}
truncate-utf8-bytes@1.0.2:

View File

@ -891,6 +891,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",

View File

@ -0,0 +1,31 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "{{pluginName}}::{{contextName}} ప్లగిన్‌ను అమలు చేయడంలో విఫలమైంది",
"executed-at-ms": "{{pluginName}}::{{contextName}} ప్లగిన్ అమలు చేయబడిన సమయం {{ms}} మిల్లీసెకను",
"initialize-failed": "\"{{pluginName}}\" ప్లగిన్‌ను ప్రారంభించడంలో విఫలమైంది",
"load-all": "అన్నీ ప్లగిన్లను లోడ్ చెయ్యబడుతోంది",
"load-failed": "\"{{pluginName}}\" ప్లగిన్ లోడ్ చేయడంలో విఫలమైంది",
"loaded": "ప్లగిన్ \"{{pluginName}}\" లోడ్ చేయబడింది",
"unload-failed": "“{{pluginName}}” ప్లగిన్‌ను అన్‌లోడ్ చేయడంలో విఫలమైంది",
"unloaded": "\"{{pluginName}}\" ప్లగిన్ అన్‌లోడ్ చేయబడింది"
}
}
},
"language": {
"code": "te",
"local-name": "తెలుగు",
"name": "Telugu"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "లోడ్ చేయడం పూర్తయింది. డెవ్‌టూల్స్ తెరవబడ్డాయి"
},
"i18n": {
"loaded": "i18n లోడ్ చేయబడింది"
}
}
}
}

View File

@ -53,9 +53,9 @@
"later": "Daha Sonra",
"restart-now": "Şimdi yeniden başlat"
},
"detail": "\"{{pluginName}}\" eklentisinin çalışabilmesi için yeniden başlatman gerekiyor",
"message": "\"{{pluginName}}\" için yeniden başlatman gerekiyor",
"title": "Uygulamayı yeniden başlatman gerekiyor"
"detail": "\"{{pluginName}}\" eklentisinin çalışması için uygulamanın yeniden başlatılması gerekiyor",
"message": "\"{{pluginName}}\" eklentisi için uygulamanın yeniden başlatılması gerekiyor",
"title": "Uygulamanın yeniden başlatılması gerekiyor"
},
"unresponsive": {
"buttons": {
@ -161,7 +161,8 @@
"default": "Varsayılan",
"force-show": "Zorla göster",
"hide": "Gizle",
"label": "Beğenme düğmeleri"
"label": "Beğenme düğmeleri",
"swap": "Beğenme butonlarının sıralamasını değiştir"
},
"remove-upgrade-button": "Yükseltme düğmesini kaldır",
"theme": {
@ -412,6 +413,17 @@
"no-captions": "Bu şarkı için altyazı mevcut değil"
}
},
"clock": {
"description": "Navigasyon barına bir saat ekle",
"menu": {
"format": {
"24-hour-format": "24-Saat Formatı, Biçimi",
"display-seconds": "Saniyeleri göster",
"label": "Format, Biçim"
}
},
"name": "Saat"
},
"compact-sidebar": {
"description": "Her zaman kompakt kenar çubugu",
"name": "Kompakt Kenar Çubuğu"

View File

@ -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'),

View File

@ -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) => {
>
<yt-formatted-string
text={{
runs: [{ text: props.line }],
runs: [{ text: text() }],
}}
/>
<Show
when={
config()?.romanization &&
simplifyUnicode(props.line) !== simplifyUnicode(romanization())
simplifyUnicode(text()) !== simplifyUnicode(romanization())
}
>
<yt-formatted-string

View File

@ -7,7 +7,12 @@ import { type LineLyrics } from '@/plugins/synced-lyrics/types';
import { config, currentTime } from '../renderer';
import { _ytAPI } from '..';
import { canonicalize, romanize, simplifyUnicode } from '../utils';
import {
canonicalize,
convertChineseCharacter,
romanize,
simplifyUnicode,
} from '../utils';
interface SyncedLineProps {
scroller: VirtualizerHandle;
@ -81,7 +86,14 @@ const EmptyLine = (props: SyncedLineProps) => {
};
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.trim();
});
const [romanization, setRomanization] = createSignal('');
createEffect(() => {

View File

@ -3,10 +3,12 @@ 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 Sanscript from '@indic-transliteration/sanscript';
import { waitForElement } from '@/utils/wait-for-element';
import { LyricsRenderer, setIsVisible } from './renderer';
@ -84,6 +86,20 @@ 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);
}
};
export const simplifyUnicode = (text?: string) =>
text
? text
@ -155,6 +171,12 @@ const hasChinese = (lines: string[]) =>
const hasThai = (lines: string[]) =>
lines.some((line) => /[\u0E00-\u0E7F]+/.test(line));
const hasBengali = (lines: string[]) =>
lines.some((line) => /[\u0980-\u09FF]+/.test(line));
const hasHindi = (lines: string[]) =>
lines.some((line) => /[\u0900-\u097F]+/.test(line));
export const romanizeJapanese = async (line: string) =>
(await kuroshiro.get()).convert(line, {
to: 'romaji',
@ -165,9 +187,9 @@ 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) => {
return pinyin(match, { separator: ' ' });
});
};
const thaiSegmenter = Intl.Segmenter.supportedLocalesOf('th').includes('th')
@ -190,11 +212,35 @@ export const romanizeThai = (line: string) => {
return latin;
};
export const romanizeBengali = (line: string) => {
try {
let out = Sanscript.t(line, 'bengali', 'iast');
out = out.normalize('NFD');
out = out.replace(/[\u0300-\u036f]/g, '');
out = out.replace(/[\u09BC\u09BE-\u09CD]/g, '');
return out.toLowerCase();
} catch {
return line;
}
};
export const romanizeHindi = (line: string) => {
try {
let out = Sanscript.t(line, 'devanagari', 'iast');
out = out.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); // strip accents
return out.replace(/[^a-zA-Z\s]/g, '') || line; // remove any remaining symbols
} catch {
return line;
}
};
const handlers: Record<string, (line: string) => Promise<string> | string> = {
ja: romanizeJapanese,
ko: romanizeHangul,
zh: romanizeChinese,
th: romanizeThai,
bn: romanizeBengali,
hi: romanizeHindi,
};
export const romanize = async (line: string) => {
@ -210,6 +256,8 @@ export const romanize = async (line: string) => {
if (hasKorean([line])) line = romanizeHangul(line);
if (hasChinese([line])) line = romanizeChinese(line);
if (hasThai([line])) line = romanizeThai(line);
if (hasBengali([line])) line = romanizeBengali(line);
if (hasHindi([line])) line = romanizeHindi(line);
return line;
};

View File

@ -10,6 +10,10 @@ export type SyncedLyricsPluginConfig = {
showLyricsEvenIfInexact: boolean;
lineEffect: LineEffect;
romanization: boolean;
convertChineseCharacter?:
| 'simplifiedToTraditional'
| 'traditionalToSimplified'
| 'disabled';
};
export type LineLyricsStatus = 'previous' | 'current' | 'upcoming';