Files
petal/internal/lexicon/provider.go
T
prosolis 74bf600593 Make the dictionary startup line report rows, not capabilities
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
2026-07-27 10:51:09 -07:00

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
}