Rewrite README for public release

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-03 09:02:26 -07:00
parent 56e073f753
commit a0cca2cb19

335
README.md
View File

@@ -1,13 +1,36 @@
# DreamDict # DreamDict
A self-contained HTTP dictionary service providing word validation, random word generation, definitions, synonyms, antonyms, translations, pronunciation, etymology, and cross-language semantic backing for English (`en`), French (`fr`), European Portuguese (`pt-PT`), and Mandarin Chinese (`zh`). A self-hosted, CGO-free HTTP dictionary service supporting English, French, **European Portuguese**, and Mandarin Chinese. Built to replace metered third-party APIs like Wordnik with something you own outright.
Runs as a plain Go binary managed by systemd. Binds to localhost only — no external network exposure. Designed as a dictionary backend for [GogoBee](https://github.com/reala-misaki/gogobee), replacing Wordnik. No API keys. No rate limits. No external runtime dependencies. Sub-millisecond response times on commodity hardware.
---
## Why DreamDict exists
Most open source dictionary tooling is either a dataset (WordNet, Wiktionary dumps, Hunspell) or a desktop application. There is no self-hosted HTTP service that combines all of them into a programmable API — so we built one.
The pt-PT distinction matters and is handled explicitly. Most open NLP resources default to Brazilian Portuguese (pt-BR) or treat both variants as interchangeable. DreamDict uses the LibreOffice pt-PT Hunspell dictionary, Dicionário Aberto, CETEMPúblico frequency data, and Wiktionary entries filtered to European Portuguese. If you are building something for a European Portuguese-speaking audience, this is the only self-hosted option that takes that seriously.
**Benchmark results (end-to-end, including HTTP and JSON serialization):**
| Endpoint | Latency | Throughput |
|---|---|---|
| GET /valid | ~20 µs | ~50,000 req/s |
| GET /random (filtered) | ~187 µs | ~5,300 req/s |
| GET /define | ~50 µs | ~20,000 req/s |
| GET /synonyms | ~37 µs | ~27,000 req/s |
| GET /translate | ~56 µs | ~18,000 req/s |
---
## Requirements ## Requirements
- Go 1.25+ - Go 1.25+
- No CGO required (uses [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite)) - No CGO required uses [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite), a pure Go SQLite port
- Single static binary, no runtime dependencies
---
## Quick Start ## Quick Start
@@ -15,7 +38,7 @@ Runs as a plain Go binary managed by systemd. Binds to localhost only — no ext
# Download all dictionary source data (~2-3 GB) # Download all dictionary source data (~2-3 GB)
./scripts/download-dict-data.sh ./scripts/download-dict-data.sh
# Build the database (~310 MB) # Build the database (~330 MB, one-time)
go run ./cmd/dictimport --data ./data --db ./dict.db go run ./cmd/dictimport --data ./data --db ./dict.db
# Start the server # Start the server
@@ -24,124 +47,149 @@ go run ./cmd/server --db ./dict.db
The server listens on `127.0.0.1:7777` by default. The server listens on `127.0.0.1:7777` by default.
---
## API ## API
All endpoints are read-only `GET` requests returning JSON. All endpoints are read-only `GET` requests returning JSON. When authentication is enabled, all endpoints except `/health` require a bearer token (see [Authentication](#authentication)).
### `GET /valid?word=...&lang=...` ### `GET /valid`
Check if a word exists in a language's word list. Includes inflected forms via affix expansion. Check if a word exists in a language's word list. Includes inflected forms via affix expansion`running` and `ran` resolve correctly, not just base forms.
``` ```bash
$ curl 'localhost:7777/valid?word=running&lang=en' curl 'localhost:7777/valid?word=running&lang=en'
{"valid":true} {"valid":true}
``` ```
### `GET /random?lang=...` ### `GET /random`
Return a random word. Optional filters: `pos` (`noun`, `verb`, `adjective`, `adverb`), `min`/`max` (word length), `min_freq` (frequency score), `min_difficulty`/`max_difficulty` (0.01.0). Return a random word. Optional filters: `pos` (`noun`, `verb`, `adjective`, `adverb`), `min`/`max` (word length), `min_freq` (frequency score), `min_difficulty`/`max_difficulty` (0.01.0).
``` ```bash
$ curl 'localhost:7777/random?lang=en&pos=noun&min=5&max=8' curl 'localhost:7777/random?lang=en&pos=noun&min=5&max=8&max_difficulty=0.5'
{"word":"lantern","difficulty":0.42} {"word":"lantern","difficulty":0.42}
``` ```
### `GET /define?word=...&lang=...` ### `GET /define`
Return all definitions, ordered by source priority. Return all definitions ordered by source priority. Curated sources (WordNet, WOLF, Dicionário Aberto) appear before Wiktionary supplemental definitions.
``` ```bash
$ curl 'localhost:7777/define?word=ephemeral&lang=en' curl 'localhost:7777/define?word=ephemeral&lang=en'
{"word":"ephemeral","lang":"en","definitions":[ {
"word": "ephemeral",
"lang": "en",
"definitions": [
{"pos":"adjective","gloss":"lasting for a very short time","source":"wordnet","priority":10}, {"pos":"adjective","gloss":"lasting for a very short time","source":"wordnet","priority":10},
{"pos":"adjective","gloss":"lasting only for a day","source":"wiktionary","priority":20} {"pos":"adjective","gloss":"lasting only for a day","source":"wiktionary","priority":20}
]} ]
}
``` ```
### `GET /synonyms?word=...&lang=...` ### `GET /synonyms`
Return deduplicated synonyms. Return deduplicated synonyms sourced from WordNet, WOLF, and Wiktionary.
``` ```bash
$ curl 'localhost:7777/synonyms?word=happy&lang=en' curl 'localhost:7777/synonyms?word=happy&lang=en'
{"word":"happy","lang":"en","synonyms":["content","glad","joyful","pleased"]} {"word":"happy","lang":"en","synonyms":["content","glad","joyful","pleased"]}
``` ```
### `GET /antonyms?word=...&lang=...` ### `GET /antonyms`
Return antonyms sourced from WordNet and Wiktionary. Return antonyms sourced from WordNet and Wiktionary.
``` ```bash
$ curl 'localhost:7777/antonyms?word=happy&lang=en' curl 'localhost:7777/antonyms?word=happy&lang=en'
{"word":"happy","lang":"en","antonyms":["sad","unhappy"]} {"word":"happy","lang":"en","antonyms":["sad","unhappy"]}
``` ```
### `GET /translate?word=...&from=...&to=...` ### `GET /translate`
Return known translations between supported languages. Return known translations between supported languages.
``` ```bash
$ curl 'localhost:7777/translate?word=cat&from=en&to=fr' curl 'localhost:7777/translate?word=cat&from=en&to=fr'
{"word":"cat","from":"en","to":"fr","translations":["chat","minet"]} {"word":"cat","from":"en","to":"fr","translations":["chat","minet"]}
``` ```
### `GET /backing?word=...&lang=...` ### `GET /backing`
Return English semantic equivalents for a non-English word via shared WordNet synset IDs. Falls back to the translations table when no synset mapping exists. Return English semantic equivalents for a non-English word via shared Princeton WordNet synset IDs. This is semantic equivalence — not translation guesswork. Falls back to the translations table when no synset mapping exists.
``` ```bash
$ curl 'localhost:7777/backing?word=éphémère&lang=fr' curl 'localhost:7777/backing?word=éphémère&lang=fr'
{"word":"éphémère","lang":"fr","equivalents":[ {
"word": "éphémère",
"lang": "fr",
"equivalents": [
{"word":"ephemeral","definition":"lasting a very short time","synset":"00914031-a"} {"word":"ephemeral","definition":"lasting a very short time","synset":"00914031-a"}
]} ]
}
``` ```
### `GET /pronunciation?word=...&lang=...` ### `GET /pronunciation`
Return pronunciation data (CMU phonemes for English, IPA from Wiktionary for all languages). Return pronunciation data. English gets CMU phonemes and IPA. All other languages get IPA from Wiktionary where available.
``` ```bash
$ curl 'localhost:7777/pronunciation?word=ephemeral&lang=en' curl 'localhost:7777/pronunciation?word=ephemeral&lang=en'
{"word":"ephemeral","lang":"en","pronunciations":[ {
"word": "ephemeral",
"lang": "en",
"pronunciations": [
{"format":"cmu","value":"IH0 F EH1 M ER0 AH0 L","source":"cmudict"}, {"format":"cmu","value":"IH0 F EH1 M ER0 AH0 L","source":"cmudict"},
{"format":"ipa","value":"ɪˈfɛm.ər.əl","source":"wiktionary"} {"format":"ipa","value":"ɪˈfɛm.ər.əl","source":"wiktionary"}
]} ]
}
``` ```
### `GET /etymology?word=...&lang=...` ### `GET /etymology`
Return the etymology of a word from Wiktionary. Return the etymology of a word from Wiktionary. Returned as free-form text — Wiktionary etymology is not structured consistently enough to parse reliably.
``` ```bash
$ curl 'localhost:7777/etymology?word=ephemeral&lang=en' curl 'localhost:7777/etymology?word=ephemeral&lang=en'
{"word":"ephemeral","lang":"en","etymology":"From Medieval Latin ephemerus, from Ancient Greek ἐφήμερος (ephḗmeros, \"lasting only a day\")..."} {
"word": "ephemeral",
"lang": "en",
"etymology": "From Medieval Latin ephemerus, from Ancient Greek ἐφήμερος (ephḗmeros, \"lasting only a day\")..."
}
``` ```
### `GET /frequency?word=...&lang=...` ### `GET /frequency`
Return the frequency score for a word. Higher values = more common. Return the frequency score for a word. Higher values = more common. Sourced from SUBTLEX-US (en), Lexique (fr), and CETEMPúblico (pt-PT).
``` ```bash
$ curl 'localhost:7777/frequency?word=house&lang=en' curl 'localhost:7777/frequency?word=house&lang=en'
{"word":"house","lang":"en","frequency":800} {"word":"house","lang":"en","frequency":800}
``` ```
### `GET /frequency/batch?words=...&lang=...` ### `GET /frequency/batch`
Batch frequency lookup. Comma-separated words, max 100. Batch frequency lookup. Comma-separated words, max 100 per request.
``` ```bash
$ curl 'localhost:7777/frequency/batch?words=house,cat,ephemeral&lang=en' curl 'localhost:7777/frequency/batch?words=house,cat,ephemeral&lang=en'
{"lang":"en","frequencies":{"cat":800,"ephemeral":50,"house":800}} {"lang":"en","frequencies":{"cat":800,"ephemeral":50,"house":800}}
``` ```
### `GET /health` ### `GET /health`
Health check with database stats. Health check with database statistics. Always unauthenticated — safe for monitoring tools.
``` ```bash
$ curl localhost:7777/health curl localhost:7777/health
{"status":"ok","db_path":"./dict.db","word_counts":{"en":214832,"fr":82104,"pt-PT":71088},...} {
"status": "ok",
"db_path": "./dict.db",
"word_counts": {"en":214832,"fr":82104,"pt-PT":71088,"zh":120000},
"def_counts": {"en":380000,"fr":200000,"pt-PT":120000,"zh":80000},
"imported_at": "2026-03-28T14:22:00Z",
"schema_version": "2"
}
``` ```
### Error Responses ### Error Responses
@@ -149,42 +197,38 @@ $ curl localhost:7777/health
```json ```json
{"error": "bad_request", "message": "word and lang are required"} {"error": "bad_request", "message": "word and lang are required"}
{"error": "no_match", "message": "no words match the given filters"} {"error": "no_match", "message": "no words match the given filters"}
{"error": "unauthorized", "message": "invalid or missing bearer token"}
``` ```
## Server Configuration ---
## Configuration
| Flag | Env Var | Default | Description | | Flag | Env Var | Default | Description |
|---|---|---|---| |---|---|---|---|
| `--port` | `DREAMDICT_PORT` | `7777` | Listen port | | `--port` | `DREAMDICT_PORT` | `7777` | Listen port |
| `--host` | `DREAMDICT_HOST` | `127.0.0.1` | Listen host | | `--host` | `DREAMDICT_HOST` | `127.0.0.1` | Listen host (localhost only by default) |
| `--db` | `DREAMDICT_DB` | `./dict.db` | Path to SQLite database | | `--db` | `DREAMDICT_DB` | `./dict.db` | Path to SQLite database |
| `--token` | `DREAMDICT_TOKEN` | _(none)_ | Bearer token for API auth | | `--token` | `DREAMDICT_TOKEN` | _(none)_ | Bearer token for API authentication |
| `--log-level` | | `info` | `debug`, `info`, `warn`, `error` | | `--log-level` | | `info` | `debug`, `info`, `warn`, `error` |
Flags take precedence over environment variables. Flags take precedence over environment variables.
### Authentication ### Authentication
When `--token` or `DREAMDICT_TOKEN` is set, all endpoints except `/health` require a bearer token: When `--token` or `DREAMDICT_TOKEN` is set, all endpoints except `/health` require a bearer token. When no token is configured, authentication is disabled.
```bash ```bash
# Start with auth enabled # Start with auth enabled
DREAMDICT_TOKEN=mysecret go run ./cmd/server --db ./dict.db DREAMDICT_TOKEN=your-secret-token go run ./cmd/server --db ./dict.db
# Authenticated request # Authenticated request
curl -H 'Authorization: Bearer mysecret' 'localhost:7777/define?word=casa&lang=pt-PT' curl -H 'Authorization: Bearer your-secret-token' 'localhost:7777/define?word=saudade&lang=pt-PT'
# Health is always public (for monitoring)
curl localhost:7777/health
``` ```
Requests without a valid token receive a `401` response: Requests without a valid token receive a `401` response. The error message does not distinguish between a missing token and an incorrect one.
```json ---
{"error": "unauthorized", "message": "invalid or missing bearer token"}
```
When no token is configured, auth is disabled and all endpoints are open.
## Import CLI ## Import CLI
@@ -197,102 +241,121 @@ go run ./cmd/dictimport [flags]
| `--db` | `./dict.db` | Output database path | | `--db` | `./dict.db` | Output database path |
| `--data` | `./data` | Source data directory | | `--data` | `./data` | Source data directory |
| `--langs` | `en,fr,pt-PT,zh` | Languages to import | | `--langs` | `en,fr,pt-PT,zh` | Languages to import |
| `--clean` | `false` | Drop and recreate all tables first | | `--clean` | `false` | Drop and recreate all tables before import |
| `--skip` | | Comma-separated loader names to skip | | `--skip` | | Comma-separated loader names to skip |
### Loader Names ### Loaders
| Loader | Language | What it does | | Loader | Language | What it provides |
|---|---|---| |---|---|---|
| `scowl` | en | English word list with frequency tiers | | `scowl` | en | Word list with frequency tiers |
| `affix-en` | en | English inflected form expansion | | `affix-en` | en | Inflected form expansion |
| `wordnet` | en | Definitions, synonyms, antonyms, synset mappings | | `wordnet` | en | Definitions, synonyms, antonyms, synset IDs |
| `subtlex` | en | SUBTLEX-US frequency enrichment | | `subtlex` | en | SUBTLEX-US subtitle frequency data |
| `cmudict` | en | CMU pronunciation data | | `cmudict` | en | CMU phoneme pronunciation data |
| `wiktionary-en` | en | Supplemental definitions, synonyms, antonyms, translations, IPA, etymology | | `wiktionary-en` | en | Supplemental definitions, synonyms, antonyms, translations, IPA, etymology |
| `hunspell-fr` | fr | French word list | | `hunspell-fr` | fr | Word list |
| `affix-fr` | fr | French inflected form expansion | | `affix-fr` | fr | Inflected form expansion |
| `lexique` | fr | French POS + frequency enrichment | | `lexique` | fr | POS and frequency enrichment |
| `wolf` | fr | French definitions, synonyms, synset mappings | | `wolf` | fr | Definitions, synonyms, synset IDs |
| `wiktionary-fr` | fr | Supplemental definitions, synonyms, antonyms, translations, IPA, etymology | | `wiktionary-fr` | fr | Supplemental definitions, synonyms, antonyms, translations, IPA, etymology |
| `hunspell-pt` | pt-PT | Portuguese word list | | `hunspell-pt` | pt-PT | European Portuguese word list (not pt-BR) |
| `affix-pt-PT` | pt-PT | Portuguese inflected form expansion | | `affix-pt-PT` | pt-PT | Inflected form expansion |
| `dicionario` | pt-PT | Portuguese definitions | | `dicionario` | pt-PT | Definitions from Dicionário Aberto |
| `cetempublico` | pt-PT | Portuguese frequency enrichment | | `cetempublico` | pt-PT | Frequency data from CETEMPúblico corpus |
| `omw-pt` | pt-PT | Open Multilingual Wordnet synset mappings | | `omw-pt` | pt-PT | Open Multilingual Wordnet synset mappings |
| `wiktionary-pt` | pt-PT | Supplemental definitions, synonyms, antonyms, translations, IPA, etymology | | `wiktionary-pt` | pt-PT | Supplemental definitions, synonyms, antonyms, translations, IPA, etymology (filtered to European Portuguese entries) |
| `cedict` | zh | Chinese words, definitions, translations | | `cedict` | zh | Words, definitions, translations via CC-CEDICT |
| `wiktionary-zh` | zh | Supplemental Chinese data | | `wiktionary-zh` | zh | Supplemental Chinese data |
| `difficulty` | all | Composite difficulty scoring (runs last) | | `difficulty` | all | Composite difficulty scoring runs last |
---
## Data Sources ## Data Sources
| Source | Language | What it provides | | Source | Language | What it provides |
|---|---|---| |---|---|---|
| [SCOWL](https://wordlist.aspell.net/) | en | Word list + frequency tiers | | [SCOWL](https://wordlist.aspell.net/) | en | Word list + frequency tiers |
| [WordNet](https://wordnet.princeton.edu/) | en | Definitions, synonyms, antonyms, synset IDs | | [WordNet 3.1](https://wordnet.princeton.edu/) | en | Definitions, synonyms, antonyms, synset IDs |
| [SUBTLEX-US](http://www.ugent.be/pp/experimentele-psychologie/en/research/documents/subtlexus) | en | Subtitle-based word frequency | | [SUBTLEX-US](http://www.ugent.be/pp/experimentele-psychologie/en/research/documents/subtlexus) | en | Subtitle-based word frequency |
| [CMU Pronouncing Dictionary](http://www.speech.cs.cmu.edu/cgi-bin/cmudict) | en | Phoneme-based pronunciation | | [CMU Pronouncing Dictionary](http://www.speech.cs.cmu.edu/cgi-bin/cmudict) | en | Phoneme pronunciation |
| [Hunspell/LibreOffice](https://github.com/LibreOffice/dictionaries) | fr, pt-PT | Word lists + affix rules | | [Hunspell/LibreOffice](https://github.com/LibreOffice/dictionaries) | fr, pt-PT | Word lists + affix rules |
| [Lexique](http://www.lexique.org/) | fr | POS + frequency enrichment | | [Lexique 3.83](http://www.lexique.org/) | fr | POS + frequency enrichment |
| [WOLF](https://github.com/nicolashernandez/WOLF) | fr | Definitions, synonyms, synset IDs | | [WOLF](https://github.com/nicolashernandez/WOLF) | fr | Definitions, synonyms, synset IDs |
| [Dicionario Aberto](https://github.com/jorgecardoso/dicionario-aberto) | pt-PT | Definitions | | [Dicionário Aberto](https://github.com/jorgecardoso/dicionario-aberto) | pt-PT | Definitions |
| [CETEMPúblico](https://www.linguateca.pt/acesso/corpus.php?corpus=CETEMPUBLICO) | pt-PT | Word frequency | | [CETEMPúblico](https://www.linguateca.pt/acesso/corpus.php?corpus=CETEMPUBLICO) | pt-PT | European Portuguese word frequency |
| [Open Multilingual Wordnet](https://github.com/omwn/omw-data) | pt-PT | Synset mappings to Princeton WordNet | | [Open Multilingual Wordnet](https://github.com/omwn/omw-data) | pt-PT | Synset mappings to Princeton WordNet |
| [CC-CEDICT](https://cc-cedict.org/) | zh | Words, definitions, translations | | [CC-CEDICT](https://cc-cedict.org/) | zh | Words, definitions, translations |
| [Wiktionary (kaikki.org)](https://kaikki.org/) | all | Definitions, synonyms, antonyms, translations, IPA pronunciation, etymology | | [Wiktionary via kaikki.org](https://kaikki.org/) | all | Definitions, synonyms, antonyms, translations, IPA, etymology |
Run `./scripts/download-dict-data.sh` to fetch everything. Pass `--force` to re-download existing files. Run `./scripts/download-dict-data.sh` to fetch all sources automatically. Pass `--force` to re-download existing files.
## Database Schema (v2) ---
### Tables ## Difficulty Scoring
| Table | Purpose | Each word receives a composite difficulty score (0.0 = easiest, 1.0 = hardest) computed at import time from frequency, word length, and syllable count:
|---|---|
| `words` | Core word inventory (word, lang, pos, frequency, difficulty) |
| `definitions` | Word definitions with source priority |
| `synonyms` | Synonym relationships |
| `antonyms` | Antonym relationships |
| `translations` | Cross-language translations |
| `synsets` | Princeton WordNet synset IDs |
| `word_synsets` | Maps words to synsets (for cross-language semantic backing) |
| `pronunciations` | CMU phonemes and IPA data |
| `etymology` | Word origin text from Wiktionary |
| `meta` | Import metadata (imported_at, schema_version) |
### Difficulty Scoring
Each word receives a composite difficulty score (0.0 = easiest, 1.0 = hardest) computed at import time:
``` ```
difficulty = inverse_frequency * 0.5 + word_length * 0.3 + syllable_count * 0.2 difficulty = inverse_frequency * 0.5 + word_length * 0.3 + syllable_count * 0.2
``` ```
Use `min_difficulty` / `max_difficulty` on `/random` to select words by difficulty tier. Use `min_difficulty` / `max_difficulty` on `/random` to select words by difficulty tier without maintaining hardcoded word lists.
---
## Database Schema
Schema version 2. Full schema in `internal/dictionary/db.go`.
| Table | Purpose |
|---|---|
| `words` | Core word inventory: word, lang, pos, frequency, difficulty |
| `definitions` | Definitions with source and priority |
| `synonyms` | Synonym relationships |
| `antonyms` | Antonym relationships |
| `translations` | Cross-language translations |
| `synsets` | Princeton WordNet synset IDs |
| `word_synsets` | Word-to-synset mappings for cross-language semantic backing |
| `pronunciations` | CMU phonemes and IPA |
| `etymology` | Word origin text from Wiktionary |
| `meta` | Import metadata: schema version, import timestamp, word/definition counts |
---
## Deployment ## Deployment
A systemd unit file is provided at `deploy/dreamdict.service`. A systemd unit file is provided at `deploy/dreamdict.service`. The service runs as a dedicated unprivileged system user and binds to localhost only by default.
```bash ```bash
# Create system user # Create dedicated system user
useradd --system --no-create-home --shell /usr/sbin/nologin dreamdict useradd --system --no-create-home --shell /usr/sbin/nologin dreamdict
# Build and install # Build and install binary
go build -o /usr/local/bin/dreamdict ./cmd/server go build -o /usr/local/bin/dreamdict ./cmd/server
chmod 755 /usr/local/bin/dreamdict
# Build the database (run as yourself, not the dreamdict user)
mkdir -p /opt/dreamdict /etc/dreamdict mkdir -p /opt/dreamdict /etc/dreamdict
go run ./cmd/dictimport --db /opt/dreamdict/dict.db --data ./data go run ./cmd/dictimport --db /opt/dreamdict/dict.db --data ./data
chown -R dreamdict:dreamdict /opt/dreamdict chown -R dreamdict:dreamdict /opt/dreamdict
# Install service # Configure
cp deploy/dreamdict.service /etc/systemd/system/ cat > /etc/dreamdict/dreamdict.env <<EOF
echo 'DREAMDICT_PORT=7777' > /etc/dreamdict/dreamdict.env DREAMDICT_PORT=7777
echo 'DREAMDICT_DB=/opt/dreamdict/dict.db' >> /etc/dreamdict/dreamdict.env DREAMDICT_DB=/opt/dreamdict/dict.db
echo 'DREAMDICT_TOKEN=changeme' >> /etc/dreamdict/dreamdict.env DREAMDICT_TOKEN=changeme
EOF
chmod 600 /etc/dreamdict/dreamdict.env chmod 600 /etc/dreamdict/dreamdict.env
# Install and start
cp deploy/dreamdict.service /etc/systemd/system/
systemctl daemon-reload systemctl daemon-reload
systemctl enable --now dreamdict systemctl enable --now dreamdict
# Verify
systemctl status dreamdict
curl localhost:7777/health
``` ```
### Updating the Database ### Updating the Database
@@ -304,24 +367,30 @@ chown dreamdict:dreamdict /opt/dreamdict/dict.db
systemctl start dreamdict systemctl start dreamdict
``` ```
---
## Testing ## Testing
```bash ```bash
go test ./... go test ./...
``` ```
---
## Project Structure ## Project Structure
``` ```
cmd/server/ HTTP server (12 endpoints) cmd/server/ HTTP server binary
cmd/dictimport/ One-time data import CLI cmd/dictimport/ One-time data import CLI
internal/dictionary/ Core package: DB schema, types, queries internal/dictionary/ Core package: schema, types, queries
internal/loader/ Data source parsers (one per source) internal/loader/ One loader per data source
scripts/ Download script for source data scripts/ Data download script
deploy/ systemd unit file deploy/ systemd unit file
data/ Source data files (gitignored) data/ Source data files (gitignored)
``` ```
---
## Expected Database Size ## Expected Database Size
| Language | Words | Definitions | Size | | Language | Words | Definitions | Size |
@@ -332,4 +401,10 @@ data/ Source data files (gitignored)
| zh | varies | ~80k | ~20 MB | | zh | varies | ~80k | ~20 MB |
| **Total** | | | **~330 MB** | | **Total** | | | **~330 MB** |
Word counts are higher than base forms due to affix expansion of inflected forms. Word counts exceed base form counts due to affix expansion of inflected forms.
---
## License
MIT