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>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-09 14:47:46 +02:00
commit a6699b30cb
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 11756 additions and 84 deletions

View file

@ -225,8 +225,9 @@ class EndpointGitHubUpgrade extends _isc.EndpointRef {
}
/// The dictionaries the instance holds, to look words up in and to take
/// words from by a label (all JLPT N5 words). Reference data: any signed-in
/// person may read all of it.
/// words from by a label (all JLPT N5 words). A dictionary is one source
/// in one meaning language (`jmdict` in `en`). Reference data: any
/// signed-in person may read all of it.
/// {@category Endpoint}
class EndpointDictionary extends _isc.EndpointRef {
EndpointDictionary(_isc.EndpointCaller caller) : super(caller);
@ -242,13 +243,14 @@ class EndpointDictionary extends _isc.EndpointRef {
{},
);
/// The entries of the dictionary of [language] with meanings in
/// The entries of the dictionary of [source] with meanings in
/// [glossLanguage] that [query] finds, best first: those with a key that
/// is the query, then a loose key that is (`sushi` for すうし), then a key
/// starting with it, then a loose key; each in the dictionary's order. At
/// most [limit] (30 unless given).
/// most [limit] (30 unless given). None for a dictionary the instance
/// doesn't hold.
_ida.Future<List<_iqbqiphu.DictionaryHit>> search(
String language,
String source,
String glossLanguage,
String query, {
int? limit,
@ -256,24 +258,24 @@ class EndpointDictionary extends _isc.EndpointRef {
'dictionary',
'search',
{
'language': language,
'source': source,
'glossLanguage': glossLanguage,
'query': query,
'limit': limit,
},
);
/// How many entries of the dictionary of [language] with meanings in
/// How many entries of the dictionary of [source] with meanings in
/// [glossLanguage] carry [label].
_ida.Future<int> countLabelled(
String language,
String source,
String glossLanguage,
String label,
) => caller.callServerEndpoint<int>(
'dictionary',
'countLabelled',
{
'language': language,
'source': source,
'glossLanguage': glossLanguage,
'label': label,
},
@ -282,7 +284,7 @@ class EndpointDictionary extends _isc.EndpointRef {
/// The entries that carry [label], in the dictionary's order: [limit] of
/// them (100 unless given, at most [maxPage]) after the first [offset].
_ida.Future<List<_iqbqiphu.DictionaryHit>> labelled(
String language,
String source,
String glossLanguage,
String label, {
int? offset,
@ -291,7 +293,7 @@ class EndpointDictionary extends _isc.EndpointRef {
'dictionary',
'labelled',
{
'language': language,
'source': source,
'glossLanguage': glossLanguage,
'label': label,
'offset': offset,

View file

@ -23,6 +23,7 @@ abstract class DictionarySource
this.id,
required this.source,
required this.sourceVersion,
String? kind,
required this.language,
required this.glossLanguage,
required this.license,
@ -31,12 +32,13 @@ abstract class DictionarySource
required this.fileName,
required this.fileHash,
required this.loadedAt,
});
}) : kind = kind ?? 'words';
factory DictionarySource({
int? id,
required String source,
required String sourceVersion,
String? kind,
required String language,
required String glossLanguage,
required String license,
@ -52,6 +54,7 @@ abstract class DictionarySource
id: jsonSerialization['id'] as int?,
source: jsonSerialization['source'] as String,
sourceVersion: jsonSerialization['sourceVersion'] as String,
kind: jsonSerialization['kind'] as String?,
language: jsonSerialization['language'] as String,
glossLanguage: jsonSerialization['glossLanguage'] as String,
license: jsonSerialization['license'] as String,
@ -79,6 +82,11 @@ abstract class DictionarySource
/// Its version (`3.6.2 (2026-10-05)`).
String sourceVersion;
/// What it holds: `words` (JMdict) or `characters` (KANJIDIC2). A string
/// rather than an enum, so an app older than a new kind can leave out
/// what it doesn't know.
String kind;
/// The words' language (`ja`).
String language;
@ -109,6 +117,7 @@ abstract class DictionarySource
int? id,
String? source,
String? sourceVersion,
String? kind,
String? language,
String? glossLanguage,
String? license,
@ -125,6 +134,7 @@ abstract class DictionarySource
if (id != null) 'id': id,
'source': source,
'sourceVersion': sourceVersion,
'kind': kind,
'language': language,
'glossLanguage': glossLanguage,
'license': license,
@ -143,6 +153,7 @@ abstract class DictionarySource
if (id != null) 'id': id,
'source': source,
'sourceVersion': sourceVersion,
'kind': kind,
'language': language,
'glossLanguage': glossLanguage,
'license': license,
@ -167,6 +178,7 @@ class _DictionarySourceImpl extends DictionarySource {
int? id,
required String source,
required String sourceVersion,
String? kind,
required String language,
required String glossLanguage,
required String license,
@ -179,6 +191,7 @@ class _DictionarySourceImpl extends DictionarySource {
id: id,
source: source,
sourceVersion: sourceVersion,
kind: kind,
language: language,
glossLanguage: glossLanguage,
license: license,
@ -197,6 +210,7 @@ class _DictionarySourceImpl extends DictionarySource {
Object? id = _Undefined,
String? source,
String? sourceVersion,
String? kind,
String? language,
String? glossLanguage,
String? license,
@ -210,6 +224,7 @@ class _DictionarySourceImpl extends DictionarySource {
id: id is int? ? id : this.id,
source: source ?? this.source,
sourceVersion: sourceVersion ?? this.sourceVersion,
kind: kind ?? this.kind,
language: language ?? this.language,
glossLanguage: glossLanguage ?? this.glossLanguage,
license: license ?? this.license,