Files
dreamdict/internal/dictionary/dict.go
prosolis 704c06f41d Add regional variant tagging (us/gb) for English words
- SCOWL loader now loads american-words and british-words alongside
  english-words, tagging each with variant "us", "gb", or NULL (common)
- Words appearing in both American and British lists get NULL variant
- Add variant column to words table with schema migration
- API: /define returns variant field for English, /random and /words
  accept ?variant=us|gb filter
- Add total word count to server status line
- Fix rows.Close leak in PopulateCMUTails, add rows.Err() check
- Add tests for PopulateCMUTails, cmuTail, TotalWordCount
- Expand test seed data and assertions to cover pt-PT definitions,
  synonyms, and validity checks
- Update README with variant docs, /difficulty, /words, /rhyme
  endpoints, and current database statistics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 15:21:27 -07:00

109 lines
2.3 KiB
Go

package dictionary
import (
"database/sql"
"errors"
)
var (
ErrNoMatch = errors.New("dictionary: no word matches filters")
ErrNotSeeded = errors.New("dictionary: database not seeded, run: go run ./cmd/dictimport")
)
var supportedLangs = []string{"en", "fr", "pt-PT", "zh"}
func ValidLang(lang string) bool {
for _, l := range supportedLangs {
if l == lang {
return true
}
}
return false
}
type Options struct {
MinLength int
MaxLength int
POS string // "", "noun", "verb", "adjective", "adverb"
MinFrequency int // 0 = no filter
MinDifficulty float64 // 0.0 = no filter
MaxDifficulty float64 // 0.0 = no filter
Variant string // "", "us", "gb" — empty means no filter
}
type Definition struct {
POS string `json:"pos"`
Gloss string `json:"gloss"`
Source string `json:"source"`
Priority int `json:"priority"`
}
type EnglishEquivalent struct {
Word string `json:"word"`
Definition string `json:"definition"`
Synset string `json:"synset"`
}
type Pronunciation struct {
Format string `json:"format"`
Value string `json:"value"`
Source string `json:"source"`
}
type Dictionary struct {
db *sql.DB
}
// New opens the database read-write (for import CLI).
func New(dbPath string) (*Dictionary, error) {
db, err := openDB(dbPath)
if err != nil {
return nil, err
}
d := &Dictionary{db: db}
if err := d.checkSeeded(); err != nil {
db.Close()
return nil, err
}
return d, nil
}
// NewReadOnly opens the database read-only with concurrent read support (for server).
func NewReadOnly(dbPath string) (*Dictionary, error) {
db, err := openReadOnlyDB(dbPath)
if err != nil {
return nil, err
}
d := &Dictionary{db: db}
if err := d.checkSeeded(); err != nil {
db.Close()
return nil, err
}
return d, nil
}
// NewFromDB creates a Dictionary from an existing *sql.DB (useful for testing).
func NewFromDB(db *sql.DB) *Dictionary {
return &Dictionary{db: db}
}
func (d *Dictionary) Close() error {
return d.db.Close()
}
func (d *Dictionary) checkSeeded() error {
var count int
err := d.db.QueryRow("SELECT COUNT(*) FROM meta").Scan(&count)
if err != nil {
return ErrNotSeeded
}
if count == 0 {
return ErrNotSeeded
}
return nil
}
func (d *Dictionary) SupportedLangs() []string {
return supportedLangs
}