feat(dictionary): characters of Chinese, Korean and Greek, written by hand; pinyin typed

Parity with Japanese's kanji and kana:

- The characters' tab is named by its language (Hanzi, Hangul & Hanja,
  Alphabet), its groups too: HSK levels and the characters in common use
  (Hong Kong's grades for traditional ones); Hangul and the Hanja taught
  in schools; Greek's letters. Radicals for Chinese and Korean in their
  own forms (讠 for 言 in simplified Chinese), none for Greek.
- A character's readings are named as its language names them: On and
  Kun, Pinyin, Eum and Hun (음, 훈); a letter means its sound and name.
- How characters are written comes by language: Chinese from Make Me a
  Hanzi (its medians in KanjiVG's square, Arphic Public License, a data
  file beside the app, never in cards), the Hangul letters drawn by the
  curator; a Korean word is written letter by letter (닭: ㄷ ㅏ ㄺ). Words
  of alphabets (German, Greek) aren't written by hand.
- A Chinese word's pinyin is typed in a session, tone numbers turning
  into marks (ni3 → nǐ); KRDict's levels (초급, 중급, 고급) tag Korean
  words; a Korean word's pronunciation shows in brackets ([가치]).
- Smaller: Look up in the composer keeps a noun's article; the Watch
  filter names its level list; hints and placeholders by language.

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:35:17 +02:00
commit 5479b973fc
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
3 changed files with 2559 additions and 230 deletions

View file

@ -0,0 +1,26 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_learn_flutter/src/feature/kanji/model/hangul.dart';
void main() {
test('a Hangul syllable is written with its letters: the opening '
'consonant, the vowel and the closing one or cluster', () {
expect(hangulLetters('가'), ['ㄱ', 'ㅏ']);
expect(hangulLetters('닭'), ['ㄷ', 'ㅏ', 'ㄺ']);
expect(hangulLetters('쓰'), ['ㅆ', 'ㅡ']);
expect(hangulLetters('왜'), ['ㅇ', 'ㅙ']);
expect(hangulLetters('힣'), ['ㅎ', 'ㅣ', 'ㅎ']);
// What is no syllable stays as it is.
expect(hangulLetters('ㄱ'), ['ㄱ']);
expect(hangulLetters('食'), ['食']);
});
test('scripts whose strokes are drawn: Han characters, kana, Hangul; '
'not the alphabets', () {
expect(hasDrawnScript('食べる'), isTrue);
expect(hasDrawnScript('学习'), isTrue);
expect(hasDrawnScript('학교'), isTrue);
expect(hasDrawnScript('ㄱ'), isTrue);
expect(hasDrawnScript('das Haus'), isFalse);
expect(hasDrawnScript('Β β'), isFalse);
});
}

View file

@ -34,4 +34,21 @@ void main() {
test("a radical's label is the dictionary's", () {
expect(radicalLabel(85), 'radical:85');
});
test('the forms a radical takes in each language: simplified Chinese its '
"own, traditional Chinese and Korean without Japanese's own", () {
final speech = radicals[148];
final dragon = radicals[211];
final food = radicals[183];
expect(radicalFormsIn(speech, 'zh-Hans'), ['讠']);
expect(radicalFormsIn(speech, 'zh-Hant'), isEmpty);
expect(radicalFormsIn(dragon, 'ja'), ['竜']);
expect(radicalFormsIn(dragon, 'zh'), ['龙']);
expect(radicalFormsIn(dragon, 'ko'), isEmpty);
// Simplified Chinese writes 食 as 饣 at a character's side, not 飠.
expect(radicalFormsIn(food, 'zh-CN'), ['饣']);
expect(radicalFormsIn(food, 'zh-TW'), ['飠']);
expect(radicalsIn('zh-Hans')[148].variants, ['讠']);
expect((hasRadicals('ko'), hasRadicals('el')), (true, false));
});
}

View file

@ -51,6 +51,43 @@ void main() {
expect(await strokes.strokesOf(''), isNull);
});
test("Chinese draws its characters as Make Me a Hanzi does, KanjiVG's "
'for what it lacks; Korean its letters with its own strokes', () async {
final strokes = StrokeRepository();
// 说 is simplified Chinese's alone, which KanjiVG doesn't draw.
expect(await strokes.strokesOf('说'), isNull);
final say = await strokes.strokesOf('说', language: 'zh-Hans');
expect(say!.paths, hasLength(9));
expect(strokes.read('说', language: 'zh'), say);
// 水 is drawn by both, each in its own shapes.
expect(
(await strokes.strokesOf('水', language: 'zh'))!.paths,
isNot((await strokes.strokesOf('水'))!.paths),
);
// A kana Make Me a Hanzi doesn't draw comes from KanjiVG.
expect(await strokes.strokesOf('あ', language: 'zh'), isNotNull);
expect(
(await strokes.strokesOf('ㄹ', language: 'ko'))!.paths,
hasLength(3),
);
expect(
(await strokes.strokesOf('ㄺ', language: 'ko'))!.components,
['ㄹ', 'ㄱ'],
);
expect(await strokes.strokesOf('ㄹ', language: 'ja'), isNull);
});
test('each set by language: Japanese KanjiVG, Chinese Make Me a Hanzi '
'first, Korean its letters first', () {
expect(StrokeSet.of(null), [StrokeSet.kanjivg]);
expect(StrokeSet.of('zh-Hant'), [
StrokeSet.makemeahanzi,
StrokeSet.kanjivg,
]);
expect(StrokeSet.of('ko').first, StrokeSet.hangul);
});
test('the file a character is in: each 256 code points', () {
expect(strokeFileOf('食'), '098');
expect(strokeFileOf('あ'), '030');