fix(dictionary): a Hanja reads 물 수, and each radical's form shows once

Korean names a Hanja by its meaning, then its sound; simplified Chinese
shares some radicals' forms with Japanese (麦), which showed twice, and
writes 黑 as itself. A test can read a language's strokes beforehand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-14 12:45:11 +02:00
commit e7f3abdb8d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
4 changed files with 26 additions and 18 deletions

View file

@ -154,10 +154,12 @@ class const DictionaryCharacterTile({
final l10n = AppLocalizations.of(context); final l10n = AppLocalizations.of(context);
final theme = Theme.of(context); final theme = Theme.of(context);
final muted = theme.colorScheme.onSurfaceVariant; final muted = theme.colorScheme.onSurfaceVariant;
final readings = [ final on = joinReadings(character.onReadings, character.language);
joinReadings(character.onReadings, character.language), final kun = joinReadings(character.kunReadings, character.language);
joinReadings(character.kunReadings, character.language), // Korean names a Hanja by its meaning, then its sound (물 수).
].where((part) => part.isNotEmpty).join(' · '); final readings = primaryLanguageOf(character.language) == 'ko'
? [kun, on].where((part) => part.isNotEmpty).join(' ')
: [on, kun].where((part) => part.isNotEmpty).join(' · ');
final marks = [ final marks = [
...characterMarks(l10n, character), ...characterMarks(l10n, character),
if (character.strokes case final strokes?) if (character.strokes case final strokes?)

View file

@ -300,14 +300,16 @@ List<String> radicalFormsIn(Radical radical, String language) {
if (_japaneseForms[radical.number] != form) form, if (_japaneseForms[radical.number] != form) form,
]; ];
final simplified = _simplifiedForms[radical.number]; final simplified = _simplifiedForms[radical.number];
return primary == 'ko' || isTraditionalChinese(language) || simplified == null if (primary == 'ko' || isTraditionalChinese(language) || simplified == null) {
? shared return shared;
: [ }
// Simplified Chinese writes 食 as 饣 at a character's side. // Simplified Chinese writes 食 as 饣 at a character's side; a form it
for (final form in shared) // shares with Japanese (麦), or the radical itself (黑), only once.
if (radical.number != 184) form, return {
simplified, for (final form in shared)
]; if (radical.number != 184) form,
simplified,
}.where((form) => form != radical.character).toList();
} }
/// The 214 radicals with the forms they take in [language] /// The 214 radicals with the forms they take in [language]

View file

@ -49,6 +49,9 @@ void main() {
expect(radicalFormsIn(food, 'zh-CN'), ['饣']); expect(radicalFormsIn(food, 'zh-CN'), ['饣']);
expect(radicalFormsIn(food, 'zh-TW'), ['飠']); expect(radicalFormsIn(food, 'zh-TW'), ['飠']);
expect(radicalsIn('zh-Hans')[148].variants, ['讠']); expect(radicalsIn('zh-Hans')[148].variants, ['讠']);
// Each form once, never the radical itself.
expect(radicalFormsIn(radicals[198], 'zh'), ['麦']);
expect(radicalFormsIn(radicals[202], 'zh'), isEmpty);
expect((hasRadicals('ko'), hasRadicals('el')), (true, false)); expect((hasRadicals('ko'), hasRadicals('el')), (true, false));
}); });
} }

View file

@ -1,18 +1,19 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_learn_flutter/src/feature/kanji/data/stroke_repository.dart'; import 'package:halkyon_learn_flutter/src/feature/kanji/data/stroke_repository.dart';
/// The bundled strokes of [characters], read already: reading an asset is /// The bundled strokes of [characters] in [language], read already:
/// real input, which a widget test's fake clock never lets finish, so it /// reading an asset is real input, which a widget test's fake clock never
/// happens outside it and the app gets this repository /// lets finish, so it happens outside it and the app gets this repository
/// (`testDependencies(strokes: ...)`). /// (`testDependencies(strokes: ...)`).
Future<StrokeRepository> loadedStrokes( Future<StrokeRepository> loadedStrokes(
WidgetTester tester, WidgetTester tester,
Iterable<String> characters, Iterable<String> characters, {
) async { String? language,
}) async {
final strokes = StrokeRepository(); final strokes = StrokeRepository();
await tester.runAsync(() async { await tester.runAsync(() async {
for (final character in characters) { for (final character in characters) {
await strokes.strokesOf(character); await strokes.strokesOf(character, language: language);
} }
}); });
return strokes; return strokes;