fix(study): pinyin of names and sayings is typed and checked right

CC-CEDICT capitalises names, so their readings start with a capital vowel
with its tone mark (欧洲 Ōu zhōu, 澳门 Ào mén, 俄语 É yǔ), which the test
for pinyin didn't know: those words were never typed. It now reads the
reading in lower case.

CC-CEDICT also keeps a saying's comma, written between spaces (bù pà màn ,
jiù pà zhàn), and the comma split the reading into two readings, so typing
the whole saying was wrong and half of it right. A comma with a space
before it now stays part of the reading, and commas count for nothing when
readings are compared; a comma written straight after a reading (xíng,
háng) still separates readings.

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 17:45:56 +02:00
commit 6ea7375376
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 38 additions and 8 deletions

View file

@ -77,22 +77,33 @@ class const PinyinInputFormatter() extends TextInputFormatter {
}
/// [pinyin] as pinyin is compared: with tone marks ([withToneMarks]),
/// lower case, without spaces, apostrophes or hyphens (`Xué xí`, `xue2xi2`
/// → `xuéxí`).
/// lower case, without spaces, apostrophes, hyphens or commas (`Xué xí`,
/// `xue2xi2` → `xuéxí`; a saying's `màn , jiù` → `mànjiù`).
String pinyinKey(String pinyin) => withToneMarks(
pinyin.trim(),
).toLowerCase().replaceAll(RegExp(r"[\s'’·-]+"), '');
).toLowerCase().replaceAll(RegExp(r"[\s'’·,,-]+"), '');
/// The readings a card's pinyin field gives, one or several (`xíng,
/// háng`), as compared ([pinyinKey]).
Set<String> givenPinyin(String field) => {
for (final reading in field.split(RegExp('[,,;;//、]')))
for (final reading in field.split(_readingsApart))
if (pinyinKey(reading).isNotEmpty) pinyinKey(reading),
};
/// Whether [reading] is pinyin: Latin letters, with or without tone marks,
/// and no kana or Han characters.
bool isPinyin(String reading) =>
reading.trim().isNotEmpty && _pinyin.hasMatch(reading.trim());
/// What separates the readings of a pinyin field: a comma written straight
/// after a reading (`xíng, háng`), a semicolon, a slash or an enumeration
/// comma. A comma with a space before it is a saying's own, as CC-CEDICT
/// writes its punctuation between spaces (`bù pà màn , jiù pà zhàn`): the
/// whole saying is its one reading.
final _readingsApart = RegExp(r'(?<!\s)[,,]|[;;//、]');
final _pinyin = RegExp(r"^[A-Za-zÜüāáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜ\s'’·,,:-]+$");
/// Whether [reading] is pinyin: Latin letters, with or without tone marks,
/// whatever their case (CC-CEDICT capitalises names: `Ōu zhōu`), and no
/// kana or Han characters.
bool isPinyin(String reading) {
final lower = reading.trim().toLowerCase();
return lower.isNotEmpty && _pinyin.hasMatch(lower);
}
/// Pinyin in lower case, as [isPinyin] reads it.
final _pinyin = RegExp(r"^[a-züāáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜ\s'’·,,:-]+$");

View file

@ -27,6 +27,17 @@ void main() {
expect(givenPinyin('xíng').contains(pinyinKey('xing1')), isFalse);
});
test("a saying's comma is part of its reading, as CC-CEDICT writes it "
'between spaces; a comma after a reading separates readings', () {
final saying = givenPinyin('bù pà màn , jiù pà zhàn');
expect(saying.contains(pinyinKey('bu4 pa4 man4 jiu4 pa4 zhan4')), isTrue);
expect(saying.contains(pinyinKey('bu4pa4man4, jiu4pa4zhan4')), isTrue);
expect(saying.contains(pinyinKey('bu4 pa4 man4')), isFalse);
expect(saying.contains(pinyinKey('jiu4 pa4 zhan4')), isFalse);
expect(givenPinyin('xíng,háng'), {'xíng', 'háng'});
});
test("pinyin is told from kana and from words' own letters", () {
expect(isPinyin('xué xí'), isTrue);
expect(isPinyin('shuō'), isTrue);
@ -35,6 +46,14 @@ void main() {
expect(isPinyin(''), isFalse);
});
test('a proper noun, capitalised with its tone mark, is pinyin too', () {
expect(isPinyin('Ōu zhōu'), isTrue);
expect(isPinyin('Ào mén'), isTrue);
expect(isPinyin('É yǔ'), isTrue);
expect(isPinyin('Lǘ'), isTrue);
expect(givenPinyin('Ōu zhōu').contains(pinyinKey('ou1zhou1')), isTrue);
});
test('typing turns a syllable and its tone into the marked syllable, the '
'caret where it was typed', () {
const formatter = PinyinInputFormatter();