German-language readability indices — Wiener Sachtextformel (WSTF 1–4), Flesch-Reading-Ease (Amstad) and LIX (Björnsson) — computed from a single shared tokenization pass, with zero runtime dependencies.
dotnet add package Readability.Germanusing Readability.German;
ReadabilityReport report = GermanReadability.Analyze(
"Die Sonne schien den ganzen Tag über dem kleinen Dorf.");
Console.WriteLine(report.Wstf.Average); // Wiener Sachtextformel — school-grade level, 4–15+
Console.WriteLine(report.FleschReadingEase); // Flesch-Reading-Ease (Amstad) — 0–100, higher = easier
Console.WriteLine(report.Lix); // LIX (Björnsson) — unbounded, higher = harder
Console.WriteLine(WstfInterpretation.DescribeGerman(report.Wstf.Average));
Console.WriteLine(FleschInterpretation.DescribeGerman(report.FleschReadingEase));
Console.WriteLine(LixInterpretation.DescribeGerman(report.Lix));report.IsEmpty is true when the input contained no words; every metric and score is 0 rather
than NaN/Infinity in that case. text == null throws ArgumentNullException.
Direction is the most important column below — Flesch runs opposite to the other two.
| Index | Formula | Scale | Direction | Bands |
|---|---|---|---|---|
WSTF (report.Wstf.Average) |
0.1935·MS + 0.1672·SL + 0.1297·IW − 0.0327·ES − 0.875 (and 3 more variants, see below) |
~4–15 (Austrian/German school grade) | higher = harder | 4–6 very easy · 6–8 easy · 8–10 good · 10–12 moderately difficult · 12–14 difficult · 14+ very difficult |
Flesch-Reading-Ease (Amstad) (report.FleschReadingEase) |
180 − ASL − 58.5·ASW |
nominally 0–100 | higher = easier (inverted!) | 90–100 sehr leicht · 80–90 leicht · 70–80 mittelleicht · 60–70 mittel · 50–60 mittelschwer · 30–50 schwer · 0–30 sehr schwer |
LIX (Björnsson) (report.Lix) |
ASL + IW (IW = % words with more than 6 letters) |
unbounded, typically 20–70 | higher = harder | <30 very easy · 30–40 easy · 40–50 medium · 50–60 difficult · 60+ very difficult |
Where SL/ASL = mean sentence length in words, ASW = mean syllables per word, MS = % words
with ≥3 syllables, ES = % words with exactly 1 syllable, IW = % long words (definition varies
by index — see below).
GermanReadability.Analyze computes all three from one tokenization pass. WstfResult.Average is
the mean of the four WSTF variants only — a valid average, since all four share one scale and
direction. There is no cross-index average — see Advanced usage below.
If you want to inspect the shared statistics, or compute only some of the indices, tokenize once
and pass the result to each calculator directly instead of calling Analyze:
using Readability.German;
using Readability.German.Indices;
TextStatistics stats = TextAnalyzer.GetStatistics(text);
WstfResult wstf = WienerSachtextformel.Compute(stats);
double flesch = FleschReadingEase.Compute(stats);
double lix = Lix.Compute(stats);This is exactly what GermanReadability.Analyze does internally — useful when you need
stats.SentenceCount, stats.SyllableCount, etc. alongside the scores, or when you only need one
index and want to skip computing the others.
The built-in GermanSyllableCounter is a vowel-group heuristic (see
Accuracy & limitations). Supply your own ISyllableCounter — e.g. a
dictionary/hyphenation-based one — via TokenizerOptions/ReadabilityOptions:
var options = new ReadabilityOptions { SyllableCounter = new MyDictionaryCounter() };
ReadabilityReport report = GermanReadability.Analyze(text, options);ISyllableCounter implementations must be thread-safe and pure — Readability.German never
mutates shared state across calls.
You can also swap the abbreviation list used for sentence splitting (Abbreviations), and toggle
clamping to each index's nominal display range (ClampScores, off by default — see
Clamping).
Sources disagree on the Wiener Sachtextformel's IW (long-word) threshold: Bamberger/Vanecek and
Wikipedia define it as more than six letters (≥7), but several online calculators use six or
more (≥6). LIX's definition, by contrast, is fixed at more than six letters — that's Björnsson's
published definition, and changing it means the output is no longer LIX.
Because these two definitions are not interchangeable across indices, TextStatistics exposes
both counts (WordsWithMoreThanSixLetters, WordsWithSixOrMoreLetters), computed in the same
pass. LIX always uses WordsWithMoreThanSixLetters and is not configurable. WSTF defaults to
the same (LongWordRule.MoreThanSix) but can be switched to LongWordRule.SixOrMore:
var options = new ReadabilityOptions { WstfLongWordRule = LongWordRule.SixOrMore };This option deliberately lives on ReadabilityOptions, not the shared TokenizerOptions — a
global "long word" setting would silently corrupt LIX output the moment someone tuned it for WSTF
parity.
By default, no index is clamped to its nominal display range — a short or unusual text can
legitimately produce a WSTF below 4, a Flesch score above 100 or below 0, or a very low LIX.
Clamping by default would silently hide those signals. Pass ClampScores = true on
ReadabilityOptions to clamp WSTF to [4, 15] and Flesch to [0, 100]; LIX is never clamped — it
has no hard upper bound by design.
- Syllable counting is a heuristic, not a dictionary.
GermanSyllableCountercounts vowel groups with German-specific adjustments (diphthongs/digraphs, no final-estripping,quexcluded, hyphenated compounds split and summed) and targets ≥95% exact-match accuracy on a curated German word list. It does not embed a hyphenation dictionary — the standard German pattern files are LGPL/MPL-licensed, which would compromise this package's MIT license, and they are large. If you need dictionary-grade accuracy, implementISyllableCounteragainst one. - Known failure cases: words where
iespans a morpheme boundary rather than forming a single vowel sound undercount by one syllable — e.g.Familie,Ferien,Linie. Loanwords and acronyms with non-German vowel patterns are not specially handled. - Flesch is the most sensitive of the three to syllable-count error. Its
ASWterm carries a 58.5 coefficient, so a syllable miscount moves the score substantially. WSTF is more forgiving, and LIX doesn't use syllables at all — it's immune to this class of error entirely. - German only. English Flesch, Flesch-Kincaid, SMOG, Gunning-Fog and RIX are out of scope for this package by design; using the English Flesch formula on German text is a common and serious error, which is why this library doesn't offer it.
- Input is plain
string— HTML/PDF/DOCX extraction is the caller's responsibility.
MIT licensed — see LICENSE. This is a clean-room implementation from the published formulas and definitions (Bamberger/Vanecek 1965 for WSTF, Amstad 1978 for the German Flesch adaptation, Björnsson 1968 for LIX); no GPL or other copyleft-licensed code was used or consulted beyond formula-level facts, which are not copyrightable.
Credit and thanks to pablotheissen/wstf, a small Python WSTF implementation, for the initial inspiration to build this — its source (GPL-3.0) was not copied, translated, or transliterated in any form, and none of its test fixtures were reused.
Issues and pull requests welcome at github.com/taskinkemal/Readability.German.
To run the tests locally:
dotnet test -c ReleaseThe solution targets net8.0 and net10.0; global.json pins the SDK feature band so local
builds match CI.