It logged dictionary.Langs(), which is a compile-time constant of the languages DreamDict *supports*. The database deployed until today supported Spanish and contained none of it, so the line printed a confident "[en fr pt-PT es zh]" over a file where every Spanish lookup came back empty — the exact failure the line exists to catch, reported as success. Contents() counts rows per language instead. For a file somebody has to copy onto the box by hand, "what is in it" is the only question worth asking, and the answer is now en=136615 es=102971 fr=56096 pt-PT=136300 zh=120883. Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
112 lines
4.5 KiB
Go
112 lines
4.5 KiB
Go
package lexicon
|
|
|
|
// A word lookup used to mean exactly one thing: the embedded datasets, which
|
|
// speak English and Mandarin and nothing else. That was fine while Petal had
|
|
// one writer. It stops being fine the moment a pt-PT writer right-clicks a
|
|
// word and gets a Chinese gloss.
|
|
//
|
|
// So the lookup becomes a seam. A [Provider] answers the same two questions the
|
|
// popover and the hover tooltip have always asked; which provider answers them
|
|
// depends on the writer's language pair, and [Set.For] is the only place that
|
|
// decision is made.
|
|
|
|
// Provider answers word lookups for one writer. The embedded datasets and
|
|
// DreamDict both satisfy it, and both treat a word they don't carry as an empty
|
|
// result rather than an error — a miss is an ordinary outcome of looking a word
|
|
// up, not a failure.
|
|
type Provider interface {
|
|
// Lookup returns the full popover payload: gloss, phonetic, definitions,
|
|
// synonyms, and whatever extras the provider carries.
|
|
Lookup(word string) (Result, error)
|
|
// Gloss returns just the writer's-language translation. It is the hover
|
|
// tooltip's fast path and skips everything else.
|
|
Gloss(word string) (GlossResult, error)
|
|
}
|
|
|
|
// LangZh is the one pair language still served by the embedded datasets. Every
|
|
// other pair goes to DreamDict — see [Set.For] for why zh is held back.
|
|
const LangZh = "zh"
|
|
|
|
// langEN is the language DreamDict is asked about for definitions, synonyms and
|
|
// pronunciation. English is always the *target* language of the pair — what
|
|
// varies is the language the gloss is written in.
|
|
const langEN = "en"
|
|
|
|
// Set holds every provider Petal can serve a lookup from and picks between them
|
|
// by pair language. One Set is shared by the whole process: the embedded
|
|
// datasets load once, and dict.db is one read-only handle.
|
|
type Set struct {
|
|
embedded *Lexicon
|
|
// dream is nil when dict.db was not deployed. That is a supported state,
|
|
// not an error — see [Set.For].
|
|
dream *DreamDict
|
|
}
|
|
|
|
// NewSet returns a Set backed by the embedded datasets and, when dream is
|
|
// non-nil, DreamDict. Passing a nil dream is how Petal runs without dict.db.
|
|
func NewSet(dream *DreamDict) *Set {
|
|
return &Set{embedded: New(), dream: dream}
|
|
}
|
|
|
|
// HasDreamDict reports whether a dict.db is open. Only startup logging and
|
|
// tests care; a handler never asks, because [Set.For] always returns something
|
|
// usable.
|
|
func (s *Set) HasDreamDict() bool { return s.dream != nil }
|
|
|
|
// Contents describes what the open dict.db actually holds, for the startup log.
|
|
// With no dictionary it says so rather than returning an empty string, because
|
|
// a blank in a log line is indistinguishable from a bug in the log line.
|
|
func (s *Set) Contents() string {
|
|
if s.dream == nil {
|
|
return "no dict.db — embedded datasets only"
|
|
}
|
|
return s.dream.Contents()
|
|
}
|
|
|
|
// For returns the provider that should answer lookups for a writer whose pair
|
|
// language is lang.
|
|
//
|
|
// Three rules, in order:
|
|
//
|
|
// zh — and an empty code, which is what a pre-Phase-16 row reads as — stays on
|
|
// the embedded ECDICT gloss. Not because DreamDict lacks Chinese (it has
|
|
// CC-CEDICT), but because that path is in daily use by a real writer and the
|
|
// two have not yet been compared on her actual lookups. Switching it is a
|
|
// quality decision, and it hasn't been made.
|
|
//
|
|
// Any other pair goes to DreamDict, which is the only source that has pt-PT,
|
|
// French or Spanish at all.
|
|
//
|
|
// If dict.db was never deployed, a non-zh writer falls back to the embedded
|
|
// datasets with the gloss suppressed. This is the interesting case: the naive
|
|
// "no data" answer would blank the popover entirely, when in fact the English
|
|
// half of it — definitions, synonyms, phonetic — is compiled into the binary
|
|
// and perfectly correct for her. Only the translation is missing, so only the
|
|
// translation goes missing. A failed dictionary deploy costs her the gloss, not
|
|
// the dictionary.
|
|
func (s *Set) For(lang string) Provider {
|
|
if lang == "" || lang == LangZh {
|
|
return s.embedded
|
|
}
|
|
if s.dream != nil {
|
|
return dreamProvider{dict: s.dream, native: lang}
|
|
}
|
|
return glossless{s.embedded}
|
|
}
|
|
|
|
// glossless serves the embedded datasets with the Chinese gloss stripped, for a
|
|
// writer who does not read Chinese. Handing her the zh gloss would be worse
|
|
// than handing her nothing: an empty field reads as "not found", where the
|
|
// wrong language reads as Petal being broken.
|
|
type glossless struct{ inner Provider }
|
|
|
|
func (g glossless) Lookup(word string) (Result, error) {
|
|
res, err := g.inner.Lookup(word)
|
|
res.Gloss = ""
|
|
return res, err
|
|
}
|
|
|
|
func (g glossless) Gloss(word string) (GlossResult, error) {
|
|
return GlossResult{Word: word}, nil
|
|
}
|