feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/dictionary/data/dictionary_gateway.dart';
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/dictionary/model/dictionary.dart';
|
|
|
|
|
|
|
|
|
|
/// JMdict in English, as an instance that loaded it lists it.
|
|
|
|
|
const jmdict = Dictionary(
|
|
|
|
|
source: 'jmdict',
|
|
|
|
|
sourceVersion: '3.6.2 (2026-10-05)',
|
feat: dictionaries hold words or characters, and are asked by their source
A dictionary source now says what it holds (`kind`: `words`, the default
for files from before, or `characters`), a string so that an app older
than a new kind leaves it out. Search, countLabelled and labelled take the
dictionary by its source and meaning language, as the unique index names
it, rather than by the language: with KANJIDIC2 beside JMdict, a search
for words would otherwise bring kanji, and JLPT N5 would hold both. The
search reads the language for its query variants from the source's row;
a dictionary the instance doesn't hold finds nothing. The rank index goes
by source too.
Migration: drops and recreates dictionary_entry_rank_idx on (source,
glossLanguage, rank), and adds dictionary_source.kind, text NOT NULL
DEFAULT 'words', so a JMdict loaded before is a dictionary of words. Both
tables are reference data, never synced; the old server keeps working
until the switch (its queries by language just lose the index).
In the app, a studyset's dictionary is one of words unless a kind is
asked for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 14:47:46 +02:00
|
|
|
kind: DictionaryKind.words,
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
language: 'ja',
|
|
|
|
|
glossLanguage: 'en',
|
|
|
|
|
license: 'CC-BY-SA-4.0',
|
|
|
|
|
credits: [
|
|
|
|
|
(
|
|
|
|
|
name: 'JMdict',
|
|
|
|
|
by: 'Electronic Dictionary Research and Development Group (EDRDG)',
|
|
|
|
|
license: 'CC-BY-SA-4.0',
|
|
|
|
|
url: 'https://www.edrdg.org/edrdg/licence.html',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
entryCount: 3,
|
|
|
|
|
);
|
|
|
|
|
|
feat: dictionaries hold words or characters, and are asked by their source
A dictionary source now says what it holds (`kind`: `words`, the default
for files from before, or `characters`), a string so that an app older
than a new kind leaves it out. Search, countLabelled and labelled take the
dictionary by its source and meaning language, as the unique index names
it, rather than by the language: with KANJIDIC2 beside JMdict, a search
for words would otherwise bring kanji, and JLPT N5 would hold both. The
search reads the language for its query variants from the source's row;
a dictionary the instance doesn't hold finds nothing. The rank index goes
by source too.
Migration: drops and recreates dictionary_entry_rank_idx on (source,
glossLanguage, rank), and adds dictionary_source.kind, text NOT NULL
DEFAULT 'words', so a JMdict loaded before is a dictionary of words. Both
tables are reference data, never synced; the old server keeps working
until the switch (its queries by language just lose the index).
In the app, a studyset's dictionary is one of words unless a kind is
asked for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 14:47:46 +02:00
|
|
|
/// KANJIDIC2 in English, with the kana, as an instance that loaded it lists
|
|
|
|
|
/// it.
|
|
|
|
|
const kanjidic = Dictionary(
|
|
|
|
|
source: 'kanjidic',
|
|
|
|
|
sourceVersion: '2026-282',
|
|
|
|
|
kind: DictionaryKind.characters,
|
|
|
|
|
language: 'ja',
|
|
|
|
|
glossLanguage: 'en',
|
|
|
|
|
license: 'CC-BY-SA-4.0',
|
|
|
|
|
credits: [
|
|
|
|
|
(
|
|
|
|
|
name: 'KANJIDIC2',
|
|
|
|
|
by: 'Electronic Dictionary Research and Development Group (EDRDG)',
|
|
|
|
|
license: 'CC-BY-SA-4.0',
|
|
|
|
|
url: 'https://www.edrdg.org/edrdg/licence.html',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
entryCount: 2,
|
|
|
|
|
);
|
|
|
|
|
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
/// A JMdict word: [headword] read [reading], meaning [glosses], [labels]
|
|
|
|
|
/// and the parts of speech [pos] on its one sense.
|
|
|
|
|
DictionaryWord word(
|
|
|
|
|
String id,
|
|
|
|
|
String headword, {
|
|
|
|
|
String? reading,
|
|
|
|
|
List<String> glosses = const [],
|
|
|
|
|
List<String> labels = const [],
|
|
|
|
|
List<String> pos = const [],
|
|
|
|
|
}) => DictionaryWord(
|
|
|
|
|
ref: (source: 'jmdict', id: id),
|
|
|
|
|
headword: headword,
|
|
|
|
|
reading: reading,
|
|
|
|
|
labels: labels,
|
|
|
|
|
senses: [VocabularySense(glosses: glosses, pos: pos)],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/// A [DictionaryGateway] in memory: an instance with the dictionaries
|
|
|
|
|
/// [available], each holding its [words] in their order. A search finds the words whose
|
|
|
|
|
/// headword, reading or a meaning starts with the query.
|
|
|
|
|
class FakeDictionaryGateway({
|
|
|
|
|
final List<Dictionary> available = const [jmdict],
|
|
|
|
|
final List<DictionaryWord> words = const [],
|
|
|
|
|
}) implements DictionaryGateway {
|
|
|
|
|
/// When set, every request fails with it.
|
|
|
|
|
Exception? fail;
|
|
|
|
|
|
|
|
|
|
/// How often the dictionaries were asked for.
|
|
|
|
|
int asked = 0;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<List<Dictionary>> dictionaries() async {
|
|
|
|
|
asked++;
|
|
|
|
|
if (fail case final error?) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
return available;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<List<DictionaryWord>> search(
|
|
|
|
|
Dictionary dictionary,
|
|
|
|
|
String query,
|
|
|
|
|
) async {
|
|
|
|
|
if (fail case final error?) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
final typed = query.trim().toLowerCase();
|
|
|
|
|
return [
|
|
|
|
|
for (final word in words)
|
|
|
|
|
if ([
|
|
|
|
|
word.headword,
|
|
|
|
|
?word.reading,
|
|
|
|
|
for (final sense in word.senses) ...sense.glosses,
|
|
|
|
|
].any((key) => key.toLowerCase().startsWith(typed)))
|
|
|
|
|
word,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<int> countLabelled(Dictionary dictionary, String label) async =>
|
|
|
|
|
words.where((word) => word.labels.contains(label)).length;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<List<DictionaryWord>> labelled(
|
|
|
|
|
Dictionary dictionary,
|
|
|
|
|
String label, {
|
|
|
|
|
required int offset,
|
|
|
|
|
required int limit,
|
|
|
|
|
}) async => words
|
|
|
|
|
.where((word) => word.labels.contains(label))
|
|
|
|
|
.skip(offset)
|
|
|
|
|
.take(limit)
|
|
|
|
|
.toList();
|
|
|
|
|
}
|