english
english is a fast, lightweight English inflection library written in Rust. Total bundled data is about 1 MB. It provides highly accurate verb conjugation and noun/adjective/adverb declension from processed Wiktionary data, making it useful for real-time procedural text generation.
⚡ Speed and Accuracy
Evaluation of the English inflector (cargo xtask accuracy, using the
2026-07-02 Wiktionary dump) and performance benchmarking
(examples/speedmark.rs; measured rows averaged over 10 release runs) shows:
| Part of Speech | Correct / Total | Accuracy | Variant Gap | Throughput (calls/sec) | Time per Call |
|---|---|---|---|---|---|
| Nouns | 225900 / 225900 | 100.00% | 0 | 6,072,834 | 164.67 ns |
| Verbs | 151544 / 151544 | 100.00% | 1 | 9,549,311 | 104.73 ns |
| Adjectives | 121548 / 121550 | 99.998% | 8 | 6,859,637 | 145.79 ns |
| Adverbs | 25123 / 25125 | 99.99% | 2 | 11,144,625 | 89.73 ns |
The accuracy percentages measure recall through any published key: the share
of plain attested Wiktionary slots reproducible via the bare lemma or any
_n sense key. They do not measure precision, nor whether the natural bare-lemma
call returns the primary/most-standard attested form.
For that natural-call view, cargo xtask accuracy also reports bare-lemma
correctness:
| Part of Speech | Bare Primary / Total | Bare Accuracy | Standard Form Demoted to _n |
Over-generated |
|---|---|---|---|---|
| Nouns | 223505 / 225900 | 98.94% | 1655 | 740 |
| Verbs | 150787 / 151544 | 99.50% | 626 | 131 |
| Adjectives | 120924 / 121550 | 99.48% | 624 | 0 |
| Adverbs | 24882 / 25125 | 99.03% | 241 | 0 |
Benchmarks are machine- and workload-dependent; run cargo run -p english --example speedmark --release on your target platform for local numbers.
Breaking change in 0.4: spelling cleanup
The possessive-determiner enum variant is now Case::PersonalPossessive, and the
last-occurrence string helper is now EnglishCore::replace_last_occurrence.
Breaking change in 0.3: underscore sense-key format
Sense-numbered keys now use the canonical underscore format (die_2, lie_2).
The pre-0.3 adjacent-digit spelling (die2, lie2) was removed because it is
ambiguous with ordinary digit-bearing words such as mp3, F16, and F2, which
are now always treated opaquely unless they are exact table keys.
📦 Installation
Then in your code:
use *;
For a more involved but still minimal example of building a small domain layer on
top of english, see examples/semantic_triples.rs:
It shows custom noun/verb/adj/adv types, semantic triples, perspective-sensitive rendering, modifiers, complements, adjuncts, and agreement-driven pronoun and tense shifts.
Case handling
english accepts lowercase lemmas, but the public API also has a simple casing
convenience for common sentence text:
use ;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
Title-case and ALL-CAPS words may use lowercase table rows and then restore the
input style. Mixed case is deliberately not guessed, so proper-name-like tokens
such as McDonald fall through to the regular rule on the original spelling.
This is not semantic proper-noun or acronym detection: names, brands,
initialisms, and house-style casing may need caller-side normalization.
Helper limitations
countandcount_with_numberare small conveniences foru32counts. Exactly1is singular; every other value is plural. Decimal, negative, formatted, or localized quantities are caller responsibilities.English::add_possessiveuses a simple trailing-srule:dogs', but alsobus'andJames'. Apply your own style guide if you preferbus'sorJames's.
🔧 Crate Overview
english
The public API for verb conjugation and noun/adjective/adverb declension.
- Combines optimized data generated from
extractorwith inflection logic fromenglish-core. - Pure Rust; one third-party dependency (
phf) plus the first-partyenglish-core. - PHF-backed irregular lookups with regular-rule fallback.
- Code generation ensures no runtime penalty.
english-core
The compact fallback / prediction engine for English inflection.
- Implements small rule approximations for conjugation/declension.
- Used by the extractor to classify forms as regular or irregular.
- Has no data dependency — logic-only.
- Can be used standalone for a smaller footprint, but is not guaranteed correct
for arbitrary out-of-vocabulary words; use
englishfor the table-backed API.
extractor
A tool to process and refine Wiktionary data.
- Parses large English Wiktionary dumps.
- Extracts verb, noun, adjective, and adverb forms.
- Uses
english-coreto filter out regular forms, preserving only irregulars. - Numbers homograph senses deterministically by a pure sort of their emitted forms (no lockfile, no identity, no human review — see below).
- Emits every plain attested variant as its own sense-numbered key (
cactus_2→ cacti,cactus_3→ …), numbered in form-signature order. - Generates the static PHF tables used in
english. cargo xtask accuracymeasures both any-key reachability and bare-lemma primary correctness; run it before and after any rule or table change.
📦 Obtaining Wiktionary Data & Running the Extractor
This project relies on raw data extracted from Wiktionary. Current version built with data from 2026-07-02.
Steps
- Download the raw Wiktextract JSONL dump (~20 GB) from Kaikki.org.
- Place the file somewhere accessible (e.g.
../rawwiki.jsonl). - From the repository root, run:
cargo xtask refresh-data --dump ../rawwiki.jsonl. - The generated Rust tables are written to
crates/english/generated; intermediate CSV/JSONL artifacts todata/intermediate. - Review
git diff crates/english/generated/, then runcargo xtask check-registrybefore committing.
After committing regenerated tables, run cargo xtask accuracy to score them
against the dump. It measures the currently compiled committed tables, and needs
either the cached data/intermediate/english_filtered.jsonl or an explicit
--dump /path/to/raw-wiktextract.jsonl.
Adverb degree
Adverbs use the same two-tier design as the other parts of speech (table first, rule fallback) but a different rule from adjectives:
- Adjectives have a linguistically informed rule — short words take suffixal
-er/-est(fast → faster), longer words go periphrastic (beautiful → more beautiful). - Adverbs have a deliberately conservative, unconditional periphrastic
rule:
EnglishCore::comparative_adverb("quickly") == "more quickly". On the dump, 99.1% of gradable adverbs takemore/most, and a suffixal guess would be wrong far more often than right (quicklier,abruptlier, ...).
The small closed set of adverbs that inflect otherwise is table-driven, exactly like irregular nouns/verbs:
- flat adverbs (homographs of their adjective):
fast → faster,hard → harder,early → earlier,late → later; - suppletives:
well → better,badly → worse,far → farther(withfar_2 → further); - locational/directional adverbs inflect with
farther/further:downhill → farther downhill,east → farther east.
Where an adverb attests both the periphrastic and a single-word form, the
periphrastic wins the bare key (so quickly → more quickly) and the single-word
form is a numbered key (quickly_2 → quicklier). This is correct for the large
-ly class; the handful of flat adverbs that also list more X (e.g. deep) get
the periphrastic on the bare key and the suffixal at deep_2 → deeper — both stay
reachable, so any-key accuracy is unaffected.
Accuracy: 25123 / 25125 slots (99.99%); the two residuals are malformed
Wiktionary forms (more ... humouredly / most ... humouredly). See
English::adverb and EnglishCore::adverb for the API.
Deterministic sense numbering
Homographs that inflect differently share a lemma and are disambiguated by a
numeric suffix (lie → lay, lie_2 → lied; die_2 → dice). The suffix is
assigned by a pure, transparent sort of the forms — no lockfile, no frozen
identity, no human-review workflow.
For each (lemma, part of speech) the extractor:
- gathers every plain attested inflection pattern and drops the one the regular rule engine already produces (so the rule serves it at runtime);
- sorts the survivors by emitted form signature (standard senses before slang/soft ones, then lexicographically);
- hands out suffixes: if a regular form was dropped, the bare key is reserved
for the rule engine and numbering starts at
_2; otherwise the first-sorted survivor takes the bare lemma. The rest number upward (_2,_3, …).
Because the key is a function of the forms alone, reordering the dump's entries
can never change the output — generation is reproducible. cargo xtask check-registry is a dump-free consistency gate: it verifies the committed tables
are well-formed, have unique and correctly shaped keys, no empty columns, and
preserve rule/table layering. It deliberately does not verify that a row's
irregular values are correct — those are attested data, not derivable without the
dump. cargo xtask accuracy (with the dump) is the authoritative value check.
Stability guarantee — "fairly stable", not frozen. Keys are deterministic but
not immutable. If Wiktionary adds, removes, or edits a lemma's attested forms,
the sort can renumber that lemma's _<n> keys — a lexicographically earlier new
variant deliberately shifts later ones up. Do not persist _n keys as stable
semantic IDs across data refreshes. What stays true is that the set of forms a
lemma exposes is reachable through some key, and that a slang-only sense never
takes the bare key from a standard one. Review the crates/english/generated/
diff on a refresh as you would any regenerated artifact.
Current runtime lookup is permissive for compatibility: a _<digits> suffix is
stripped when it resolves to a real table key or to a tabled base lemma. That
means a nonsensical key like child_999 may behave like child rather than being
reported invalid. If your application needs strict key validation, keep your own
allow-list from the versioned generated tables until a strict public API exists.
Benchmarks
Performance benchmarks were run on an M2 MacBook.
Benchmarking this kind of project requires opinionated decisions: many words have alternative inflections, Wiktionary data is imperfect, and countability tags can be inconsistent. Treat bundled numbers as a baseline, take them with a grain of salt, and benchmark your own use cases. Suggestions to improve benchmarking are welcome.
Disclaimer
Wiktionary data is unstable and subject to upstream changes. The generated lookup
tables in crates/english/generated/*_phf.rs are the source of truth for a given
revision. Sense-numbered keys (lie_2, die_2, …) are deterministic for a dump
but may be renumbered when upstream forms change — see
Deterministic sense numbering.
Inspirations and Thanks
- Ole in the Bevy Discord suggested
phfinstead of sorted arrays, which resulted in up to 40% speedups. - https://github.com/atteo/evo-inflector
- https://github.com/plurals/pluralize
📄 License
-
Code: Dual licensed under MIT and Apache © 2024 gold-silver-copper
-
Data: Wiktionary content is dual-licensed under