From 212491aa895390af5009feb71ed89c0338ee4e0f Mon Sep 17 00:00:00 2001 From: Honey Date: Wed, 27 May 2026 02:52:35 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20convert=20katakana=20proper=20nouns=20(e?= =?UTF-8?q?.g.=20=E3=83=A8=E3=83=AB=E3=82=B7=E3=82=AB)=20to=20romaji=20in?= =?UTF-8?q?=20sort=20tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When kuromoji marks a katakana token as reading '*' (unknown word — common for band names like ヨルシカ), the code fell back to the surface_form which is still katakana. _hiraToRomaji can't handle katakana so it passed through unchanged, causing sort tags to show ヨルシカ instead of Yorushika. Fix: if the surface_form contains katakana and reading is *, convert it mechanically via _kataToHira + _hiraToRomaji — katakana-to-romaji is always unambiguous and needs no dictionary. Also add a Japanese-char guard in the frontend when applying romaji index values, so stale cache entries can never overwrite correct values with Japanese. Co-Authored-By: Claude Sonnet 4.6 --- backend/lib/romaji.js | 11 +++++++++-- src/ui.js | 11 +++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/lib/romaji.js b/backend/lib/romaji.js index 672e987..41f4670 100644 --- a/backend/lib/romaji.js +++ b/backend/lib/romaji.js @@ -65,12 +65,16 @@ function _hiraToRomaji(str) { const JP_RE = /[぀-ヿ㐀-䶿一-鿿＀-￯]/; +const KATA_RE = /[ァ-ヶ]/; + function toRomaji(text) { if (!text || !_tokenizer || !JP_RE.test(text)) return text || ''; const tokens = _tokenizer.tokenize(text); const hira = tokens.map(t => { const r = t.reading; - return (r && r !== '*') ? _kataToHira(r) : t.surface_form; + if (r && r !== '*') return _kataToHira(r); + if (KATA_RE.test(t.surface_form)) return _kataToHira(t.surface_form); // katakana proper noun + return t.surface_form; }).join(''); return _hiraToRomaji(hira).toLowerCase(); } @@ -81,7 +85,10 @@ function romajiForStorage(text) { const tokens = _tokenizer.tokenize(text); const parts = tokens.map(t => { const r = t.reading; - if (!r || r === '*') return t.surface_form; // ASCII — keep original + if (!r || r === '*') { + if (KATA_RE.test(t.surface_form)) return _hiraToRomaji(_kataToHira(t.surface_form)); // katakana proper noun + return t.surface_form; // ASCII — keep original + } return _hiraToRomaji(_kataToHira(r)); // Japanese — convert (lowercase) }); // Add space between adjacent alphabetic segments (word separation for names/titles) diff --git a/src/ui.js b/src/ui.js index afbd317..7d1e258 100644 --- a/src/ui.js +++ b/src/ui.js @@ -364,10 +364,13 @@ export function toggleInfoEdit() { fetchRomajiIndex().catch(() => null).then(() => { const rm = romajiLookup(_sId); if (rm) { - document.getElementById('ie-titlesort').value = rm.titleRomaji || ''; - document.getElementById('ie-artistsort').value = rm.artistRomaji || ''; - document.getElementById('ie-albumartistsort').value = rm.artistRomaji || ''; - document.getElementById('ie-albumsort').value = rm.albumRomaji || ''; + const _jp = /[ぁ-んァ-ン一-龯]/; + if (rm.titleRomaji && !_jp.test(rm.titleRomaji)) document.getElementById('ie-titlesort').value = rm.titleRomaji; + if (rm.artistRomaji && !_jp.test(rm.artistRomaji)) { + document.getElementById('ie-artistsort').value = rm.artistRomaji; + document.getElementById('ie-albumartistsort').value = rm.artistRomaji; + } + if (rm.albumRomaji && !_jp.test(rm.albumRomaji)) document.getElementById('ie-albumsort').value = rm.albumRomaji; } if (!S.cfg.backendUrl || !_sId) return null; const p = new URLSearchParams({ ndUrl: S.cfg.url, user: S.cfg.user, pass: S.cfg.pass, songId: _sId });