Fix katakana lookup: normalize halfwidth forms to fullwidth - #106
Conversation
e9b6f9b to
52f6cb5
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #106 +/- ##
=======================================
Coverage 98.74% 98.75%
=======================================
Files 51 51
Lines 2155 2170 +15
Branches 415 421 +6
=======================================
+ Hits 2128 2143 +15
Misses 27 27
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thank you for the proposal. However, I'm afraid this change doesn't work as intended. For example, with the current behavior, hovering over "ソ" in "ソフトウェアエンジニア" should show all of the following candidates at once (depending on the installed dictionaries):
Hovering over the "エンジニア" part should show the following (again, depending on the installed dictionaries):
This is the intended behavior, and this PR breaks it. Furthermore, with a spelling like "ソフトウェアエンジニアー", nothing would be shown at all unless the dictionary contains that exact spelling. A core design principle of Mouse Dictionary is to show all plausible candidates at once, and this change goes against that principle, so I can't merge it as is. |
Halfwidth katakana (U+FF61-FF9F, common in manga/UI text) never matched fullwidth headwords: hovering テレビ found nothing for テレビ. When the hovered text contains halfwidth katakana, the NFKC-normalized form is added as an additional candidate chain, so every prefix decomposition is generated for both spellings (テレヒ -> テレ/テ and テレ/テ). NFKC also folds voiced/semi-voiced combining sequences (カ + ゙ -> ガ). Prefix generation for fullwidth katakana runs is unchanged: all candidates are kept, per the show-all-plausible-candidates design.
|
@wtetsu You're right — the mid-run collapse guard broke the show-all-candidates design, and I verified it against your exact examples before reworking: with the guard, hovering I've dropped that part entirely. The PR now does only the halfwidth normalization you agreed would be useful: for halfwidth input the NFKC form is added as a second candidate chain, so all the usual prefixes are generated for both spellings and fullwidth katakana behavior is byte-for-byte unchanged. Confirmed against your examples on the new code (via
Rebased onto current master; 274/274 tests pass. |
52f6cb5 to
8e9eddd
Compare
Drop the mid-run collapse guard: upstream keeps every prefix candidate (show-all-plausible-candidates design, wtetsu's review on wtetsu#106). The ノーベル賞 case is handled in the data layer: the ja-ar pack carries ノーベル賞 itself as a headword, so the longest match wins and the shorter ノー no longer misleads the lookup on its own. Keep the halfwidth NFKC chain (テレビ -> テレビ + both prefix chains). Matches the reworked PR wtetsu#106 head so a future merge is a no-op.
| // prefix decomposition (show-all-plausible-candidates design), e.g. | ||
| // テレヒ -> テレヒ/テレ/テ plus テレビ/テレ/テ. | ||
| const chains = [str]; | ||
| if (RE_HALFWIDTH_KATAKANA.test(str)) { |
There was a problem hiding this comment.
If the goal of this PR is to handle halfwidth katakana, applying normalize("NFKC") to the whole string probably causes unintended conversions in the non-katakana parts as well.
NFKC may well be useful for some characters other than halfwidth katakana. But the current code applies NFKC to the entire string as soon as it contains even a single halfwidth katakana character, which seems a bit odd.
To keep this PR focused on its goal, would it be possible to convert only the halfwidth katakana?
There was a problem hiding this comment.
Good point — that was sloppy. Reworked in 15a0ddc: the conversion now runs per character inside the halfwidth-katakana range only, so nothing else in the string gets a compatibility fold (① stays ① — whole-string NFKC would have rewritten it to "1").
The one thing I couldn't drop is the fold of カ + ゙ -> ガ. Converting U+FF9E/FF9F alone yields the combining marks ゙/ ゚, and the precomposed fullwidth letters (ガ, タ->ダ etc.) don't exist as single conversions of the halfwidth pairs, so a canonical-composition pass is needed after the scoped replace. I used NFC: it composes canonically (exactly what the voiced marks need) and, unlike NFKC, performs no compatibility mappings, so it can't touch anything outside katakana. A test now pins the scoping: "①テレビ" produces "①テレビ/①テレ/…" chains and asserts "1テレビ" is never generated.
The English-Gloss bridge returned the WHOLE en-ar page entry for a gloss, which merges every homograph Wiktionary put on that English page: ノー (loanword no) carried the archaic cattle noun نَعَم and a duplicated interjection chunk, and arwiktionary definitions attached as full grammar essays. Bridging now keeps only the dominant (first) sense chunk and caps the parenthetical definition at 80 chars in both bridge sites (JMdict render_sense + kaikki fill_from_jawikt). Applied across the pack: 92k shard lines shortened, 0 headwords changed, en-ar pack untouched. Code paths (src/, PRs wtetsu#106/wtetsu#107) unchanged — data layer only.
Whole-string NFKC folds unrelated characters (circled digits -> ASCII, ligatures, fullwidth latin). Convert each halfwidth-katakana character (U+FF61-FF9F) individually, then a canonical-composition NFC pass to combine the converted voiced marks with the preceding letter (カ + ゙ -> ガ). NFC performs no compatibility folds, so nothing outside the halfwidth katakana range can change.
Syncs feat/arabic-packs with the PR wtetsu#106 review fix (wtetsu: whole-string NFKC would fold unrelated characters). Per-character conversion inside U+FF61-FF9F plus a canonical NFC composition pass for voiced marks; nothing outside the range can change.
| // (circled digits, fullwidth latin, ligatures...). The trailing NFC pass is a | ||
| // canonical-composition pass, needed to combine the converted voiced marks | ||
| // with the preceding letter (カ + ゙ -> ガ); it performs no compatibility folds. | ||
| const RE_HALFWIDTH_KATAKANA = /[\uFF61-\uFF9F]/; |
There was a problem hiding this comment.
The intent of this code isn't very clear, and it seems inefficient. Could you rewrite it?
For example, I don't see any reason to define two nearly identical regexes and use one for test() and the other for replace(). Isn't replace() alone enough? If there's no halfwidth katakana in the string, it simply does nothing.
Also, this code calls the conversion function once per character, which looks inefficient. I think this could also cause dakuten-related processing to produce incorrect results.
Wouldn't a regex like /[\uFF61-\uFF9F]+/g solve this?
"テレビ".replace(/[\uFF61-\uFF9F]+/g, (run) => run.normalize("NFKC")) // => "テレビ"There was a problem hiding this comment.
Done in 4fe574f — the run-based regex is exactly right and simpler than what I had. One regex, one NFKC call per maximal run, and the dakuten concern you raised is handled by it directly: NFKC over the run composes カ + ゙ -> ガ in the same pass, whereas my per-character version produced a decomposed カ+combining mark until a second NFC pass fixed it (and would have left ゙ following non-kanakana input decomposed).
The test()/replace() duplication is gone too: a string without halfwidth katakana comes back unchanged from replace(), so the existing converted !== str check is the only guard needed.
Behavior verified unchanged on top of this: テレビ/ガス chains as before, ①テレビ keeps ① intact, あ゙ outside a katakana run stays out of scope. 274/274 tests, biome/tsc clean.
Follow-up on review: one regex (runs, U+FF61-FF9F+), one NFKC call per maximal run. Running NFKC per character needed a separate NFC pass to compose voiced marks; NFKC over the whole run composes them directly (カ + ゙ -> ガ). No test()/replace() duplication: a string without halfwidth katakana comes back unchanged, so the chain guard is the already-present converted !== str check.
Syncs feat/arabic-packs with PR wtetsu#106 round-3 review fix: one run-based regex (U+FF61-FF9F+), NFKC composes voiced marks within each run, no separate NFC pass, no test()/replace() duplication.
|
Thanks! |
Problem
Halfwidth katakana (
U+FF61-FF9F, common in manga and legacy UI text) never matches fullwidth headwords: hovering overテレビfinds nothing for the dictionary entryテレビ.Fix
When the hovered text contains halfwidth katakana, its NFKC-normalized fullwidth form is added as an additional candidate chain, so the usual prefix decomposition runs on both spellings:
テレビ→テレビ / テレヒ / テレ / テandテレビ / テレ / テカ + ゙combining sequences fold to voiced characters (ガス→ガス)No other behavior changes: fullwidth katakana runs are still cut into all prefixes, exactly as before (hovering
ソinソフトウェアエンジニアstill yieldsソフトウェア…,ソフトウェア,ソフト,ソ), and hiragana/kanji okurigana handling is untouched. Theエンジニア-part lookup and theエンジニアーlong-vowel spelling also keep their original chains.(Prior revision removed per @wtetsu's review — dropped the mid-run collapse guard to restore the show-all-plausible-candidates design.)
Tests
Added to the existing "Test Japanese words" suite: halfwidth→fullwidth headword lookup with both prefix chains, voiced-fold case, and a regression assertion that fullwidth katakana prefix cutting still works.
npm test: 274/274 pass.