woerterbuch is a small async Rust CLI for German dictionary lookups. It queries multiple German-language sources and returns either human-readable terminal output or structured JSON for Emacs, scripts, and other tools.
The project is intended to be installed from a Git clone, not published as a public Cargo package.
- Looks up German words and expressions across several sources.
- Queries selected sources concurrently.
- Provides structured JSON output as the stable integration format.
- Provides human-readable, Markdown, and Org output for quick use and notes.
- Supports filtering by source and by content section.
- Uses defensive HTML parsers, so source-specific website changes remain isolated in the corresponding modules.
Clone the repository first:
git clone https://github.com/hubisan/rust-woerterbuch
cd rust-woerterbuchTo build an optimized binary without installing it, run:
make buildThis creates the binary at:
./target/release/woerterbuchYou can run it directly from the repository:
./target/release/woerterbuch Bank --jsonTo install woerterbuch into Cargo's local binary directory, run:
make installThis uses cargo install --path . --locked --force and makes the command available outside the repository, assuming Cargo's binary directory is in your PATH:
woerterbuch Bank --jsonTo test the installation without overwriting an existing binary, run:
make install-checkwoerterbuch takes the lookup query as its main argument:
woerterbuch <QUERY>Example:
woerterbuch BankUse --sources to select which dictionary sources should be queried.
Default source order:
openthesaurus,dwds,duden,wiktionary
Supported sources:
openthesaurusdwdsdudenwiktionary
Example:
woerterbuch Bank --sources dwds,dudenNetwork lookups can be slow depending on the selected source. Duden in particular may sometimes respond slowly.
Use --sections to select which content sections should be included in the lookup result.
Common sections:
definitionssynonymsexamplesoriginidioms
Examples:
woerterbuch Bank --sections definitions,synonyms
woerterbuch Bank --sections definitions,examples,originBy default, woerterbuch prints human-readable terminal output.
Use --format to choose an output format:
woerterbuch Bank --format human
woerterbuch Bank --format json
woerterbuch Bank --format markdown
woerterbuch Bank --format org--json is kept as a backwards-compatible shortcut for --format json:
woerterbuch Bank --jsonUse --layout to choose how formatted content is grouped:
woerterbuch Bank --format markdown --layout by-source
woerterbuch Bank --format markdown --layout by-section--layout by-source groups by source first, then by entry and content section. --layout by-section groups by content section first, then by source. --layout is only supported for human, markdown, and org output; JSON always uses the source-native structure. In text-like output, idioms are rendered as their own final section; sense-level idioms keep a reference such as 1a. Human-readable, Markdown, and Org output are intended for reading and may change more freely.
Use --max-examples to limit how many examples are rendered per definition in text-like output:
woerterbuch Bank --format markdown --max-examples 2
woerterbuch Bank --format org --layout by-section --max-examples 1JSON ignores --max-examples and always returns the full source-native data.
Basic lookup:
woerterbuch BankJSON output:
woerterbuch Bank --jsonUse selected sources only:
woerterbuch Bank --sources dwds,duden
woerterbuch Bank --sources openthesaurus,wiktionaryUse selected sections only:
woerterbuch Bank --sections definitions,synonyms
woerterbuch Bank --sections definitions,examples,originShow command-line help:
woerterbuch --helpRun directly from the repository without installing:
cargo run -- Bank
cargo run -- Bank --json
cargo run -- Bank --format markdown --layout by-section
cargo run -- Bank --format markdown --max-examples 2
cargo run -- Bank --sources dwds,dudenRun the usual checks before committing:
cargo fmt
cargo test
cargo clippy --all-targets --all-features -- -D warningsBuild the release binary:
cargo build --releaseGenerate local Rust documentation:
cargo doc --no-deps --openLive HTTP smoke tests are intentionally not part of the default recommendation, because external dictionary websites can be slow, temporarily unavailable, or change their HTML. Parser tests with local fixtures are more reliable for CI.
src/
main.rs CLI, parallel source execution, output handling
models.rs JSON-native lookup data structures
http.rs reqwest client, User-Agent setup, HTML helper
format.rs human-readable, JSON, Markdown, and Org output
sources.rs source routing, timeouts, and section filtering
sources/
duden.rs Duden fetcher and parser
dwds.rs DWDS fetcher and parser
wiktionary.rs Wiktionary REST HTML fetcher and parser
openthesaurus.rs OpenThesaurus fetcher and parser
This was originally an Emacs-Lisp package. I converted it to Rust and added the possibility to use Org-mode as output format.
These Emacs-Lisp wrappers come in handy:
(cl-defun woerterbuch
(&optional query
&key
(command "woerterbuch")
(sources '("openthesaurus" "dwds" "duden" "wiktionary"))
(sections '("definitions" "examples" "synonyms" "origin" "idioms"))
(layout "by-source")
max-examples
(display-function #'pop-to-buffer))
"Lookup QUERY with woerterbuch asynchronously and display Org output.
When called interactively without prefix argument, ask for QUERY.
When called interactively with prefix argument, use word at point.
When called from Lisp with QUERY non-nil, use QUERY directly.
When called from Lisp with QUERY nil, read QUERY according to
`current-prefix-arg'."
(interactive)
(let* ((query
(or query
(if current-prefix-arg
(let ((word (thing-at-point 'word t)))
(unless (and word (not (string-empty-p word)))
(user-error "Kein Wort an Punkt gefunden"))
word)
(read-string "Wort/Redewendung: "))))
(buffer (generate-new-buffer (format "*woerterbuch: %s*" query)))
(process
(apply #'start-process
"woerterbuch"
buffer
command
(append
(list "--format" "org")
(when layout
(list "--layout" layout))
(when sources
(list "--sources" (mapconcat #'identity sources ",")))
(when sections
(list "--sections" (mapconcat #'identity sections ",")))
(when max-examples
(list "--max-examples" (number-to-string max-examples)))
(list query)))))
(message "woerterbuch: async lookup started for %S" query)
(set-process-query-on-exit-flag process nil)
(set-process-sentinel
process
(lambda (proc _event)
(when (memq (process-status proc) '(exit signal))
(when-let* ((buf (process-buffer proc)))
(with-current-buffer buf
(goto-char (point-min))
(org-mode))
(funcall display-function buf)))))))
(defun woerterbuch-all-by-source ()
"Lookup all sections by source.
Without prefix argument, ask for a word or phrase.
With prefix argument, use word at point."
(interactive)
(woerterbuch
nil
:sections '("definitions" "examples" "synonyms" "origin" "idioms")
:layout "by-source"))
(defun woerterbuch-all-by-section ()
"Lookup all sections by section.
Without prefix argument, ask for a word or phrase.
With prefix argument, use word at point."
(interactive)
(woerterbuch
nil
:sections '("definitions" "examples" "synonyms" "origin" "idioms")
:layout "by-section"))
(defun woerterbuch-synonyms-by-source ()
"Lookup synonyms by source.
Without prefix argument, ask for a word or phrase.
With prefix argument, use word at point."
(interactive)
(woerterbuch
nil
:sections '("synonyms")
:layout "by-source"))
(defun woerterbuch-definitions-by-source ()
"Lookup definitions including examples by source.
Without prefix argument, ask for a word or phrase.
With prefix argument, use word at point."
(interactive)
(woerterbuch
nil
:sections '("definitions" "examples")
:layout "by-source"))
(defun woerterbuch-origin-by-source ()
"Lookup origin by source.
Without prefix argument, ask for a word or phrase.
With prefix argument, use word at point."
(interactive)
(woerterbuch
nil
:sections '("origin")
:layout "by-source"))
(defun woerterbuch-idioms-by-source ()
"Lookup idioms by source.
Without prefix argument, ask for a word or phrase.
With prefix argument, use word at point."
(interactive)
(woerterbuch
nil
:sections '("idioms")
:layout "by-source"))This project is licensed under the GNU General Public License v3.0. See LICENSE for details.