diff --git a/.github/workflows/printing.yml b/.github/workflows/printing.yml index e22a4b368..1a5dad4c8 100644 --- a/.github/workflows/printing.yml +++ b/.github/workflows/printing.yml @@ -6,6 +6,7 @@ on: pull_request: paths: - 'Cargo*' + - 'craft-i18n.rev' - 'apps/wordcraft/**' - 'apps/wordcraft-web/**' - 'crates/ui-egui/**' @@ -14,6 +15,7 @@ on: branches: [main] paths: - 'Cargo*' + - 'craft-i18n.rev' - 'apps/wordcraft/**' - 'apps/wordcraft-web/**' - 'crates/ui-egui/**' @@ -41,6 +43,26 @@ jobs: CARGO_INCREMENTAL: '0' steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # The interface catalogs and craft-proof's Language data come from craft-i18n at the commit in + # craft-i18n.rev, so these tests build with the same data as the gate (ci.yml). + - name: Check out craft-i18n + uses: storytold/craft-common-actions/actions/checkout-craft-i18n@main + + # The catalogs include CJK interface languages, so the UI tests need the suite's fonts, as in + # the gate (same commit as ci.yml and release.yml). + - name: Check out craft-fonts + uses: storytold/craft-common-actions/actions/checkout-craft-fonts@main + with: + ref: b076417b5880bd4154bd22f00695b1d28c6bea83 + + - name: Build with craft-fonts + shell: bash + run: | + echo "CRAFT_FONTS_DIR=$GITHUB_WORKSPACE/craft-fonts" >> "$GITHUB_ENV" + echo "CRAFT_FONTS_REQUIRED=1" >> "$GITHUB_ENV" - name: Install Linux build and software-rendering dependencies if: runner.os == 'Linux' diff --git a/AGENTS.md b/AGENTS.md index f52cb94d7..04fb60a52 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ People trust WordCraft with their writing; a crash loses their work. **This outr - Never copy, trace, screenshot-crop or recreate icons, images, templates, themes, clip art, fonts, cursors, sounds or any other asset from Microsoft Word/Office or any Microsoft, Adobe, Avid or Autodesk product. Not even "temporarily". - Every asset must be **original** (drawn in code — prefer this: `crates/ui-egui/src/icons.rs`), **public domain / CC0**, **OSI open source**, or **Creative Commons that allows redistribution**, or licensed open source by a contributor who made it. - **Every asset file has a row in [`ATTRIBUTION.md`](ATTRIBUTION.md)** (author, source, licence). `cargo xtask assets` (part of `cargo xtask ci`) fails otherwise. Add the row in the same commit as the file. -- Fonts: new font files go to [`storytold/craft-fonts`](https://github.com/storytold/craft-fonts), never this repo; it's an optional build input (`CRAFT_FONTS_DIR`). Documents asking for proprietary fonts (Calibri, Aptos, Cambria…) are rendered with installed system fonts or metric-compatible open substitutes (`crates/fonts/src/word.rs`) — never bundle proprietary fonts. +- Fonts: new font files go to [`storytold/craft-fonts`](https://github.com/storytold/craft-fonts), never this repo; it's an optional build input (`CRAFT_FONTS_DIR`). Spelling word lists and hyphenation patterns live in [`storytold/craft-i18n`](https://github.com/storytold/craft-i18n) `language-data/`, never this repo; they are embedded from `CRAFT_I18N_DIR` (pinned by `craft-i18n.rev`, which CI checks out). Without it the app builds, but English spelling falls back to an installed Hunspell English or the built-in Moby word list (with no American/British variant table) and pattern hyphenation is off (Bulgarian, Turkish and Russian still hyphenate by rule), so proofing tests need it. Documents asking for proprietary fonts (Calibri, Aptos, Cambria…) are rendered with installed system fonts or metric-compatible open substitutes (`crates/fonts/src/word.rs`) — never bundle proprietary fonts. - Templates, sample documents and themes are our own text and colours (`crates/engine/src/sample.rs`, `cmd/design.rs`). Demo images are public domain, with sources recorded. ## Clean room @@ -34,11 +34,12 @@ People trust WordCraft with their writing; a crash loses their work. **This outr | L0 | `geom` | units, rects, measurement parsing | | L1 | `doc`, `fonts` | document model + edit primitives; font DB, shaping, Word font substitution | | L2 | `layout`, `docx`, `formats` | line breaking, pagination, tables, headers/footers, hit testing; OOXML I/O; TXT/MD/HTML/RTF/ODT | -| L3 | `render`, `pdf`, `proof` | vello_cpu rasteriser; krilla PDF; proofing | +| L3 | `render`, `pdf` | vello_cpu rasteriser; krilla PDF | | L4 | `engine` | Session, undo, **command registry**, Word feature catalog, samples, I/O dispatch | | L5 | `mcp` | MCP server (headless or bridged to the app) | | L6 | `ui-egui` | Word-style egui front end (swappable) | | apps | `apps/wordcraft`, `apps/wordcraft-cli`, `apps/wordcraft-web` | binaries | +| external | `craft-proof` (craft-libs, used as `wordcraft-proof`, sits at L1) | proofing: spelling, grammar, hyphenation; its word lists and patterns are craft-i18n's `language-data/` (`CRAFT_I18N_DIR`) | - **Everything is a command.** User-visible behaviour = a `CommandSpec` in `crates/engine/src/cmd/*.rs` (id, label, ribbon location, shortcut, params doc, `enabled`, `run`) + tests. UI-only commands (`ui.*`) live in `crates/ui-egui/src/lib.rs`. The ribbon, shortcuts, command search, CLI, control channel and MCP all dispatch by id. Programmatic calls never open dialogs. - **Parity is measured:** `crates/engine/src/catalog.rs` lists Word's ribbon/menu features with command ids. `cargo xtask parity` writes `docs/parity-checklist.md`; a test enforces a floor that only rises. Implement missing ids to raise it. diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 23d8cbd21..36c3b38a3 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -10,15 +10,6 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra |---|---|---|---|---| | `egui` dependency: `epaint_default_fonts/fonts/egui-icons.ttf` | John Slegers (emoji-icon-font) | https://github.com/storytold/egui/blob/173cc7375d35dc5fbc5b5d0994fdf787f547c5d1/crates/epaint_default_fonts/fonts/egui-icons.txt; original https://github.com/jslegers/emoji-icon-font | MIT (full copyright and permission text in NOTICE) | Small egui special-icon subset; embedded by default_fonts, retained in FontDefinitions::default() | | `egui` dependency: `epaint_default_fonts/fonts/emoji-icon-font.ttf` | John Slegers | https://github.com/storytold/egui/blob/173cc7375d35dc5fbc5b5d0994fdf787f547c5d1/crates/epaint_default_fonts/fonts/emoji-icon-font-mit-license.txt; original https://github.com/jslegers/emoji-icon-font | MIT (full copyright and permission text in NOTICE) | Parent monochrome icon/emoji face embedded by monochrome_emoji_fonts; separate from the egui-icons subset | -| `assets/hyphenation/en-us.pat` | DesignCraft contributors (generated) | trained from the Moby list by DesignCraft's patgen | Public Domain (CC0-1.0) | Our own Liang hyphenation patterns (no TeX patterns) | -| `assets/hyphenation/en-us.dic` | Grady Ward (Moby Hyphenator II word list); converted by DesignCraft contributors | https://www.gutenberg.org/ebooks/3204 (file `mhyph.txt`, SHA-256 `eeb30474c86b8af3469035ec1a0913e35905325ca885290db9dfed9881e230ac`), via DesignCraft | Public Domain | ~165k words with break points; drives spelling and hyphenation | -| `assets/spelling/en-extra.txt` | WordCraft contributors | original word list written for this repository (common modern words the Moby list lacks); no other dictionary copied | MIT OR Apache-2.0 | ~240 lowercase English base forms supplementing spelling (#520), including British spellings (#40) | -| `assets/spelling/en-variants.txt` | WordCraft contributors | original list written for this repository from general knowledge of English spelling; no other dictionary or variant list copied | MIT OR Apache-2.0 | ~280 American/British spelling pairs (-or/-our, -er/-re, -ize/-ise, -yze/-yse, -ense/-ence, doubled l, ae/oe…) with a policy per English: en-US, en-CA and the British-spelling Englishes (#1281, #1045) | -| `assets/spelling/ru-extra.txt` | WordCraft contributors | original word list written for this repository (common modern Russian words older Hunspell dictionaries lack, with their declension classes); no other dictionary copied | MIT OR Apache-2.0 | ~200 Russian base forms supplementing spelling; their forms are generated by `crates/proof/src/ru/extra.rs` | -| `assets/hyphenation/pl.pat` | Hanna Kołodziejska, Bogusław Jackowski, Marek Ryćko (Polish TeX hyphenation patterns, 1987–1995); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-pl.tex`, SHA-256 `83ddbbd21bc06a44bc4f4a4c49c38782e809cff5f026b764c044daa29e818843`), bundled unmodified | MIT (the file is offered under MIT or Knuth's public-domain terms; WordCraft uses MIT, whose notice is in the file's header) | 4,053 Liang patterns and 20 exception words; Polish hyphenation | -| `assets/hyphenation/pt.pat` | Pedro J. de Rezende, J. Joao Dias Almeida, Leonardo Araujo, Aline Benevides (Portuguese TeX hyphenation patterns, 1987–2024); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-pt.tex`, SHA-256 `c110a39e501db372d1d755e1c64464321a2efa7d8e9617f66001000389b941f2`), bundled unmodified | BSD 3-clause (the file's licence header names it and carries the notice) | 426 Liang patterns and 2 exception words; Portuguese hyphenation | -| `assets/hyphenation/it.pat` | Claudio Beccari (Italian TeX hyphenation patterns, 2008–2014); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-it.tex`, SHA-256 `6ce56ed6ed2ca7c0688838e366e6595e0429f984f4fc6e96ec045cebb1e76a47`), bundled unmodified | MIT (the file is offered under LPPL 1.3+ or MIT; WordCraft uses MIT, whose notice is in the file's header) | 355 Liang patterns, no exception words; Italian hyphenation | -| `assets/hyphenation/de.pat` | Deutschsprachige Trennmustermannschaft: Stephan Hennig, Werner Lemberg, Günter Milde, Sander van Geloven, Georg Pfeiffer, Gisbert W. Selke, Tobias Wendorf, Keno Wehr (German TeX hyphenation patterns, reformed spelling, 2013–2024); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-de-1996.tex`, SHA-256 `374ad1ce3263f8a2791ec070ef9b516c532dae64456f3fb656e617e8ae53a9d3`), bundled unmodified | MIT (the notice is in the file's header) | 36,709 Liang patterns, no exception words; German hyphenation (de-DE, de-AT, de-CH) | | `assets/app-icon/wordcraft-1024.png` | WordCraft contributors | original artwork drawn in SVG (`assets/app-icon/wordcraft.svg`), rendered by `packaging/icons.sh` | MIT OR Apache-2.0 | Placeholder app icon until the WordCraft mascot drawing lands | | `packaging/macos/dmg/background.svg` | @XusBadia | original work for this repository, derived from the WordCraft app icon (`assets/app-icon/wordcraft.svg`, referenced via ``, not copied); text is outlined Inter / JetBrains Mono glyph paths | MIT OR Apache-2.0, like the code ([`LICENSE-MIT`](LICENSE-MIT), [`LICENSE-APACHE`](LICENSE-APACHE)) | WordCraft macOS DMG window background (2026-10-08); used for the Finder window of the macOS DMG, rendered to `background.tiff` by `packaging/macos/dmg/generate.py` | | `packaging/macos/dmg/background.tiff` | @XusBadia | Rendered from `packaging/macos/dmg/background.svg` by `packaging/macos/dmg/generate.py` | MIT OR Apache-2.0, like the code ([`LICENSE-MIT`](LICENSE-MIT), [`LICENSE-APACHE`](LICENSE-APACHE)) | 1x + 2x HiDPI TIFF for the macOS DMG window; includes the app icon (see its row) and LittleCMS's built-in sRGB profile ("No copyright, use freely") | @@ -70,3 +61,4 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra | `docs/brand/artcraft-mark.svg` | ArtCraft Team | ArtCraft brand kit (project owner) | ArtCraft trademark, `docs/brand/LICENSE-brand.txt` | Not open source; usable unmodified only | | craft-fonts (optional build input, not files in this repo): BIZ UDPGothic, BIZ UDMincho, Shippori Mincho, Noto Sans CJK SC, Noto Sans Arabic | see https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md | embedded only when built with `CRAFT_FONTS_DIR` (all official releases) | OFL-1.1 | Japanese and Chinese UI, Japanese document fallback | | craft-i18n (optional build input, not files in this repo): the interface translations | see https://github.com/storytold/craft-i18n/blob/main/ATTRIBUTION.md, which credits every translator of WordCraft's former in-repo catalogs (`crates/ui-egui/src/i18n/*.tsv`, migrated at `wordcraft@806a945`) | embedded only when built with `CRAFT_I18N_DIR`, at the commit in `craft-i18n.rev` (all official releases) | MIT OR Apache-2.0 | Interface languages other than English; clean-room translations from the English interface labels | +| craft-i18n Language data (optional build input, not files in this repo): the spelling word lists and hyphenation patterns (`language-data/`: the Moby Hyphenator II list and our patterns for English, `en-extra.txt`, `en-variants.txt`, `ru-extra.txt`, and the hyph-utf8 German, Polish, Portuguese and Italian patterns) | see the Language data section of https://github.com/storytold/craft-i18n/blob/main/ATTRIBUTION.md (authors, upstream sources with SHA-256, licences); moved there from `assets/hyphenation/` and `assets/spelling/` byte for byte | embedded by craft-proof (storytold/craft-libs) only when built with `CRAFT_I18N_DIR`, at the commit in `craft-i18n.rev` (all official releases) | Public Domain / CC0-1.0, MIT, BSD-3-Clause, MIT OR Apache-2.0 (per file; see NOTICE) | Spelling and hyphenation; without it English spelling falls back to an installed Hunspell English or craft-proof's built-in Moby list, and pattern hyphenation is off | diff --git a/CLAUDE.md b/CLAUDE.md index 35024b74d..c3d28358b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ People trust WordCraft with their writing; a crash loses their work. **This outr - Never copy, trace, screenshot-crop or recreate icons, images, templates, themes, clip art, fonts, cursors, sounds or any other asset from Microsoft Word/Office or any Microsoft, Adobe, Avid or Autodesk product. Not even "temporarily". - Every asset must be **original** (drawn in code — prefer this: `crates/ui-egui/src/icons.rs`), **public domain / CC0**, **OSI open source**, or **Creative Commons that allows redistribution**, or licensed open source by a contributor who made it. - **Every asset file has a row in [`ATTRIBUTION.md`](ATTRIBUTION.md)** (author, source, licence). `cargo xtask assets` (part of `cargo xtask ci`) fails otherwise. Add the row in the same commit as the file. -- Fonts: new font files go to [`storytold/craft-fonts`](https://github.com/storytold/craft-fonts), never this repo; it's an optional build input (`CRAFT_FONTS_DIR`). Documents asking for proprietary fonts (Calibri, Aptos, Cambria…) are rendered with installed system fonts or metric-compatible open substitutes (`crates/fonts/src/word.rs`) — never bundle proprietary fonts. +- Fonts: new font files go to [`storytold/craft-fonts`](https://github.com/storytold/craft-fonts), never this repo; it's an optional build input (`CRAFT_FONTS_DIR`). Spelling word lists and hyphenation patterns live in [`storytold/craft-i18n`](https://github.com/storytold/craft-i18n) `language-data/`, never this repo; they are embedded from `CRAFT_I18N_DIR` (pinned by `craft-i18n.rev`, which CI checks out). Without it the app builds, but English spelling falls back to an installed Hunspell English or the built-in Moby word list (with no American/British variant table) and pattern hyphenation is off (Bulgarian, Turkish and Russian still hyphenate by rule), so proofing tests need it. Documents asking for proprietary fonts (Calibri, Aptos, Cambria…) are rendered with installed system fonts or metric-compatible open substitutes (`crates/fonts/src/word.rs`) — never bundle proprietary fonts. - Templates, sample documents and themes are our own text and colours (`crates/engine/src/sample.rs`, `cmd/design.rs`). Demo images are public domain, with sources recorded. ## Clean room @@ -34,11 +34,12 @@ People trust WordCraft with their writing; a crash loses their work. **This outr | L0 | `geom` | units, rects, measurement parsing | | L1 | `doc`, `fonts` | document model + edit primitives; font DB, shaping, Word font substitution | | L2 | `layout`, `docx`, `formats` | line breaking, pagination, tables, headers/footers, hit testing; OOXML I/O; TXT/MD/HTML/RTF/ODT | -| L3 | `render`, `pdf`, `proof` | vello_cpu rasteriser; krilla PDF; proofing | +| L3 | `render`, `pdf` | vello_cpu rasteriser; krilla PDF | | L4 | `engine` | Session, undo, **command registry**, Word feature catalog, samples, I/O dispatch | | L5 | `mcp` | MCP server (headless or bridged to the app) | | L6 | `ui-egui` | Word-style egui front end (swappable) | | apps | `apps/wordcraft`, `apps/wordcraft-cli`, `apps/wordcraft-web` | binaries | +| external | `craft-proof` (craft-libs, used as `wordcraft-proof`, sits at L1) | proofing: spelling, grammar, hyphenation; its word lists and patterns are craft-i18n's `language-data/` (`CRAFT_I18N_DIR`) | - **Everything is a command.** User-visible behaviour = a `CommandSpec` in `crates/engine/src/cmd/*.rs` (id, label, ribbon location, shortcut, params doc, `enabled`, `run`) + tests. UI-only commands (`ui.*`) live in `crates/ui-egui/src/lib.rs`. The ribbon, shortcuts, command search, CLI, control channel and MCP all dispatch by id. Programmatic calls never open dialogs. - **Parity is measured:** `crates/engine/src/catalog.rs` lists Word's ribbon/menu features with command ids. `cargo xtask parity` writes `docs/parity-checklist.md`; a test enforces a floor that only rises. Implement missing ids to raise it. diff --git a/Cargo.lock b/Cargo.lock index 7bff2098d..9396eae9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1061,6 +1061,16 @@ dependencies = [ "winsafe", ] +[[package]] +name = "craft-proof" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=48ed6f8cbd4104225a9ecde122d9fb8d1353b5a6#48ed6f8cbd4104225a9ecde122d9fb8d1353b5a6" +dependencies = [ + "log", + "miniz_oxide 0.9.1", + "unicode-normalization", +] + [[package]] name = "craft-ui" version = "0.1.0" @@ -6654,6 +6664,7 @@ dependencies = [ "chrono", "clipboard-win", "craft-print", + "craft-proof", "craft-update", "dispatch2", "eframe", @@ -6676,7 +6687,6 @@ dependencies = [ "wordcraft-doc", "wordcraft-engine", "wordcraft-fonts", - "wordcraft-proof", "wordcraft-ui-egui", "zbus", ] @@ -6759,6 +6769,7 @@ version = "0.14.0" dependencies = [ "cfb", "chrono", + "craft-proof", "encoding_rs", "getrandom 0.3.4", "image", @@ -6778,7 +6789,6 @@ dependencies = [ "wordcraft-geom", "wordcraft-layout", "wordcraft-pdf", - "wordcraft-proof", "wordcraft-render", "zeroize", "zip 3.0.0", @@ -6829,6 +6839,7 @@ dependencies = [ name = "wordcraft-layout" version = "0.14.0" dependencies = [ + "craft-proof", "image", "js-sys", "kurbo", @@ -6842,7 +6853,6 @@ dependencies = [ "wordcraft-fonts", "wordcraft-geom", "wordcraft-metafile", - "wordcraft-proof", ] [[package]] @@ -6886,16 +6896,6 @@ dependencies = [ "wordcraft-metafile", ] -[[package]] -name = "wordcraft-proof" -version = "0.14.0" -dependencies = [ - "log", - "miniz_oxide 0.9.1", - "proptest", - "unicode-normalization", -] - [[package]] name = "wordcraft-render" version = "0.14.0" diff --git a/Cargo.toml b/Cargo.toml index a1b229db3..caa50c2d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,12 @@ wordcraft-docbin = { path = "crates/docbin" } wordcraft-formats = { path = "crates/formats" } wordcraft-render = { path = "crates/render" } wordcraft-pdf = { path = "crates/pdf" } -wordcraft-proof = { path = "crates/proof" } +# Proofing is craft-libs' craft-proof (it was crates/proof); its Language data is craft-i18n's +# language-data/, embedded from CRAFT_I18N_DIR (craft-i18n.rev). The package keeps the +# `wordcraft-proof` name here so `wordcraft_proof::` paths are unchanged. +# Pinned like the other craft-libs crates (each has its own rev). This rev is the head of +# storytold/craft-libs#482, which adds craft-proof; move it to that PR's merge commit once merged. +wordcraft-proof = { package = "craft-proof", git = "https://github.com/storytold/craft-libs", rev = "48ed6f8cbd4104225a9ecde122d9fb8d1353b5a6", version = "=0.1.0" } wordcraft-engine = { path = "crates/engine" } wordcraft-mcp = { path = "crates/mcp" } wordcraft-zotero = { path = "crates/zotero" } diff --git a/NOTICE b/NOTICE index 82e79a0a9..99d190fbe 100644 --- a/NOTICE +++ b/NOTICE @@ -20,13 +20,17 @@ list (author, source, license) is in ATTRIBUTION.md. https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md (BIZ UDPGothic, BIZ UDMincho, Shippori Mincho), under the SIL Open Font License 1.1. Their licence texts ship with the packages as OFL-.txt. Builds without CRAFT_FONTS_DIR do not include them. -- English word list (assets/hyphenation/en-us.dic) — derived from the Moby Hyphenator II list by +- Language data (craft-i18n): the word list and hyphenation patterns below are embedded at build + time from https://github.com/storytold/craft-i18n (language-data/, CRAFT_I18N_DIR at the commit + in craft-i18n.rev; all official releases) by craft-proof (storytold/craft-libs). Builds without + CRAFT_I18N_DIR do not include them. +- English word list (craft-i18n language-data/en-us/en-us.dic) — derived from the Moby Hyphenator II list by Grady Ward, public domain (Project Gutenberg eBook #3204). The hyphenation patterns - (assets/hyphenation/en-us.pat) were trained from it and are released as CC0-1.0. -- Polish hyphenation patterns (assets/hyphenation/pl.pat) — hyph-pl.tex from the hyph-utf8 + (craft-i18n language-data/en-us/hyphenation.pat) were trained from it and are released as CC0-1.0. +- Polish hyphenation patterns (craft-i18n language-data/pl/hyphenation.pat) — hyph-pl.tex from the hyph-utf8 package, Copyright (C) 1987-1995 Hanna Kołodziejska, Bogusław Jackowski, Marek Ryćko, bundled unmodified and used under the MIT License; the copyright and permission notice is in the file. -- Portuguese hyphenation patterns (assets/hyphenation/pt.pat) — hyph-pt.tex from the hyph-utf8 +- Portuguese hyphenation patterns (craft-i18n language-data/pt/hyphenation.pat) — hyph-pt.tex from the hyph-utf8 package, bundled unmodified and used under the BSD 3-Clause License: Copyright (C) 1987, 1994, 1996, 2015 Pedro J. de Rezende, 1996, 2015 J. Joao Dias Almeida, @@ -52,7 +56,7 @@ list (author, source, license) is in ATTRIBUTION.md. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- German hyphenation patterns (assets/hyphenation/de.pat) — hyph-de-1996.tex (reformed spelling) +- German hyphenation patterns (craft-i18n language-data/de/hyphenation.pat) — hyph-de-1996.tex (reformed spelling) from the hyph-utf8 package, bundled unmodified and used under the MIT License; the copyright and permission notice is in the file: @@ -74,7 +78,7 @@ list (author, source, license) is in ATTRIBUTION.md. FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- Italian hyphenation patterns (assets/hyphenation/it.pat) — hyph-it.tex from the hyph-utf8 +- Italian hyphenation patterns (craft-i18n language-data/it/hyphenation.pat) — hyph-it.tex from the hyph-utf8 package, Copyright (C) 2008-2011 Claudio Beccari, bundled unmodified and used under the MIT License (offered under LPPL 1.3+ or MIT); the copyright and permission notice is in the file: diff --git a/README.md b/README.md index 01d366510..2db5753ef 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,19 @@ cargo run --release -p wordcraft -- --sample # the desktop app with the s cargo run --release -p wordcraft -- report.docx # open a document ``` +The interface translations and the spelling and hyphenation data come from +[craft-i18n](https://github.com/storytold/craft-i18n), an optional build input. To build with them, +check it out at the commit in `craft-i18n.rev` and point `CRAFT_I18N_DIR` at it (an absolute +path); without it WordCraft builds with an English interface, English spelling falls back to an +installed Hunspell English or craft-proof's built-in word list, and only Bulgarian, Turkish and +Russian hyphenate (by rule): + +```sh +git clone https://github.com/storytold/craft-i18n ../craft-i18n +git -C ../craft-i18n checkout "$(cat craft-i18n.rev)" +CRAFT_I18N_DIR="$PWD/../craft-i18n" cargo run --release -p wordcraft -- --sample +``` + Command line: ```sh @@ -194,7 +207,7 @@ covers every target starting with it (`wordcraft*=debug`). The logger is | Layer | Crate | Job | |---|---|---| | L0 | `wordcraft-geom` | units and measurements | -| L1 | `wordcraft-doc`, `wordcraft-fonts`, `wordcraft-proof` | document model and editing; fonts and shaping; spelling, grammar, hyphenation | +| L1 | `wordcraft-doc`, `wordcraft-fonts`, `wordcraft-proof` ([`craft-proof`](https://github.com/storytold/craft-libs/tree/main/crates/proof) from craft-libs) | document model and editing; fonts and shaping; spelling, grammar, hyphenation (data: craft-i18n `language-data/`, `CRAFT_I18N_DIR`) | | L2 | `wordcraft-layout`, `wordcraft-docx`, `wordcraft-formats` | line breaking, pagination, tables, notes, hit testing; OOXML; ODT/RTF/HTML/Markdown/TXT | | L3 | `wordcraft-render`, `wordcraft-pdf` | rasteriser (vello_cpu); PDF ([craft-pdf](docs/shared-pdf.md), Krilla) | | L4 | `wordcraft-engine` | session, undo, 422 commands, document feature catalog | diff --git a/ROADMAP.md b/ROADMAP.md index ee41c6059..090a57665 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -233,6 +233,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target - 2026-10-15: Find opens Navigation on Results; editing the query restores Results after manual Headings/Pages navigation. Typed and selected-text searches retain document contents (#2096). | Date | What landed | +| 2026-11-01 | Proofing moved to the shared libraries, with nothing users see changing: the code is `craft-proof` in storytold/craft-libs (with its history) and the spelling and hyphenation data (the Moby list, our English patterns and word lists, the German, Polish, Portuguese and Italian patterns, `ru-extra`) is craft-i18n's `language-data/`, embedded from `CRAFT_I18N_DIR` at the commit in `craft-i18n.rev`. Before/after `wordcraft-cli` runs on a proofing document give the same issues, suggestions and hyphenated PDF (CP1). | | 2026-10-28 | **Arabic diacritic editing (#4757):** Backspace removes the last stored Arabic combining mark, so its base letter can stay while the mark is replaced. Caret movement and forward Delete keep their cluster rules. Synthetic command, history, protection and native/DOCX fresh-reopen checks; no native Word comparison or readiness change. | | 2026-10-29 | Insert › Shapes shows the basic shapes as a grid of code-drawn icon tiles, each with an accessible name and tooltip, instead of a text list (#4349) | | 2026-11-01 | Markdown export: a list inside a block quote stays in the quote (`> - item`), its continuation lines included; it was read as quoted list paragraphs but written without `>`, so the list moved out of the quote on every save | @@ -706,6 +707,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | 2026-11-01 | trivial | Progress log: tab leader dots spaced and placed as in Word | | 2026-10-31 | trivial | Progress log: a list level above counts its first number at the first deeper item | | 2026-11-01 | trivial | Progress log: paragraph side borders lie outside the text (#5036) | +| 2026-11-01 | trivial | Progress log: proofing moved to craft-proof (craft-libs) and its data to craft-i18n `language-data/` (CP1) | | 2026-10-31 | trivial | Progress log: a paragraph naming an undefined DOCX style is formatted as Normal (#5077) | | 2026-10-31 | trivial | Progress log: TOC page numbers are PAGEREF fields (#5097) | | 2026-10-31 | trivial | Progress log: Backstage keytips, Alt, F, P prints (#4960) | diff --git a/assets/hyphenation/de.pat b/assets/hyphenation/de.pat deleted file mode 100644 index 15641e1ec..000000000 --- a/assets/hyphenation/de.pat +++ /dev/null @@ -1,36797 +0,0 @@ -% title: German Hyphenation Patterns (Reformed Orthography, 2006) -% -% notice: TeX-Trennmuster für die reformierte (2006) deutsche Rechtschreibung -% -% version: 2024-02-28 -% -% authors: -% - -% name: Deutschsprachige Trennmustermannschaft -% contact: trennmuster@dante.de -% -% copyright: Copyright (c) 2013-2024 -% Stephan Hennig, Werner Lemberg, Günter Milde, -% Sander van Geloven, Georg Pfeiffer, Gisbert W. Selke, -% Tobias Wendorf, Keno Wehr -% -% licence: -% name: MIT -% url: https://opensource.org/licenses/mit-license.php -% text: > -% Permission is hereby granted, free of charge, to any person -% obtaining a copy of this software and associated documentation -% files (the “Software”), to deal in the Software without -% restriction, including without limitation the rights to use, -% copy, modify, merge, publish, distribute, sublicense, and/or -% sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following -% conditions: -% -% The above copyright notice and this permission notice shall be -% included in all copies or substantial portions of the Software. -% -% THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -% OTHER DEALINGS IN THE SOFTWARE. -% -% source: https://repo.or.cz/w/wortliste.git?a=commit;h=304aaa2188a75e57afe36bad0443d90a9c715b8e -% -% language: -% name: German, reformed spelling -% tag: de-1996 -% -% hyphenmins: -% generation: -% left: 2 -% right: 2 -% typesetting: -% left: 2 -% right: 2 -% -% texlive: -% encoding: ec -% babelname: ngerman -% use_old_patterns_comment: Kept for the sake of backward compatibility, but newer and better patterns by WL are available. -% legacy_patterns: dehyphn.tex -% message: German hyphenation patterns (reformed orthography) -% package: german -% -% =========================================================================== - -\message{German Hyphenation Patterns (Reformed Orthography, 2006) `dehyphn-x' 2024-02-28 (WL)} - -% -% The used patgen parameters are -% -% 1 1 | 1 3 | 2 3 1 -% 2 2 | 2 4 | 1 5 1 -% 3 3 | 3 5 | 1 6 1 -% 4 4 | 4 6 | 1 7 1 -% 5 5 | 5 12 | 1 8 1 -% 6 6 | 6 12 | 1 9 1 -% 7 7 | 7 12 | 1 9 1 -% 8 8 | 8 12 | 1 9 1 - -\patterns{% -.ab3a -.abb2 -.ab5erk -.ab5er4r -.abi2 -.ab3it -.ab3l -.ab5le -.ab3ol -.ab3or -.ab3sa -.ab5so -.ab3u -.ach4sa -.achse4 -.ach4sp -.achts4 -.ack4e -.ae2 -.aer6schl -.ago3 -.ag4r -.ag2u -.ai2s -.ak6t5en6de. -.ak6t5en6den -.ak6t5en6des -.al4bl -.alb3r -.al4bra -.alb6rec -.ale3u4 -.ali3 -.al5l6en -.alle4r5 -.al4lin -.allo3 -.all3s -.al4mau -.al4pl -.al5s4e -.al4t5ak -.al6t5ang -.alt6an6l -.altan6s -.al4tei -.al4t5e4l -.al6t5er6f -.al6t5er6p -.alter6s5 -.al4t3h -.al4ti -.al4tor -.al4t5ra -.al4tri -.al4tro -.alt3s4 -.al4t3u -.al4u -.alu3s -.ampe4 -.am5sta -.amt4s3 -.ana3c -.an3ae -.an5alg -.ana3t -.anden6ke -.an3e -.ang4a -.an5g4li -.ang4r -.angs4 -.angst5 -.ani4s -.anis5c -.ank2 -.anla6gentec -.an3n4a -.an3o2 -.an4ora -.ans2 -.ansch4 -.an4si. -.an5sp -.an4tag -.anti5s -.anz2 -.an5zw -.apfel5 -.app6le. -.are3n -.ar4m5ac -.ar4mar -.ar4mau -.ar4mei -.ar4mun -.ar4sc -.arsch5l -.ar4tan -.ar4tau -.ar4t5ei -.arter4 -.ar6t5erh -.ar6t5erk -.ar4t3r -.arz2 -.arzt3 -.as2b -.asbe4 -.asie4 -.as4ko -.as2p -.as4ta. -.as5tar -.as4zen -.ata3 -.ate4m3 -.at4h -.aue4n3 -.au2f3 -.auf5s4 -.auge4 -.au2s3 -.aussen6dung. -.auto6ren. -.auto6rin -.ax2 -.ärm4s -.är6schl -.ät2h -.ät2s -.bahner6 -.ba4ran -.bau5chi -.bauer6f -.bau3f4 -.beige4 -.bei6ge. -.be3n4a -.be3n4e -.be3r4a -.ber4e -.ber4ga -.berg5ab -.berg5ar -.berg5ast -.ber6g5e6b -.ber6gin -.ber4g5l -.ber4g5o -.ber4g5r -.berg5s -.ber4t5r -.be3s4a -.be6t5raum -.bi2e -.bi2o -.bi4os -.bi4t3a -.bi4t3r -.blau3 -.blei5s4 -.boge4 -.bogen5 -.bogens6 -.bor4d5i -.bo4s3k -.bre4a -.brecher6 -.bre6cherk -.brid4 -.briefen6 -.brie6f5end -.bruch5s -.brut5s -.bu6cher -.buchse6 -.bue5be -.bu4ser -.buster6 -.buß3 -.bü3be -.by2t -.ce4ra -.ch2 -.ch4a -.char6me. -.char6mes -.ch4i -.chi5er -.dab2 -.da6ch5en -.da3la -.dan4c -.da2r -.dar5in -.dar4m3 -.dar3u -.da4te. -.da4tes -.daue4 -.de4al -.de3ch -.de6ck5eri -.dein4 -.de5ins -.de5int -.de6m5ents -.de3na -.den4ka -.den4kl -.den4ko -.den4kr -.denta4 -.de3od -.de5ran -.de4rei -.de5r4en -.de5skr -.des4pe -.de4spo -.des5se -.de3st -.des4ti -.de3xa -.di2a -.di5ae4r -.di3ä4r -.di2e -.die5ne -.dien4s -.dienst5a6d -.dienst5r -.di5s4ti -.do4mo -.dorf3 -.dor6fer -.do4tr -.drah6t5en -.drucker6zeu -.due3b -.du6schr -.dü3b -.dy2s3 -.ebe4r -.echo3 -.eck5erk -.ede4l -.eh4e -.ehe3i4 -.ehein4 -.ehe3n -.ehre4 -.eh4r5er -.eichs4 -.eich5ste -.eide4r5 -.ei3e2 -.eier3 -.eigener6 -.ei4lau -.ein3a -.ei4nat -.eine4 -.ei4n5eb -.ei6n5en6g -.eins4p -.eins6tem -.einzel6lig -.eisen5 -.ei4spe -.eis3s4 -.ei4s3t -.ei6ste -.ei4s3u -.ei4tr -.ek5lam -.ek3li -.ek3s -.el4a -.el4b3i -.el4bl -.elb5s -.el4fei -.elfens6 -.el4fl -.en1 -.en4d3a -.en4d5ei -.en4d5er4 -.en5der. -.en6dost -.en4d3r -.end3s -.en4due -.en4dü -.en4g3l -.eng5sta -.enk2 -.enn4st -.en4t3a -.en4tä -.en4t5ei -.en4tem -.en5thalp -.en4thi -.en4t5io -.en4toe -.en4t3r -.en5trep -.en5trop -.ent3s4 -.epi3c -.ep3o4d -.er6b5aen -.er4b5än -.er4b5ei -.er4b5er -.er6brecht -.er4bu -.er6d5achs -.er4dan -.er4dar -.er4d5ei -.er4del -.erden6k -.er4d5ep -.er4d5er -.er6d5ess -.ere5ch -.er3ei -.er3er -.erfolgsauto6 -.er3in -.erk2 -.er3lö -.ern4s -.er3o4b -.er3oe -.ers2 -.er6stab -.er6st5ein -.er6ster6 -.erst5eri -.erst5ers -.er6stim -.er6stritt. -.er6stritten. -.er4z5el -.erzen4 -.er6z5eng -.er4z5er -.es3p -.es4s3t -.es3ta4 -.es3te -.es5tra -.est5ri -.et2h -.et2s -.eu1 -.euch4 -.eu3g2 -.eur4 -.eu3t -.eve4r -.ex3em -.fahrzeugin6 -.fan4gl -.fang5s -.faus6t5r -.fe3la -.felden5 -.fel4d5r -.fel6lau -.fel4s5a -.fel4sp -.fel4s5t -.fen2 -.fer4n5o -.fe4st -.fest5r -.fi3d -.fi5est -.fi4le. -.fi4len -.film5a -.fil4s3 -.fin4s -.fi2r -.flu4g3 -.flus6s5en -.former6 -.for5tu -.fra4s -.fs2 -.fu4sc -.fus4st -.ga4me. -.ga4mes -.gan4ga -.gang5sc -.garnen6 -.gar6n5end -.ga2s -.gas3e -.gas3s -.gas5tein -.gas6tras -.ga2t -.geb2 -.gebe4a -.gege4n5 -.gel4b5r -.ge3lu -.ge3na -.gen6aug -.ge3ne -.gene5cke -.gen4ei -.ge3nu -.ger4a -.ger4e -.ge3ro -.ge3ru -.ge3sa -.ges4e -.ges4i -.ges4p -.ge5stak -.ge7stei -.ge3t4u -.gi2u -.gla4s5c -.gol6der -.gs2 -.gu3ru -.hafe4 -.haf6ter -.haft5s4 -.hal4br -.han4a -.han4f -.hang5s -.ha5sche -.hauser6w -.hau4su -.haut3 -.hau4to -.he2r -.he5rae5 -.her6b5ra -.he5rer -.he3ri -.he6r5inn -.he5ros -.he4sp -.hete3 -.he3xa -.hi2n -.hin3a -.hin3u -.hi2p -.hi4s -.hitze5 -.ho4f5en -.hof3s4 -.ho4la -.ho4met -.ho3ra -.hor4t5r -.höch4s -.hu4fer -.hu3la -.hund4 -.hu3s4a -.ia2 -.ibe3 -.ima4ge -.imm4 -.immen4 -.in1 -.ind2 -.ine2 -.in3eg -.in3i -.ink4 -.inse4l5 -.in5skr -.inu3 -.io2d3 -.ioni5e -.ire3 -.is2a -.isar3 -.it2h -.joni3 -.jor3 -.kabe4 -.ka4b5l -.ka3la -.kal4ta -.kal4t5r -.ka4t5i4o -.ke3ra -.ker4bl -.ker4na -.ke3ro -.ke2t -.ki2e -.ko5be -.ko4ter -.kraf4 -.kraft5 -.ks2 -.kue3b -.ku4ran -.kü3b -.la5ert -.lage4n5 -.la3ho -.lan4d5a -.lan6de6mi -.lan4dr -.lan4gl -.lan4g5r -.lang5s -.lan3t -.lase4 -.las4t5r -.le4ar -.leder5 -.le4gas -.lei4bl -.lei4br -.leider6 -.lei6derf -.lei4tr -.le3n4i -.len4k5a -.len4kl -.len4kr -.les3t -.le3th -.lich6ter6sc -.li2f -.li5gn -.lin4kl -.li3o -.li4ve. -.lo4g5in -.lo4sc -.losch5 -.lo4sk -.loss4 -.los3t -.lo3th -.lo4tr -.lo5ver -.lus4t5e -.luster6 -.lu5t4h -.ly2s3 -.ma5lae -.mal5e4c -.ma5len -.mal4z5a -.man3d -.mani4e -.man5t -.mas4s5u -.ma4st -.ma4ß3u -.mat4c -.mate3 -.märz3 -.md2 -.mee4ru -.mel4a -.me3no -.men6schl -.men6schw -.me3n4u -.me3nü -.me4sa -.mes4sp -.mes4st -.me4to -.met6tend -.mi2e -.mie4t5r -.mi2f -.mil4z3 -.mi4s3a -.mi6scha -.mi6schei -.mi6sch5l -.mi6schn -.mi6scho -.mi6sch5r -.mi6sch5w -.mi2t -.mi4t3a -.mit3e -.mit5in -.mm2 -.mo2i -.mo4nau -.mon6dru -.möbe4 -.musiker6fo -.nabe4 -.nae4s5c -.na4ha -.nah5ei -.namens5 -.na4mes -.nas4s5c -.na3t4h -.na3to -.nä4s3c -.nebe4n -.nebens6 -.neider6 -.nei6d5err -.nere3 -.ner4f -.ne3ro -.ne2s -.neuer6b -.nich6t5e -.ni4e -.nie5r -.ni2l -.nil3a -.no6tentr -.no4t3h -.no4top -.not3r -.nude4 -.nul6ler -.nus2 -.nu4t3r -.oa3s -.obe4n -.obe6r5ei -.ob6ers -.ob3i4t -.ob3l -.obs4k -.ob4sta -.oe2 -.oh4r5ei -.oh4rer -.oper4 -.orge4 -.orgi3 -.ort4s5a -.ort4s5e -.ort4st -.os4t3a4 -.ost5e4c -.os4t5ei -.os6t5en6de -.os6teng -.oste6r5e -.os4tes -.os4ti -.osto4 -.ost5om -.ost5ran -.ost5ri -.ot3a -.ou4te -.ozo2 -.ölk2 -.pa3b2 -.pa4nen -.par4ka -.par4k5l -.par5th -.pas6sers -.paulet6 -.pe4c -.pe3la -.pel4za -.pel4zw -.pe3na -.peri3 -.pes4t5e -.pf2 -.pf4u -.ph2 -.po4lei -.po4p3l -.postei6 -.prach4 -.pu5ert -.ra5ch6e -.ra4d5ei -.ra4der -.ra3el -.ran4d5r -.ran6g5e6b -.ran4gl -.rar4s -.rau5f4r -.rau4m3 -.rau6schl -.rau6schw -.rau4se -.re4b3l -.re5cha -.re5chr -.rei4bl -.re5insz -.reise5i -.rei4st -.reli5e -.ren4n5a -.re3sk -.rester6 -.res6t5ers -.re5stit -.rest5re -.re3un -.ri4as -.ri3so -.ro2a -.roe4s -.roes5c -.ro4h3a -.roher4 -.ro6h5erz -.rom4a -.roset6te. -.rost5e6c -.ro4tan -.ro4tau -.rot5ei -.ro4t3r -.rö2s -.rös3c -.rue3b -.rue6ck5er6 -.ruh4r3 -.ru4m3e -.rump6f5en -.runder6 -.rü3b -.rü6ck5er6 -.sa3br -.sa4fe -.sali5e -.sami3 -.san6dac -.sau3c -.sch4 -.scha4c -.schaf6t5end -.scheiner6 -.sco2 -.se2a -.se2e -.see3i -.seein4 -.see5r -.se4ha -.seh5ere -.se4hi -.se4h3o -.sen4f -.senf5l -.senf5r -.sen4kr -.se5no -.sen3s -.se3ro -.se3r4u -.se2t -.sex3p -.sha2 -.si2e -.siege6s5 -.si5err -.si3es -.silbe4 -.si4te -.sit6zer -.ski3e -.ski3s4 -.sli2 -.so3n4a -.sour4 -.spani5er. -.spiege6lei -.st2 -.st4a -.sta4ge -.stau6be6cken. -.st4e -.ste4h5a -.ste4i -.steiner6k -.ster6na -.sto4n -.sto4re -.st4r -.stro6ma -.su6cherf -.su6cher6g -.sue3b -.sumpf5l -.ta3c -.tage4s5 -.ta5lar -.tan4k5a -.tan4k5l -.tan4ko -.tan6z5er -.ta3ra -.tart4 -.ta4tab -.ta4t3h -.ta4top -.ta4tor -.ta4tri -.ta4t3u -.tau4f5o -.tau6schl -.te2e -.te2f -.tehe5 -.tei4l5a -.tei4l5ä -.teiler6s -.tei6l5ersc -.tei4l5i -.teils4 -.te5in. -.te5le -.telle4 -.te3ra -.te4st -.test5ae -.test5ä -.te6ste -.test5ei -.tester6h -.test5r -.te4te -.te3xa -.texter6 -.tex4tr -.ti2a -.tie6f5ern -.ti4me. -.ti4mes -.ti2s -.tischen6 -.tisch5end -.tisch5l -.tisch5w -.tit2 -.tit4a -.tite4 -.tode4 -.to4der -.todes5 -.to4nar -.to4nat -.tonen4 -.to6n5eng -.toner6n -.toner6z -.to4nin -.tons4 -.to4pan -.top3e -.to4pl -.to4pr -.tor5an -.to4rei -.to4th -.to4t3r -.to2w -.tow3e -.tra4de -.trag5s -.tri5es -.trost5 -.tsa3 -.ts4c -.tse3 -.ts4u3 -.tu4e -.tu5end -.tu3ri -.tur4m3 -.ub2 -.ue2 -.ue4b -.ufe2 -.ufer3 -.uh3l -.ul2b3 -.ult4 -.um1 -.ume2 -.umo2 -.ums2 -.una2 -.unab3 -.un3dr -.une2 -.unen4t5 -.un3er -.un3g2 -.unge5n4 -.uni4t -.unk2 -.un3s4t -.ur3a4d -.ur3af -.ur3a4m -.ur4anb -.uran6fa -.ur6anfo -.ur4anh -.ur4anp -.ur3at -.ur3c -.ur3e -.ur4e3t -.ur4i -.ur3o4m -.ur3o4p -.uro3s -.ur3s2 -.ut3r -.ves3t -.vi3d -.vol6lerw -.wah4l3 -.wah4n3 -.wanderer6 -.ware4 -.waren5 -.wa2s -.we4b5ei -.weg3s4 -.weh4r5u -.weine4 -.wei6neb -.weines6 -.we6ing -.wei4ta -.welter6k -.welt5s -.wer6k5er6 -.wer4k5r -.wer4tr -.wese4 -.wetterer6 -.wi2e -.win6d5er5 -.winte4 -.wo2r2 -.worten6 -.wort5end -.xan3 -.xe1 -.ya2l -.zeit5r -.zeit5s4 -.zel4l5a -.zelle4 -.zel6leb -.zel4li -.zel4th -.ze3ro -.zi4e -.zie4l5u -.zin4ka -.zin4s5c -.zin4st -.zi6schl -.zol6lers -.zuch4 -.zu5ende. -.zu4gei -.zu6g5end -.zu4glo -.zu4g5ra -.zug5st -.zurue4 -.zurü4 -.zwe2 -.zweigen6 -.zwei6g5end -a1ab -a2aba -aa2be -a3ace -a3achs -a1ae2 -aa2gr -aags2 -a3akt -aal3ac -aal3an -aal3ec -aa4lei -aale3s -aalf2 -aal5la -aa2l3o -aal3ö -aals4t -aal5ste -a1am -2a1an -a3an. -a2anä -aan3e -a1a2q -aa2r3a -aa3re -aa4r3ei -aa4r5ers -3aarg4 -aar3in -aa4rit -aa4r3oe -aar3ö -aarp2 -aar3t -aar3z -3aase -a3ass -aa2su -aata2 -aa2t3h -aa2t3r -aat2s3 -aatsche6 -a1au -a1ä2 -2abah -abais4 -ab4ak -2abal -ab3alt -ab2am -2aban -2abas -4abat -abat4t5a -ab3auf -ab1ä -2abäd -2abär -ab2äu -abbe4p -4abbine -abb5rig -1abd -abda3c -2abe. -2abec -ab3eic -abe3id -ab3eil -abein4s -abein4t -4a3b4el -abe4l5ac -abel5a4d -4aben. -abe4nin -a4berd -aber4ka -ab3erz -aber4zi -aber4zo -aber4zö -2abes -abes4p -ab5esse -abes4t -2a3bet -1abf -1abg -1abh -4abil -ab3inf -ab3inh -ab3ins -ab3iso -abi3s4t -1abk -ab1l -a2bla -ab3lag -3ablas -3ablau -a4bläu -4ab4le. -3a4bled -a4bleg -3ablei -4a3blem -4ab4ler -5ablesu -4ablet -a4bleu -a4blin -a4bloe -ab4log -a2blö -1abn -ab2o -2abod -2aboe -2abol -a2bon -abor5ang -abo5rato -abor3s -2abot -2ab2ö -ab1r -a4b3rec -ab3rie -ab4rot -2abrö -3abruf -1abs -2abs. -4absaer -4absap -4absar -4absär -ab3sc -ab3se -2absf -ab3so -4ab4sof -abs2p -4abspi -abs4pr -4abst. -ab3sta -ab3sti -ab4s5tro -3abtei -abtes3 -2abu -a4bueb -ab4ug -a2bum -a4bunt -ab3usu -a2büb -1abw -2aby -1abz -ab5zun -2ac. -2aca -1ac1c -a3cee -a3cem -a3cen -2ach. -ach3ab -ach5aen -ach3ak -ach5all -ach3an -a5chari -ach5art -ach3as -ach3au -ach3än -2achb -a3che -a4ch5e4ck -ach5e4he -ach5eic -ach5ein -ach5eis -a4ch3ep -ach5erbe. -ach5erben -ach5erbes -ach5erfu -ach5erfü -ach5erke -ach5erkl -ach5er6kr -a4ch5erl -a6cherne -a4chero -ach5eroe -a4ch5erö -acher6öf -a4ch5erw -a4ch5erz -ache5str -ach5eta -a4ch3eu -2achf -achgene6r -2achh -4achi -a3chi. -ach3i4d -a3chig -ach5ind -a3chip -ach3ir -2achk -2ach3l -ach5le -2ach3m -ach5mi -ach3n -2acho -a3cho. -ach3o4b -ach5ord -ach5org -ach5o4ri -a3chov -ach3öf -2achp -2ach3r -4achs. -4achsc -4achses -4achso -4achsp -achst4 -ach5stre -ach3su -ach5sze -ach4tak -ach6tant -acht5erfu -4achtg -ach4t5in -4achtk -ach6traeume. -ach6traeumen. -ach6träume. -ach6träumen. -ach6trit -4achtsc -achts6tu -ach4t5um -ach3u4f -ach3uh -a3chus -2achv -2ach3w -a1ci -acin3 -4ack. -a4ckeg -4a3cken -a4ckerd -4ackes -a4ck3in -2ackl -4acks -acksau6s -ack5sta -acksta6g -ack5sti -2a1cl -a3co -acon4n -ac2p -acre2 -2acu -2ad. -4ada. -ad4ab. -ad4abr -a2dad -a2dae -ad2ag -a2d3a2k -ad3ang -3adap -ada4rer -a6d5ar6ten -4adas -a4daut -1a2dä -ad3än -ad1c -2a3de. -ade4al -ad5eben -2a3dec -ade3e -adelei6n -adel6spo -3adelu -2aden -ade4nat -adenes4 -adenk4r -aden4se -ade3o2 -2adep -ade5ram -a4derg -a4derig -ader4sa -a5deru -2ades -ade5s4p -ades4s -ade5sta -ade5stel -ade5str -2adf -adgas3 -2adh -adie4s5c -adie6s5el -ad3inf -1adj -2admu -a2dob -ado3c -ado4n3a -adon3e -a2dop -ad4o3s -adot2 -2adp -2adq -adrat5s4 -a2dre -3adres -ad4ret -4adro -ad3rot -ad3rue -ad3run -ad3rü -2ads2 -3adsor -ad5stu -2ad2t1 -adte2 -ad4tel -adto2 -adt3re -a2düb -1adv -2ae. -aea2 -aeap2 -ae2bl -3aebte -aech4sa -ae3chu -2aeck -aeck5sp -ae2da -ae4ders -aede3s4 -ae4d3ia -aedi4e -ae2dr -2ae1e -aee2k3 -ae2f3l -ae2fr -aefts3 -2aeg -ae2ga -aeg3an -ae4gent -ae2gl -ae2gr -aeg3ri -aeg5sta -aeg5s4tr -3aegy -ae2ha -4aehe -ae4hei -ae4hel -ae4h3in -aeh6lein -4aehm -3aehnl -2aehr -aeh4rei -2aehs -2aeht -2aei -ae3im -a3ein -ae5isch. -ae5ischl -ae5ischs -ae5i6sch5w -ae3isk -a3eiw -ae2kr -a3el. -ael4bl -a3elh -ae4lia -aelk3s4 -a5ella. -ael4lak -a3ello -ael4pl -a3els. -a3el4sa -a3elsb -ael6schl -a3elsf -a3elsg -a3elsh -a3elsk -a3elsl -a3elso -ael4st -a5elsta -a3elsv -ael3te -aelte5i -4aemm -aemte4 -2aen. -2aene -aene4n3 -aen2f3 -4aenge -2aeni -aen3k4e -aen4kl -aenne4 -aenner5 -2aens -aen4s3a -aen4s3c -aen4sp -ae3nu -2aenz -ae2o3p2 -5aepfel -a3e2pi -1ae2q -2aer. -aer3a2 -3aera. -ae3r4af -ae4rap -ae4rat -aerauto6 -aerb3l -aer4ble -2aerd -ae4r3ei -ae4r3e4l -aere4m -ae4r5emi -ae4r5erf -ae4r5erh -aerer4k -aer5ersa -ae4rese -aeri4e -ae4r5ing -4aerk -aer4mau -aer4m5in -aer6munt -ae3ro -3aerod -ae4rop -ae4ror -aero3s -2aers -a3ersa -aer4seh -aers5er -4aert -aert6er -aert4s3 -ae4rue -2aerw -a3erwä -aer6z5en6t -aer6z5err -2aes4e -ae3se3i -aese5rei -aese5ri -ae3s4kr -ae2sp -2aess -aes4s3c -aess5erwe -aes4so -ae4st -aes3te -aestein6 -3aesth -aeß5erk -aeß5erw -ae4t3a -aeta4b -aet4eb -aete3e -aeteo4 -aet5erbl -ae2th -4aeto -ae4tob -ae4t3op -ae4t3ra -ae4t3ri -ae4t3ro -aet4sen -aet4ste -aet4sti -aet4str -aett3s4 -ae2u -aeu4eri -2aeuf -3aeugi -5aeugle -4aeul -2aeum -4aeur -aeu5re -4aeus. -aeu6scha -aeu6schr -aeu6schw -aeu4sp -ae2vi -a1e2x -2afa -a4f3aen -a2f3a2n -a2fap -a2f3au -2afä -a2f3än -2afe -a2f3ec -a2f3ef -a4f5einh -a4f5einr -afel3e -a4fentl -a2f3e2p -afe4re -afe2s -a4f3ese -a2fex -aff4a -3affär -af4fei -3affek -af4flu -aff2r -2afi -afi2e -afie3i -afi6kanz -afi4kat -afi4k3l -a4f3ins -afi2t -2afl -af3lae -af3lau -af3lä -2afo -a4fober -a2f3oc -a2fop -a4ford -a4f3ort -afo3s -a2f3o2v -2afra -af3rae -a3frag -af3rat -af3rä -af3rie -a3fris -2af2ro -af3roe -af3rö -af3run -af1s -af3sa -af2si -afs4ke -af4soc -afs2t -af4t3ak -aft5anz -af4t3au -af4tei -af4t3e4l -af4t5ent -aft5erfa -aft5int -af4toe -af4t3op -af2tö -aft5rad -af4t5res -af4t5ric -aft4sei -aft4sel -aft4spi -aft6stic -af4tur -2afu -a2f3um -a2f3ur -2afü -2aga -ag3a2d -a2g3au -ag3ärm -agd3an -ag4d3ar -ag4del -ag4det -ag4din -ag4dre -ag4dro -4age. -ageb2 -age3id -age5ind -age5inf -age5inh -age5in4s -2agel -agel5d -agelei6s -a4gelem -age4len -age4ler -4a5gen. -age4nas -agend4 -age4n5e4b -a6generg -agens4p -5a4gent. -agenta6bl -5a6genten -a4gentf -age4rad -age4r5af -age4ral -age4r5ap -age6rauf -agerei6g -ager6eign -agerer6f -age4s5am -age4s5an -age4sau -ages5ka -age4s5oe -age4sö -ages6sen -age3u -3aggr -a2g3i2d -ag3ill -a2g3im -ag3inn -ag3ins -agi3oe -agi4os -2agl -ag4lan -ag3lat -a2gli -a2glö -a2glu -ag2n2 -a2gnä -3agog -ag3opf -agori4 -ago2s3 -a4g3rad -a2gre -a2gri -ag3rie -ag3rin -a4g3rum -ag2sa -ag3s4ah -ag2se -agsen4t5 -ag4s3in -ag5span -ag5s6port. -ag5s6porta -ags5taet -ag4stät -ag5stel -ags4top -ag5stras -2agt -aguet4 -a4gund -2a1h2a -ah3ang -a1hä -ahdi3 -a1he -a4hein -ahein6se -a4h3er4h -aher4ku -a4h3er4w -ahe3s -a1hi -ahin5d -ah2l3a2 -ahl3än -ah4l3e4b -ahl3ec -ah4l3ei -ah4lel -ahlenk4 -ah5lenz -ah4l5erd -ahl5erf -ahl5erg -ah4l5er4h -ah4l5erz -ahli4n3 -ah2l3o2 -ah2lö -ahl3sa -ahl4sal -ahl5se -ahls4pa -ahl3sz -ah4m3ae -ah4m3ar -ah2m3ä -ahme3e -ahme3i -ahmen5e -ahmen7sc -ahme3s4 -ah3mu -ah2n3a2 -ah4nas -ah2n3ä -ah3nee -ah4nef -ahner6e -ahn5er4h -ahn5erk -ah6nerle -ahner4n -ah4n5erp -ah6n5ersa -ah6n5er6sc -ah4n3eu -ah4nin -ah2no -ahns2 -ahn3sa -a3hom -a3hop -3a2hor -ah3ost -a3hot -a1hö -ahr3a -ahra4b -ahra4g -ah3rai -ahr5eig -ahren6se -ahren6s5o -ahr6ere -ahrer6lau -ahre4s -ah3ri -ahr4tae -ahr4t5ei -ahr4t3h -ahr4tin -ahr4t5ri -ahr4tro -ah2ta -ah4t3e4l -aht5ero -ahte4s -ah4t5ese -ah4t3ex -ah4tis -aht3re -ah4t3ri -aht3s2 -ah4t3um -a1hu -ah3w -a1hy -ai1a2 -a3ia. -aian3 -aidi2 -ai3dr -aid2s -ai1e2 -aien3 -aien4de -ai3er3 -aif2 -ai3fr -aig2 -ai5gel -a3ik. -ai4k3er -ai4lar -ai4l5auf -ai4lei -ail5er4l -ailge3 -a3im. -a3imp -2ain -ai3nac -ai3nal -aind2 -a3indu -ai4neb -ainen4 -ai5ner -a3info -ains4p -a4inv -ai1o -3airb -3airl -airo3 -air2s -ai2sa -ais3ac -a5isch. -ai4schi -a5ischs -aiser5a -ais4o -ai3spr -aiss2 -ais4se. -ais4sem -ais4ses -ais3t -ai3s4tr -ai3ti -ait4r -a3iv. -a3ivl -a3ivs -a2jat -2ak. -2a3ka. -aka3b4r -a4kade -2akae -a3kal -2akam -2akan -3a4kanl -2akar -aka4tak -aka4tan -aka4t5er -aka4t3r -akat3s -3a2kaz -2akä -2akb -2akc -1akd -2a1ke -a2kef -ak3eis -3a2k3em -ake2n -aken3e -a2keo -2akf -2akh -2a1ki -ak3ins -aki3p -1akk -4akko. -2akl -a3klec -ak4lin -2akm -3a2kne3 -2ako -ako3c -akoni4e -3akox -2akp -2akra -2akre -2akri -ak2ro3 -a2k3ru -a2k3rü -2aks -ak3sh -ak3sta -ak3ste -ak4t3a4b -ak4tag -ak4t3ar -ak4t3au -2aktb -akter5a -2aktg -ak2th -4aktik -aktionsche6 -4aktis -2aktm -ak4t3ob -ak4toe -ak2tö -ak4t3re -ak4t3ro -ak4tru -4aktsi -4aktsp -4aktst -4aktun -2aktw -2akul -2akun -2akur -aku2s3 -2akw -1akz -2a3la. -al3abb -4alabo -a4l3abt -a3lad. -al3add -al3adm -al3aen -al3af -2alai -ala3kl -al3akr -al3ame -alami3 -al3ana -a3lanc -4al4and -a4l5ande -al5angel -al5angr -alang5s -ala3ni -al3ank -al3anm -al3ann -al3ans -al3anz -al3app -a3lar. -al3arb -al3arg -a3lari -al3arr -al3art -a3las. -alaska5 -al3asp -a3lat. -ala4tan -al5attr -alat3z -al3au -a3l4au. -alauto6r -a3lay -a1lä -a2l3än -al3ärm -al3äu -alb5aer -al4bär -3albb -4albe. -alb5eink -al4b5erh -al6b5er6sti -al4b5er4w -al4b5eta -alb5ins -al4ble -al4bon -alb5rae -al4b3rä -alb5rin -alb5ros -alb4spe -3album -al1c -alda3c -al2dä -5aldehy -ald5enkl -al4d5ere -al4d5erh -al4d5erl -al4d5ern -al4d5erw -al4d5ese -ald5inn -al4d3ot -al4drä -al4dro -ald3s4t -al4duf -2ale -al3e4be -alebran6 -a2l3ef -al3eie -a4l3eig -a4l3ein -a4l5eise -al3ele -al3elf -3a4lema -a4lemb -ale4mis -3a4l3emo -al3emp -alende6r5 -a4l5endr -a4l5ends -a4l5endu -a4l5engl -alen5in -alent6ei -al5enth -alen6z5ei -al3e4po -a4l5erfo -al4erg -alergene6 -a4l5erhe -a4l5er4hö -aler4kl -aler4kr -a4l5erlä -a4l5er4mi -aler5oe -a4l3erö -3alerr -a4l5erse -a4l5er4tr -5alerwa -5a4l5erwä -a4l5erze -a4l3e4s4k -a4less -a4l3e4th -a4l3eur -al3exi -al3ext -a1lé -al5gas -algene6r -3algor -alg2r -alig4h -al3imm -al3imp -a4l3in4q -alin6stin -al3int -al3inv -ali3o -al3ira -al3irr -ali3sp -ali5str -a4l5ital -al4k5enz -alk5erd -al4k3lä -al4klö -al4klu -al4kne -al4kof -3alkoh -al4k3rü -alk3sa -alk3se -alks4i -alk3sp -alk3st -al5lad -all5ank -all5ano -all5ans -all5anz -al4l3a4r -alla4s -al4lass -al4laus -3allee -all5eim -all5erfa -all5er6mu -all5ermü -al6lersa -al4l5er4z -al3les -al4lev -3allgä -3allia -al4l3id -alli5er. -alli5ers. -all5ink -al4l5oef -alloe6se -all5ora -al4l3öf -al4l5ö4se -all6s5amt -all4sin -all4sph -al4lum -alm5aus -almo6de. -al3n2 -2alo. -alo2b -al3obe -al3obl -al3obr -al3obs -al3oe4d -al3oer -a2lof -2a3log -alo4g3a -alo4gn -alo4gr -alo5is. -a3lop -al3ope -al3opf -a4l3orc -al3ort -2alos -al2ov -alo2w -a2lö -al3ö2d -al3ör -3alpe. -6alphas -alp3st -als5aer -alsau4g -alsch5s -al3s2e -als4pac -al5s6temp -al3sti -alt5alg -al4t5ana -al4t4an4b -al4t5ans -al4tap -alt5auf -alt5eic -al4t5eig -alt5ein -al4t5eis -alt5elt -alter6fol -al4t5eti -al4teu -al4tid -al4tir -alt5ita -al4toe -alt5ort -al2tö -al4t5rat -alt5ric -alt5rit -alt5roc -alt5ros -al4trus -a1lu -a2lum -al3umb -3alumi -a2lur -a4l3use -alu3ta -a3lüg -2aly -al4z3ap -al4z3ar -al4zet -2am. -ama3a -am3a4ba -ama3d -4amae -am3aep -ama5ers -ama5ge -3a4malg -am4an -am5ang -aman6the -aman4tr -am3apf -amas5z -a2maz -2amä -3ambiv -2amc -2amd -4ame. -am3ein -ame5len -a4memo4 -ame4n -amen3e -amen3i -amen4s5e -amen4su -amen6tarb -am2er -amer4a -amera5u -a4m3erf -ame3r4u -a4m3erz -a4mesh -ames4p -a4me3st -2a3met -a2mex -2amf -2amg -ami4kan -ami4kl -ami4kn -ami4n5an -amine4 -ami3o -amis4tr -4amix -2amk -2aml -4amm. -am4maa -amm5aba -am4m3ac -am4mal -amm5ang -am4map -am4mar -amm5ass -am4m3au -amm3ei -amme4la -amme4n -am6m5es6sen -am4m3id -4amml -am4m3oe -am2m3ö -4amms -am4munt -ammu4t3 -a2m3oc -amo2k -amotte5s -am2pf -amp4f3a4 -amp6ferr -ampf5la -ampf3o -ampf3r -amps2 -3ampul -2am2s -am3sau -am5sche -am3se -ams3en -am3so -am3spa -am3spi -am3s4tr -am3su -am2t3a -amta4b -am4tän -am4t5eig -am4t3el -4amtem -amter4 -am4t5erb -am6t5erei -am4t5erf -am4t5erg -am4t5erh -am4t5eri -am4t5erk -am4t5erl -am4t5ern -am4t5err -am4t5ers -am4t5ert -am4t5erz -am4t3ex -am2th -am5tig -amt5int -am4tis -am4tit -amto2 -amt3ob -am4toe -am4t3op -am4tou -am2tö -am4t5rat -amt5rei -amt5ric -am4tro -3amts. -3amtsd -am2tu -amt3ur -2amtv -2amu -a2m3uf -3a2mul -a1mü -2amv -2amw -a1na -a3na. -ana2a -a2nab -4anac -ana5che -anadi5er. -an3aes -an3aeu -ana4l5in -an5al4ph -anal5sk -3analy -ana3m -an4and -an3a4no -an3ant -2anar -a4narb -an4are -an4arr -a3nas -ana4se -a3nat. -ana4ta -ana4t3r -a3nats -3anäm -an3är -3a2n3äs -an3äu -1anb -4anban -4anbas -4anbib -2anbo -2anbu -2anbü -anche4n5 -an3cht -2and. -4andaa -4andab -3an3d4ac -and5achs -4andal -and5amm -4andan -and5anz -an4d5arm -an4d5ass -4andat -an4d5att -4andaw -2andd -4ande. -an4d3ei -and5elfe -an6den6ga -and5ente. -ander5ei -anderer6le -and5erhö -ande4sa -ande6spo -andes5ta -an4d3ex -2andf -2andg -andi4e -ando3s -and5ott -and5rai -an4drau -an4drec -an4d5res -2ands -and4sam -and4seh -and6s5ein -and6spar -and6spas -and6spat -and4sph -an4düb -2andw -andy3s4 -an3e2c -a3n4ei. -3aneig -a3neip -an3e2k -ane2l -an3elf -an3e4mi -3a4nemo -ane2n -anen3e -anen4ze -anen4zy -aner4nä -a4n3eru -a4n5erz. -an3eth -a2n3e2v -1anf -2anf. -4anfab -an4f3ak -an5fes -2anfi -anfi4n -anfin5i -anf5rau -2anfs -an3f2u -4anfun -4ang. -4anga. -3angab -ang5art -4angas -ang2ä -an3gäh -an4gäs -4ange. -5angebo -5angeho -5angehö -ang5eig -ang5eis -3angek -an4g5erf -an4gerg -ang5er6he -an4g5erk -an4g5er4p -ang5ertr -an6g5erwa -an4g5erz -3angez -4angie -ang4lok -angmi4 -ang3n -3angol -ang5rad -ang5ras -an4grau -5angrif -4angs. -ang5sat -ang6st. -ang5sti -ang3t2 -1anh -4anhau -4anher -4anhex -2anhu -2anhü -2ani -4ani. -4anie -aniel4l -ani5els -ani5ers. -anie3s -ani4ka -ani4ki -ani4kr -3a2nim -an3i4on -a4niso -ani5sta -4ank. -an4kag -ank5alt -ank5anz -4anken -ank5erfa -4anki -an4kid -an4k5las -an4klö -an4k3ob -4ankon -ank5ori -an4k3öf -an4k5ras -an4kro -ank5rue -an4krü -4anks -ank3s4k -ank3s4p -ankt2 -an4küb -3ankün -1anl -4anlad -4anlan -4anman -3anmel -3anmie -3anmu -4anmun -anna4ge -3annah -3annä -an4nef -annens4 -anne4s -3annex -ann4spa -2annt -3anod -an3ode -anoe4d -an3ofe -2anog -a3nol -ano4l3a -an3oma -ano4m5en -an3opf -an3opi -an3or -ano5rak -3a4nord -a4no4ri -anor3t4 -a3nos -an4ou -a3nov -a1nö -an3ö2d -1anp -4anpaa -4anpag -2anpo -1anr -4anrad -2anrö -an3s4ab -an4s5aem -an4s3al -ansa4m -an4s5ame -an4s5amt -an5s4au -3ansäs -ansch4l -an4seu -3ansic -ansicht6 -an4sim -an4spo -ans5pon -3anspr -5anstal -an4stem -an4s5ura -ans2z -an3tac -an4t5a4go -ant5ang -ant5ans -an4t3ar -anta4re -3antá -3antei -anteri4 -antes3 -ant5e4ta -5anthro -3antib -5antilo -ant5inst -3antiq -5antise -3antr -ant5räu -2ants -3antw -a1nu -anu5ell -a3nup -a3nur -a1nü -1anw -2anwi -any3l4 -an4z5aep -an4z3an -an4z5art -an4zau -an4zäp -2anzb -an4z3e4d -5anzeig -anz5elf -anze4n -4anzes -anze4t -anz5eta -2anzg -2anzh -an4z3i4d -anzi4n -an4z5inf -an4z5ins -anz5int -2anzk -2anzm -an4zol -2anzr -2anzs -2anzt -5anzuen -3anzün -an4zwä -an4zwi -2ao -a1oe2 -a1of -ao2fe -ao1i -a3okt -a2om -a1op -ao2pe -a1or -a3orm -aor5te -a1os -ao2sk -aos2p -aos3t -a3ot. -ao3t2s -2a1ö2 -2ap. -ap2a -apa3b2 -a2ped -a3pel -apen3 -ape4ra -a2pev -2apf. -ap2fa -4apfe. -3apfel -4apfen -2apfs -2aph -aph5aer -aph3är -aph5ers -a2ph3t -2a1pi -api2e -apie5l -apie3s -2apl -a3pla -a3plä -ap3li -ap2n -ap2o -2apol -apo3p2 -a5posi -apo5sta -apos5te -apo5str -a2pot -3appar -ap4pei -ap2p3f -ap4pre -ap4p3ru -a1pr -ap4san -ap4sin -ap2s3p -apst3o -apst3r -apt2 -a4que. -2ar. -a1ra -a3ra. -ar4a3a -ar3abb -ar3abt -ar3adr -arad4s -2arae -ar3aer -ar3aes -araffi4 -a5rafo -arafor4 -ar3agg -arag4l -ar4ah -ara3i -4aral -ara3le -ar3all -ar3ang -a4ranr -ar3ans -a4ranw -a4r3anz -ar5apos -a4r3app -2arat -ar3atl -ar3atm -ar3au -arauto6r -ar3ax -2a1rä -ar3äs -ar4bab -4arbe. -4arbef -3arbei -ar4b5eig -ar4b5eim -ar4b5ein -6arbeitr -4arben -arb5erl -4arbi -ar4b3le -2arbs2 -arb3sk -2arbt2 -2arbu -1ar1c -4archl -4archr -ar4d5anz -ard5arm -ar4d3ed -arde4n -ar4dob -ar4dop -ar4dro -ards2 -2are. -a2rea -are5aler -a2reb -are5ba -2arec -a2re3e2 -a2ref -ar3eff -areg2 -ar3ehr -a4r3eid -ar3eig -areim5 -a4rein -2arem -ar3emb -a4remi -4aren. -a4renah -arens4 -aren6sem -a4r5entn -aren4zy -a2reo -4a5rer. -are3r4a -arerau5 -a4r5erei -a4rer4fo -a6rergeb -a4reri -a4r3erk -a4r3erl -a4r3ert -a4r3erz -arer4zi -2ares -a4r3es4k -ares4p -a4r3es4s -are5sta -are5str -2aret -are5the -a2reu -a2rev -arfs3a -arf4s3p -ar3fu -2arg. -ar4g3e4c -arge6l5er -ar2g3n -4argo -ar5gre -a1r2i -2ari. -2aria -arie4n3 -ari5er. -ari5erbu -ari5erd -ari5erg -ari5erh -ari5erp -ari5ers. -ari5ersc -4arif -arif3t -ar3imp -2arin -arin5it -arin3s4 -a4r3int -ar3inw -2a3rio -a3rir -4arisc -ari4su -a4rith -2a3riu -3arkad -ar4k3ak -ar4kal -ark5aue -ar4käh -ar4k5erh -ar4k5er4l -arker4n -ar6k5erne -ar6k5er6sc -ar4k5erw -ar4kid -ar4k5inn -ark4lö -ar4kne -ar4koa -ark5rin -ark3sa -ark5she -ark5spe -arks4t -ark5stu -ark4ta -arkt5a4g -ark4tel -ark4t5ex -ark4top -ark4tre -arku2 -ar4k3um -ar4küb -arlang5 -ar4les -arlichter6 -arlich6ters -arl2s -arma3l -ar4m5ans -ar4m5anz -arm5art -5armatu -arm3au -ar4meb -arme4e -ar4m3e4g -arm5ein -5armeni -ar4meo -ar4mep -ar5mes -ar4m3op -2armö -arms2 -ar4mum -3armut -2arn -ar4nan -ar4nap -arn4au. -arnte4 -a1ro -ar3ob. -4aroc -aro6ckeng -ar3o4di -2aroe -ar3oem -2arof -ar3ofe -ar3off -aro2i -2arok -2arol -ar3o4no -ar3o4pe -ar3opf -2a2r3or -aros5t -2arot -arot5an -aro4to -a4roun -aro2w -a2rox -2arö -ar3öm -2arr. -arr5ach -5array -2arrä -ar4r3e4b -ar3rei -ar6r5ei6sen -arre4n -arren5e -4arri -ar4r3or -2arrs -2arrt -2ar3s2a -ar3sä -ar6schab -ar6sch5ac -ar5sche -arsch5erl -arsch5lä -arseri4 -arsex3 -ar3s4ha -ar3si -2arsk -ar4ska -ar3sp -ar3sta -ar3stä -ars5tät -ar5stel -ar5stern -ars6t5ers -ar3str -ar3su -ars4ue3 -artal4s5 -ar4t3am -ar4tang -art5att -art5auf -art5elf -arter6fo -arter5k -arter6la -3arthr -4artie -3artig -art5int -ar4t5iso -ar4t3ob -ar4t5ram -art5ren -art5rie -ar4t5rol -art5ske -a1ru -arude4 -aruder5 -a2r3uh -a3rumm -ar3ums -2arü -a3rüc -a3rüm -arwa4s -2ary -ar4z3ae -ar3zan -ar4z3au -ar2zä -ar3zäh -arze2 -arz5elf -arz5ente -arzer4 -ar4z5erl -2arzi -3arzn -3arzt -arz4th -arz4ti -arzt5ra -arz4tri -ar2zw -asa5chi -as3aeh -as3aet -4asaf -as4ai -asal4t5e -as3amp -asa5ne -a2sap -asa4ro -asa2s -as3asc -a5sau. -asau4f -as3aug -as3ät -a2sca -as5cham -a5scher. -ascher6g -a3schi -asch5mi -asch5ru -4ascht -a4schwu -a4schwü -a2sco -2ase -a2sec -a2s3ef -as3eie -as5elef -a3sen. -ase4na4 -asene4s -ase6n5esc -asenk4 -ase4n3o -a6sen6sem -asens4p -aser3a -aser5eig -ase4ren -as4erh -as4es -as3eva -a2sex -asex3p -2asf -a3s4hen -a3s4hig -3asiat -a2s3i2k -as3imp -as3ind -asing3 -as3inn -as3int -asi4s3a -a2sk -as5kla -aslan4 -a3slip -a2so2b -as3oe -aso2f -as3ofe -a3sol -a3som -a2s3o2p -as3orc -as3org -as3ori -as3pan -aspek6t5o -aspi5en -as3pik -as3pio -a4spir -as4ple -as4por -as4pul -as5s4abl -as4s5amt -as4scr -ass5eins -assens6k -assens6p -as5s4er -asser5a -asse6r5ec -asse6reis -5assess -3assim -as3ski -as4soe -as4s5ora -3assoz -as2sö -as4spo -as2st -ass4tie -ass4top -ass4tue -as4sur -a4st. -a3s4tad -ast4af -ast5ans -ast5ant -as3tap -asta3s -as5tatt -ast5auf -ast5einl -a4ste4n -ast5ents -as3ter -as6t5er6ho -as6t5er6hö -aster5oe -aste4s -as4t5ese -as4tex -as2th -3asthm -asti4e -asti4ka -a2sto -as3toc -ast5orc -as4t5rap -as4trau -ast5raum -as4tre -as5tren -as6t5ritt -ast5roll -as4tru -ast6ruk -2astu -as4tud -as2ty -asu2s3 -aswa4s -as2y -2asy. -a2syl -3a4symm -aße2 -aßen3 -2at. -2ata -a2t3aa -at5a4ben -a4t3abl -at3abr -at3aer -ata3la -at3apf -ata3ra -at3arb -at3art -ata3s2 -at3atl -at3att -a4t3auf -ata3un -a4t3aus -at3är -2atb -2ate. -ate4au -a2teb -a3teh -ateien6d -atei3f -atei3s -3a4teli -4atell -atel6l5erk -3a4temg -a4teml -a5tempe -a4tems -2aten -aten6t5ank -aten6t5ri -ate5ral -aterer5 -ate4ro -2ates -ates4sa -at4eur -ath4a -a4t3hee -at5herd -a4thin -ath5in. -at3hir -3athl -a4t3hoc -at3hof -a2th3r -2at2i -at3idi -ati4kab -ati6k5er6w -ati4kl -ati4s3a -ati4s3e -at4isl -a4tiso -ativ3a -ati4vat -ativen6d -ati6v5er6f -ati6v5er6s -4atle -4at5mus -at2o -a2t3o2b -at3oef -at3oel -a4t3oer -at3o2f -a2tom -ato4man -ato4min -3atomk -a3tomo -3atoms -at5opfe -a4tops -2ator -ato4ra -a4t3orc -ato4rei -atorgene6 -ato3s -ato2w3 -a2t3ör -at5raet -at5raeu -a4t3rau -a4t3rav -at3rän -a4t3rät -a4t3räu -at3rec -a4trep -at5ring -at3roh -at3rol -a4t3rom -4atron -a4troo -3a4trop -a4t3rot -at3rue -at3ruh -at3rut -a2t3rü -2ats -at6schma -at6schme -ats3e4h -atse4l -at4set -at4s3in -at2so -ats3pr -at4stoe -at4stop -at4stö -at4stur -3attac -at4t3ad -at4tah -att5akt -atta3s4 -at4t3au -at4t3ec -4attel -attens4 -4atter -at6t5ertr -at4thu -at3ti -at4t3o4b -att3o4f -5attrak -at4t5ras -att5rat -att5räu -att5ros -att3s4 -2atu -atu3et -atu2n -atu4rei -atu4rer -atur3z -atus3e -atwa3r -2atz -at2za -at2zä -at4z3e4c -at4z3e4d -at4z3eh -atz5eig -atz5ela -atz5elt -at4z3em -atzen5e -atz5erb -atz5erf -atz5erg -atz5erh -atz5erke -atz5erkl -atz5erkr -atz5erl -atz5ero -atz5erp -atz5ersc -atz5ert -atz5erw -at4z3e4t -at4z3in -at2zo -atz3op -at2zw -a2u -2au3a2 -au1ä -2aub -au4b3ab -aube4e -aub5ele -aube4n -auben6se -auberin6s -au4b5eul -au4b5ins -au4b5lae -aub5las -au4blä -aub5rin -aub4spo -aub4spr -au4b3um -2auc -auch5e4c -au6ch5en6t -au6ch5erke -auch5sti -2aud -au1e -4aueb -aue2d -au3eg -auene4 -auens4t -auere4 -au5erel -au5erent -aue4r3o -aue2s -aufa4c -auf5akt -au4f3ar -auf3ei -aufen4s -5aufent -auf5erf -auf5erh -au4f5er5i -auf5er6kr -auf5erla -auf5ind -3auflö -3aufn -4aufo -auf3re -5aufruf -auf3t2 -4auft. -5aufträ -au4fum -3aufzü -4augeh -augene4 -4auger -5auges. -2augl -4augr -augs4t -aug5str -3augus -au1h -auh4a -2a3uhr -2au1i -aui2n -2auj -au4kle -auk5te -au4l3at -aul3e4c -aule4s -aul5ese -aul4les -au3lü -2aum -au4mad -au4maeh -au4mäh -aume4n -aum5ents -aume4r -au4m5er5i -au4m5ern -au4m5ers -au4m5erz -aumgene6 -au4m3id -au4mind -au4m5ins -au4mis -au4m3o4r -au4m5ott -aum3p2 -au4ms2 -au4mum -au4mun -2aun -aun4ac -aun5alg -au3nas -aun4aus -aun3d -aun2e -au4n5er4d -aun5erze -a4unf -au4nio -aunk2 -au2no -au3nu -au1o -2aup2 -au3pf -aupt5er -au1r -aur4a -auran4 -aurant5 -aur2h -au3ru -au4s3ah -ausan6ne. -au4sap -au4sas -au4s3au -4ausc -au6schan -ausche6f -au6sch5lag -au6schli -au6schmi -au6sch5wi -au4scr -3ausd -4ause. -ause4e3 -aus5ehr -aus5ele -aus5elf -au4s5erb -au4s5erf -aus5er5in -au4s5erk -au4s5erl -aus5erpr -au4s5erw -3ausgl -au4s3in -au4sis -aus5kr -2auso -au4s3or -au4spic -3ausrü -auss2 -5aussag -aus4se. -aus3sz -au5stae -au5stal -au5stat -aus5tauf -4auste. -au5stein -au5stel -4austem -4austen -4austes -5austrag -au5s6tras -au4s5tür -3auswu -3auszü -auße4 -außer3 -aut5aeu -au4t3äu -aut5ein -au4t3e4l -au4t5ero -au4ters -au4tev -aut3ex -au4thy -2auti -3auto -auto6renk -auto6renp -auto6rens -auto6rent -au4torg -auto6rinn -aut5rau -aut5rin -aut5rue -au4trü -auts2 -aut3sk -aut3z -2auu -auve4 -2auw -2aux -2auz -a1ü -av2an -avari4e -a2va3t4 -a2v3ex -2avi -avie6rer -a2vl -2avo -a2vr -2avy -2aw -awi3e -a2wr -a1x -ax2am -ax2at -ax3emp -ax3erw -3a2xio -2a1ya -a2y3ak -ay4ere -ay2es -a3yeu -ayma2 -ay3of -ayou2 -ays2 -aysi3e -ay3t -ay2u -az2a -a3zad -3a2zal -aza3s2 -az2o -2a3zu -az4ur -2az2w -azz3a2 -az4z3en4 -az4z3in -az2z3w -ä1a2 -äap2 -ä2bl -äbni2 -äb2s -äbte3 -ä1ce1 -ä3che -äche3e -äche4n3 -ä3chi -ä2chr -äch4s3a -äch4s3o -äch4sp -äch4st -ä3chu -ä1ck -ä3cke. -ä3ckes -äck3sp -ä2da -ädel3a -ädel3e -ädel3i -äde3s2 -ä2d3ia -ädi2e -ä2d1r -äd2s -äd3te -ädte3e -ädte3i -ädtein4 -ädte3s -2ä1e -äe2j -äe2k3 -ä2eri -äe2x -äfermi4 -3äffc -äf2fl -äfig3s -äf1l -äf1r -äf2ro -äf3te -äft2s3 -ä2g1a -1ä2gä -ägd2 -ägdi3 -ä4g3ent -äge4ra -äger5as -äge6rend -ägeren6g -ägerer6k -äge6rers -ägerin6d -ä2g1l -ä2g3ne -äg2ni -ä2g1r -ägs2c -äg3sta -äg5stel -äg3s4tr -1ä2gy -2äh. -äh1a -2ä1he -ä2h3ei -ä2h3e2l -ähe2n -äher6gebn -ä1hi -äh3in -äh4l3a -äh4l5e4be -ähl5ein -ähl5ins -2ähm -äh3na -äh3ne -3ähnl -ähr3a -ähre2 -äh4r3ei -äh4r3el -ähr5sa -2ähs2 -äh3stu -2äht -ä1hu -äh3w -2äi -ä1im -ä3is. -ä5isch. -ä4ische -ä5i4schl -ä5ischs -ä5i4sch5w -ä3isk -ä1k -ä2kle -äk2li -ä2kr -ä1la -äl3an -äl2bl -äl1c -äl2l3a -älle3s -äl2pl -äl4schl -äl3te -älte3i -ältein4 -ä1lu -2äma -äma3t -2äml -ämon2 -ämoni5e -ämte2 -äm2th -2ämtl -2än. -ä1na -ändes4 -än2dr -2äne -äne2n3 -äne3p -äne3s -än2f3 -änge4ra -ängerin6s -än2gl -än2gr -2äni -2änk -än3k4e -änke3i -än2kl -än2kr -änne4 -änner3 -2äns -än2s3a -än2s3c -än2sp -ä1nu -2änz -ä1on -äo3s -äos4e -2äpfc -3äpfel -äp2pl -äp2pr -äp2s3c -1ä2q -ä2r1a2 -3ära. -är2af -ärana3 -ärauto4 -är1ä -är4b3le -är1c -2ärd -ärde4n -ärde4s -2äre -ä2r3ei -ä2r3e2l -äre2m -ä4r3emi -äre2n -ären3a -ä4r5ener -ä4r3erf -ä4r3erh -ä4r3er4k -ä4r3erz -ä4rese -är2gl -ä1ri -äri2e -ä4r3ind -ä4r3ing -är3ins -ä4r3int -ärm5arm -är4m3au -ärme3e4 -är4m5ent -är4m5erz -ärme5sz -är4m3in -är4mof -är3o2b -är3of -är3o2p -är3or -ä1rö -ärp2 -är4seb -är4s3eh -ärs3er -är2sz -ärt2s3 -ä1ru -ärz3au -ärzen4t -ärzer3 -ärz3te -äsche5i -äsche5s4 -2ä3se -äse3e -äse3g -äs4e3i2 -äsein4 -äse3la -äsen4s -äse3re -äse4ren -äse5r4i -äse3u2 -ä3s2kr -äs1p -2äss -äs2s3c -äss5erkr -äss5erwe -äs2so2 -äs4spr -äs4ste -äs3te -ästein4 -3ästh -äs2tr -ä4ßent -äß3erk -äß3ers -äß3erw -ä2t3a2 -ätab3o -2äte -ät4eb -äte5be -äte3e2 -ätein4n -ät3eis -ät3emp -äte2n -äteo2 -ät3e4pi -äter4bl -ätergene6 -ätes4p -ä2th -ät3id -ät3ob -ät3op -ät1r -ät2sa -ät2sä -ät4schl -äts3en -ätsi2 -äts3p -ät2st -äts3tä -äts3te -ät2su -ät2t3a -ät4tei -ätte4n3 -ät2tr -ätt3s2 -ät3v -ät2za -ät2zw -äu2b3l -äu2br -äu1c -äude3 -äuder4 -äu1e -äuel3 -äu4eri -2ä2uf -1äug -äu2gl -2äul -2äum -äu2ma -äum3p -äum2s -äum3sc -ä2un -äun2e -äu3nu -2äu3r -äu5re3i -äurein4 -äure3s -2äus. -äu4scha -äu4schä -äu6sch5ei -äu6schel -äusch5erz -äu4schi -äu4schl -äu4schm -äu4schn -äu4sch5o -äu4schr -äu4schü -äu4schw -ä3usd -ä5useff -äu3s4e3i -ä3usg -ä3usk -ä3usn -äus4s3c -ä3usst -1äuß -äu2tr -ä2vi -1äx -âte2 -á1 -1b -ba2b3a -ba4b3er4 -2babg -2b3a2br -2babs -5bach. -bach5en -5bachh -bach3s -b5achse -bach7ter -ba4ck5er4 -backs4 -bad3a -ba4dab -ba4dan -ba4dar -4b3adel -ba4dent -2b3adl -2b3adm -ba2dr -ba2du -b2ae -2b3aeh -4b3aerz -4b3aeug -bae5us. -2b1a2f -2b3ahl -bah4n5e4b -bah6nene -bah6n5ent -bai3d -ba2k3a -ba4k3er -ba2k3i -bak3l -ba2k3n -ba4k3ra -ba4kre -ba3la -ba4lab -ba4l3ak -ba4lar -ba4las -ba4lau -bale4a -ba4lei -baler4 -ba4l5erk -bal5ins -balk4a -balke4 -bal4lan -bal4l5au -bal4l5eh -bal4l5ei -bal6lerg -balo3 -bal4tr -2b1am -bama2 -bam3az -3b2amb -ba2me -ban2a -ba4n3ae -b2and -ban4dab -ban4d5al -ban4d5an -ban4dar -ban4dec -ban6delf -ban6d5en6de. -ban6d5en6den -ban6d5en6des -ban6d5erf -ban6d5erk -ban6d5erz -ban4dob -ban4dr -ban4du -ban4k3a -ban6kerf -ban6k5erw -ban4kl -ban4k3n -ban4kop -ban4kor -ban4k3r -ban4ku -2banl -b3anna -2b3ans -2banw -2b3app -ba4r3ab -ba4rad -bar5akt -ba4rar -bar5ast -ba4r3at -ba4rau -bar5ein -bar4en -barer5ei -barer4t -bari3e -bar5in4s -bark4l -bar2s -bar4ta -bar4tel -b3arti -ba4rue -bar3zw -bas2a -ba4seb -ba4seli4 -bas4i -b3asia -bas4sa -bas4s5ei -bassin4 -bas4sp -bas4st -bas5te -bas3ti -bas4tr -ba4t5ent -2b3atl -batro4 -ba4tru -bat4tak -baub2 -bauchar6m -bau6ch5el -bau6ch5er -bau3eb -bauen4s -bauer6fo -bauer4g -bauer4l -bauer4n -bauer6tr -bauer4w -bau3fa -b3aufg -bau3f4r -4b3aufs -4b3auft -4b3aufw -baug2 -bau5kl -bau3l -bau4m5ac -bau6m5ant -bau6m5ast -bau6m5ent -ba4u3n -b3ausb -4b3ausf -bau3s4k -b3ausl -bau3s4p -b3ausr -4b3auss -bau3s4t -b1a2x -ba3yo -b2ä1c -b2äd -2b1äh -b2äl -b2är -bären5 -bärk2 -2b3ärz -b2äs -2bäug -bä3us. -2bb -b3ba -bbau3 -bbauer4 -b3be -bbens4 -bbe3ra -bbin4g -bb2lo -bb2lö -bblu2 -bb2r -bbran4 -b3bru -bbru4c -b3brü -bb2s -2b1c -b3ch2 -2bd2 -b3da -bdän3 -b3de -bde3s -bdi2e -bdome4 -3be. -bea2n -bea4ten -beat4m -b3ebb -beben3 -bebens4 -beb2l -3be1c -be2ce -becher5e -4b3echt -be3cke -4b5e4cke. -bed2 -4b3e4del -bedi2 -2b3edl -be3e2h -bee2l -be3ela -be3en -be3erd -bee4r5ei -bee6r5es -bee4r3i -be5ertr -be5erzi -bee4ta -be3eti -bee4t3r -be3eu -bef2 -be3fe -befen4 -2b3eff -befi4l -be3g2 -3beh -be4he. -beh3ri -beh2u -b2ei -beib2 -bei5bl -bei3d4r -4b3eier -bei3f2 -bei3g2 -beige4o -be3imm -be3imp -bei6ner6k -be5in4fo -bein6fra -4b3eing -be5in6hal -be5in4hi -bein6spe -bei3s4a -bei5sc -beisch4 -bei3sp -bei3s4t -beit4s5e -beit4s5o -beit4s5t -beiz4w -3bek -be3ke -b2el -bel5achs -bela4n -be4l5ano -be4l5ant -be4lanz -bel5bi -4b3el4bl -bel3d4 -be5le. -be3lec -3belei -be5lei. -be5leie -be4l5ein -be4l3en -bel5er5e -bel5erh -bel5erk -bel5ern -be4l3e4t -bel3f -3belgr -beli6ef -bel5inn -belle4n5 -bel5oef -be4l3om -be4l3or -be4l3öf -bel5öse -3belp2 -bel3sk -bels4p -bel5stern -bel3sz -belti4 -2b3emp -4b3emul -b2en -3bena -be5nabe -be4nat -ben3au -be2nä -3benb -benden4 -ben6dens -ben6d5es6s -4bendl -4bendm -4bendp -be3ne. -ben3ec -benei4l -be3nen -be4n5end -be4n5ent -be3ner -be4nerf -6benergi -be5nergr -be4n5erl -be4n5ers -ben5ert -be4nerw -be4ness -ben3f -beng2 -be4nid -be4nik -benim4 -ben5inn -be4n3is -3benk2 -ben3n -be4nob -be4n3or -ben4san -ben4s5au -ben4sek -ben4s5el -bens5end -ben6sere -ben6spar -bens5trae -bens5trau -bens5trä -5bensz -4b3entb -4b3entd -4benteu -4b3entf -4b3ents -4bentw -ben3un -3benv -3benz -be1o2 -be1p2 -be4pit -4b3epoc -b2er -be3ra. -ber3aa -ber5abe -ber5abl -ber3a4c -ber5adm -be5raeu -ber3af -ber3a4k -ber5all -ber5amm -ber5anz -be4rar -bera4s -ber5asc -ber5ass -be4rato -berbeste5cke -berd4 -be4r5eck -ber5eig -ber6eini -be4r5eiw -ber3el -bere4m -ber5emi -be3ren -ber5end -be4r5en5e -be4reng -ber5ens -berer6fa -ber5ersa -ber5e4sc -be4r3e4t -berf4 -ber4g5af -berg5an. -berg5ass. -ber6g5en6t -berger4 -ber6g5erf -ber4g5li -ber4gn -bergs4t -ber4hab -ber3i4d -berin6sp -berin6st -berin4v -be4ri4o -ber5iss -berk2 -ber4ken -ber5n4a -ber3n4e -ber3ni -b3ernt -ber3o4b -ber3oe -ber3of -be4r3oh -ber5ost -ber3ö -berr2 -bers4k -ber5stau -b2e1s -be3s4an -besch4 -be4s3ep -be4s3er -bes5esk -be4s3id -be3s4la -bes4pe -bes4po -bes3sa -bess4t -b5esst. -bes3sz -be6stang -beste4 -be4ster4 -bes6t5erh -bes6t5erk -bes6t5ern -be4stol -bes4t5o4r -bes5tos -be4s5trä -be4s5tur -be4sum -be4sur -be3s4ze -be4t5ant -4b3etap -beta5sp -bet5auf -bete4s3 -bet4o -bet4tis -bett3r -be3uh -be3um -be3un -be3ur -b2ew -2b1e2x1 -3bez -2b1f2 -b3fa -bfal4li -bfang5s -b3fä -b3fe -bfin4s -b3fr -b3fu -b3fü -2bg2 -b3ga -bga6send -bge1 -bge3ne -bges4 -b3gl -2b1h -b3ha -b3hä -b2i -bi3ab -bi3ak -bi2ar -bi3as -bib2 -bi3ba -bibe4 -bibel3 -biber3 -bi2c -bid2 -bi3dj -biens4 -bie4r3a4 -bieres4 -bie4st -bie4t3a4 -4bieti -bie4tr -biet4s3 -bi3f -bi4ke. -bi4kes -bi3k2l -bi4kre -bi3ku -3bil -bi4l5ang -bi4lant -bi4lau -bil4da -bil4deb -bil4del -bil4d5in -bil4d3r -bild3s -bi4lei -4b3illu -bi2lu -2b3imb -2b3imp -bi3na -4b3indi -bing3a -bi4n3ok -4b5insas -2b3int -2b3inv -bio1 -biob2 -bi2o3d -bi3oef -bi3re -biri3e -2b3irr -bisa2 -bi4s3ei -bi3si -bi2s3p -bis5sau -bis4s3c -bi2st -bi3sta -b4i3tr -bit3s2 -bi3z -2bj -bjek4t5o4 -2b1k2 -bk4an -b3ko -b3kr -bl2 -2bl. -bla3b2 -2b3lac -blade5s -4b3laed -4b3laen -blaes5c -3b4laet -2b3lak -2b3lam -b4lanc -b3lang -bla4s3p -6blassens -blas6serk -4blassu -bl4at -b3late -3b4latt -blat4t5r -bl4au -blau4c -4b5laus -blaus4t -b3laut -2b3law -b3lay -2b3läd -2b3län -bläs3c -3b2lät -b2le2a -2b3leh -ble4i -b4lei. -b4leib -bleich6a -4b3leid -blei3f -4bleih -ble5imp -blein4 -ble5int -blei5sc -blei7sch -4bleit -blei5ta -b2lem -ble4mei -ble6m5ent -ble4mer -blem3p4 -ble2n -b4len. -b4lend -4blenk -b2leo -b4les. -4blese -4blesu -ble3sz -b2let -b3li. -4b3lich -4blief -b4lier -2blig -bl4in -b4lind -4bling -blings5 -blin4k5a -b4linz -4blis. -b4liss -b2lit -b3lite -3b2lo. -b2loc -4b3loch -bloch5s -blo6ckent -blo2g -3b2loi -blo4id -b3lok -b2lon -2blos -blues5s -2b3lun -blu4t3a4 -blu4th -blu4tin -blut3o -blut5sc -2b1m -b3me -bmen4s -2b1n2 -b3na -bnas2 -b3ne -b3nu -bo2a2 -bo3ab3 -boabi4 -bo3as -b1ob -bo2b3l -bo2br -bobs2 -bo3ch4 -bo3d2s -body3 -bo2e -boe4bl -boe3i -boei4g -bo3en -b1of -boh4r5ei -bo3is -bo4l3an -bo4l3au -bol3c -bol5erl -boli4e -bo4l3in -bo4lit -2bolu -bol3za -bombe4 -3bon. -bo3nat -bon4d3e -bo4n3er -3bons -3boo -boo2l -boo4t3i -boo4to -b1op -bo2pe -3bor. -bor3ae -bor3al -bo5rant -bo4r3as -bor4d5an -bor4dr -bo4rei -bo4rig -bo4r3in -bor2s -b3ort. -bor4ter -bor6t5rat -bose3s -bos3t -3b2ot -bot4e -bote4r3 -bo3th -bot4se -bot4so -bots3p -bot4s3t -bot3t -bo4xel -boy1 -bö2bl -2b1öf -bö2is -b1öl -2b1p2 -bpa2g -b3po -2bq -br2 -2br. -b2ra. -bra3a -2b3rad -4b3raed -b2rah -bra5i4sc -b2rak -b2ram4 -bran6dent -bran6derk -bran4d5r -4b3rat. -brater4 -bra6terd -bra6terg -4b3ratg -bra4t3h -bra4t3r -5b4raun -bra5unte -2b3räd -2b3re. -6brecherc -brech5s -6b5rechte -6brechtl -2bred -2bref -2b3reg -b3reic -4breif -brei6t5ei -brei4t5r -b3reiz -2brek -breli3 -b2rem -brem4sc -brem6sei -brem4s5t -b4ren. -4b3rent -2breo -2b3rep -b2re3r -b4res. -b3rese -4b3rest -b2ret -bret6t5en -2b3reu -b2rev -bri4da -4brieb -brie4fa -brie6f5er -4b3riem -b4rien -b4riga -b4rigi -4brigk -bri6kans -bri4ker -bri4ku -b2ril -b4rina -4b3rind -b2rio -b4risc -4briss -4b3ritt -b2roe -2b3roh -2b3rol -bro4m3a -3b2ron -b2ros -4b3rost -bro4ta -brot5ei -bro4ter -bro4t3h -bro4t3o -bro4t3r -brotz4 -2b3rou -b2rö -b2ruc -bru4chu -4b3rueb -2bruf -br4un -4b3rund -bru3n4e -brus4 -brust3 -bru4ter -bru4t3h -2b3rüb -3brüc -2b1s -b2s3ad -b4s3aer -b2sak -b4s3all -bs3amb -b4s3amt -b4sanf -b4s3ant -b4s3anz -b2sap -b2sar -b3sau -bsau4m -bsau4r -b2s3är -bsch2 -b4s5chec -b4schef -b5schu -b2sco -bs2cu -bsde3s4 -b2s3e2b -b4s3ehr -b4s3ein -bse2n -bsen3e -bs3erd -bs3er3e -b4s3erf -b4s3erg -bs5e4r5in -bs3erk -b4s3erl -bs3err -b4s3ers -bs3ert -b5serv -bs3erw -bs3erz -bse2t -bs3eth -bset6z5er6 -b2s3eu -b2sex2 -b4s3ide -b2s3im -bs3ind -bs3inf -bs5inte -b2sk -b2so2b -bs3off -b2s3op -bso2r -bs3orc -b4s3ori -bs5ort. -bs5orts -bs2öf -bs2pe -b4speri -bs2ph -b3spit -bsprun4 -bs5rei -bs3s2 -bs2t -bst5a4be -b4stade -bst3ak -b4st3al -bstands5 -bst5ank -bst5ann -bst5art -bs4t3as -b4s5tauc -bst5au4f5 -bst5aus -bs5teil -bst5einb -bst5eis -bst3e4k -bste4m -bst5ema -bst5emi -bst5en5e -bst5ent -bst5enz -bst5er5e -bst5erf -bst5erh -bst5erk -bst5ernt -bst5er5o -bst5erö -bst5erz -bs3tes4 -bs4t5ess -b3stet -b5steue -bst3h -bst3i4d -b4stin -bst3io -b4s3tip -b4st3ob -b4stoch -bs3tod -b4stor -b4strac -b4s5trae -b4strau -b4s3trä -bst5rep -bst5ret -bst5ros -bst5rue -b4st3rü -b4stun -b4stüb -b2sty -bs3umb -bs3ums -b2s3un -bs3url -b2sz -b3s4zep -2bt -btas4t5r -b3te -bt1h -bti2s -bti5schen -btisch5l -bto2n3 -btran4 -bts2t -btu3en -btü1 -bub2 -bu5chare -buch5art -buche4 -bu4ch5ec -buch5ere -buch5erf -buch5er6h -buch5er6l -bu6ch5er6s -buch5er6w -buch5er6z -bu4chet -buchs4p -bu2e -buegel5e -buer4o -2b3u2fe -bu3f2r -bu2gl -bu4g3ra -bul4l3a -2b3umf -2bumk -2bumr -bu3n2a -3bund -bun4dä -b4undd -bun6d5ent -bun4d5er -bunde4s -bun4din -b4undn -b4undz -2b3unf -b3un3gn -2b3unh -2b3unl -bun6t5er6z -bun4tr -2bunw -bur3c -bur4gan -bur4gar -bur4g5au -bur4gin -bur4gl -bur4gr -bu3ri -2b3urn -b3ursa -b2us -bu2s3a -bu4scha -busch5ar -bus5chau -bu6schei -bu6sch5el -bu6scheu -bu4schl -bu4sch5m -bu4schn -bu4sch5o -bu4schr -bu4schw -busch5wa -bus5ers -bus5erw -bu4sin -bu2so -bu2s3p -buss4pa -bus3ta -bu2su -bus3un -bu4ßer -but2a -bu2t3r -bü1c -bügel5e -bühre4 -bür2s -2bv -2bw -b3wa -bwasse4 -b3wä -b3we -b3wi -b2y1 -byauto4 -byo2 -by3p -byp4i -bys2 -byte3s -2bz2 -b3ze -b3zu -bzug2 -bz4u3n -1ca -cab2 -ca3bl -3cac -ca4ch -cae3 -caes2 -ca3g2 -cago3 -ca1h -ca3lab -ca3lam -cam4p3l -c3ang -ca2pe -car2a -car3n2 -carri3 -car2s -ca3s4a3 -cas3te -ca3t2h -ca3ur -caus2 -caust3 -ca1y2 -cä1 -cäs2 -2cc -c1ce -c1ch -cch4i -2c2d2 -c3do -2ceae -2cear -2ceä -2ceb -2cec -1ced -ce2dr -2cee -2cef -2cegr -1ceh -ce1i -2cej -2ceka -ce3l2a -cele3r -3cell -3cels -cen1 -cene2 -ceng2 -ce2n3i -ce2no -cens2 -3cent -cent3o -cen4tos -cen3un -ce1o2 -2ceop -1cer -cere3u -ce3ri -ce4ris -ce3ro -ce3s2h -2cesp -ce3sti -2cet -3cet. -ceta2 -ce4t3am -3cett -ce1u -2cez -1cé -c1f -cg2 -2ch. -4chab -ch3a4b3i4 -2cha2c -ch3ack -ch3aep -ch5aerm -ch3aes -2chaf -5chafei -ch3a4ge -ch3akt -5chalam -chal5b -chal6lein -cha4mer -ch5ampe -3chamy -3chanc -ch5andr -ch5ange -ch5angr -ch3ano -ch5ansa -ch5anst -4chanz -3chao -ch3app -4char. -3chara -ch5a4rab -3chard -4chare -5charta -cha4sc -ch3asp -ch3atm -3chato -4chatt -4chatu -4chatz -4chauk -ch5ausf -chau5spo -ch5ausr -ch5ausst -ch5austr -ch5ausw -ch5ausz -chau5t -ch3äp -ch3ärm -ch3äs -1châ -2chb -2chc -2chd -che3b -4ch5e4ben -ch5echt -che5ers -3chef. -che4fei -4cheff -5chefin -che4fr -3chefs -2cheh -4chehe -4chehr -4chei -ch3eil -ch3eim -ch5einla -ch5eintö -ch5einze -ch5eis. -ch5eise -che4las -ch5elek -4chelem -che4len -che4ler -che4me. -ch4emi -5chemie -5chemik -4chemp -che4neb -ch5enge. -che4nid -che4no -chens4p -4chentc -chen6ten -4chentf -4chents -4chentw -cher3a -che4rat -cher6bes. -4ch5erbs -cher6de. -6chergebn -4ch5ernt -6cherregu -cher5sta -ch5ertrag -cher6trage -6cherzeu -4ch3ess -4cheta -ch5e4tag -ch5e4tik -4ch3e2x -1ché -2chf -2chg -ch5gau -2chh -3chia -chilf5s -chi5li -4chind -4ch3inf -ch3inh -ch5inko -ch5inkr -ch3ins -4ch3int -4ch3inv -3chip. -3chips -3chiru -chi5sta -3chit -2chj -2chk -2chl -chl4a -ch5lade -ch5laend -ch5lanz -ch3lat -ch5lauf. -ch5läd -ch5länd -ch5lein -ch4len -chl4o -ch4lun -2chm -ch2me -2chn2 -chn4e -ch4nep -chner6ei. -chn4i -4chob -cho3ch -ch3oel -4choen -3choer -4chof -ch3off -chof4s3 -3chog -cho5l4a -3chole3 -ch3orc -4chord -cho4rei -5chores -5chors. -ch5orts -3chorw -3chosl -ch3öl -2chön -3chör -2chp -chr2 -2chra -ch3rab -ch3rad -ch3ral -chrap4 -ch3ras -ch3rat -2chrä -2ch2re -ch3reh -ch3reu -ch3rev -ch3rh -ch3rin -5christ -4chrit -4chroe -ch3rol -5chromo -3chron -ch3ros -2chrö -2chru -2chs -ch4sm -ch6s5pani -ch6spart -chstro4 -2cht -ch4tang -cht4ank -cht6en. -ch4t5ruh -4chue -4chuf -4chuh -chu2n -4ch3unf -ch3uni -4chunt -4chunv -ch3urs -ch3urt -4chut -chut4t3 -2chü -2chv -2chw -2chz -ci2a -ci1c -ci3es -cill4 -c3ind -cins2 -c3int -ci2s1 -1cit -c2k -2ck. -ck1a -3cka. -cka2b -ck3a2c -2ckae -2ckal -cka2m -cka2n -cka2r -3ckard -3ckau. -3ckaue -2ck1ä -2ckb -2ckc -2ckd -1cke -2cke2c -4ckeff -2ckeh -ck3ehe -2ck3ei -cke4la -ckel3c -4ckelek -4ckelem -ckelti4 -4ckemb -4ckemp -ckener6ze -6ckensem -cken4s5o -cken6s5tur -6ckente. -6ck5enten -4ckentf -cken6trop -4ckentw -cke4rec -6ckeremi -cker6fül -6ckergeb -6ck5erken -cker6ker -6cker6lau -6ckernie -6ck5ernst -4ck3ero -ck6ers. -4ck5ertu -4ckertü -4ckerz. -cker6zeugn -4ck3ese -2ck3eu -2ckex -2ckf -2ckg -2ckh -1cki -2ck3i2d -4ck3ind -4ck3inh -4ckink -4ck3inn -4ck3ins -4ckint -3ckis -2ckj -2ckk -2ck3l -2ckm -2ck3n -ck1o2 -ckoe2 -ck1ö2 -2ckp -2ck3r -2cks -ck5sac -ck4sar -ck4sint -ck5stae -cks5tate -cks4tec -cks4to -cksto4r -ck3str -2ckt -ck4tar -1cku2 -2ckue -2ck3uh -ck3umb -4ck3ums -4ckumw -4ck3una -ck3unf -4ckuni -4ckunt -2ck3up3 -2ck3ur -2ckw -1cky -2ckz -c2l2 -clet2 -cle4vel -clien4 -clin4g -cli2p -clip3a -1clo -clo3ch -clo5fe -1clu2 -club3 -cm2 -c2mi -1co -coca3 -co3ch -coche5 -co2de -code3s -co3g -cog4n -coi2 -co3it -co2ke -cola3s -co4leu -colle6ge. -co3l4o -com4te. -comtes4 -continuati6 -co2o -2cope -co4pe. -co3ra -2corb -cor4da -core3s -cor5ti -corvet4 -cos3t -2cotc -co2te -co2u -cou4c -cou3si -2cout -1cô2 -2c1p -2cq -c2r -c3rae -c3rah -c3rau -c3rä -1cre -2cree -cre4me -cres2 -cros2 -cross3 -1cru -cry2 -2cs -cs2a -cs2f -c2si -c3s2ti -c3s2tr -2ct -cte2a -cti2o2 -ction3 -ctur2 -1c2u -2cua -cul4u -cup1 -cu2pe -cup3p -cu2p3r -cussi4o -cus3t -c2v -c3ve -2cx -1cy -cy3an. -cybe2 -1ç -1d -da1a -2d3a2al -d1ab -d4abab -4da4bak -2dabb -2dabd -d2abe -4d3a4ben -2dabf -2dabg -2dabh -dabi2 -4da4b3it -da4blo -2dabn -2da2bo -dab4ra -4da4bre -d4ab4ru -d4ab4rü -2dabs -2dabt -2dabw -2dabz -d1ac -dach3a -dachau6t -d4ache4 -da4chec -da4ch5er -dach4in -dach3o -dach5stu -4dachtz -da5chung -3dack -d1ad -da4del. -da4delg -da4dels -da2do -dad2r -da2e -3daec -2d3aeg -2d3aeh -4d3aemt -dae4n3a -d3aend -2d3aep -2daeq -da3erf -d5aerzt -2d3aeu -dae3us -2d3aex -d1af -2daff -da3f2l -dafo4n -da3fra -d1ag -dagi4 -dag2o -da1h -dah3l -da3in -2d3air -da3ism -da3ist -da4kad -da2kn -da4kro -dal3a4d -dal5aer -da3lag -da4lär -da4lop -da4lor -da3lö -4d3alpe -4d3alte -da3lü -2d3amb -dam4e -d3a4mei -damen5s4 -d5ammae -d5amman -4d3ammä -4d3ampe -d3ampu -2d3amt -dana3e -4d3anal -dan5alg -da4nan -d3a4na3t -dan4ce. -dan3d2 -4d3anda -4d3anei -4d3a4n3eu -2danf -d4anfu -4d3anga -d5angeb -d3angs -2danh -dani5er -4danim -dan4kal -dan4kan -d5anker -dan6k5erf -dan4kl -dan5kla -dan4k3o -dan4kr -4danle -4d3anna -2d3a2no -2dans2 -d4ans. -d4anse -danta4 -4dantr -4dantw -4d3anzu -4d3anzü -dan4zw -2d1ap -d2apa -3dapol -da4por -da2pu -da2ra -dar3an -dar3as -darb2 -5darbi -2darc -dar4dar -dar4de -dard5ei -dard5er -dar4do -dar4dr -dardwa5re -4d3area -dar3ei -dare4m -dar5emi -da4r5en5e -d5a4rist -dar4kr -dar4mar -4d5arres -4d3art. -4d3artd -4d3artg -4d3ar4th -4d3arti -dar4t5in -4d3artl -da2ru -dar5um. -4darzt -da4sche -da5sei -das6ein -da3s2h -da2s2p -da3spa -4d5aspek -4d3assi -da3st -4d3aste -das4tra -das4tu -da3ta -da3tei -4d3a4tel -date4n -da2th -dati4v3 -2d3atl -2d3atm -dat4st -4d3atta -datum4 -2d3aud -dauer5e -2d3au2f3 -2d3aug -dau2i -2d3auk -da3unt -dauri3 -2d3aus -2d3au2ß -4dauto -d1ax -d1az -da2zo -3däc -2d1äf -2d1äg -2d1äh -2d3ämt -dä2n3a -2d3änd -2d1äp -2däq -därm2 -4d3ärzt -2d1ä2u -dä3us -2däx -2db2 -dbe4e -dbi2e -2dc -d3ch2 -dchengene6 -dco2r -d1cr -2dd2 -d3da -dd4ae -ddar4m3 -d3dä -dde2a -ddel5e4c -dde3ra -d3dh -ddha3 -ddi2e -ddien4 -dding5s -d3do -dea2d -de4adl -de3ae -de3a4t -deb2 -4d5e4bene -3debü -de4ca. -de5chor -de3chr -2d3ecu -ded2 -4d3e4del -dede3s -dedi4e -de4dit -dee2 -de3eb -3de3ef -de3eg -3de3em -de3ep -de3er -de3es -de3et -3de3eu -3deex -de4fa. -2d3eff -def2l -deg2 -2d3egg -deh2a -de5hop -2d3ehr -4dehyds -de4ic -dei6ch5er -de5i4den -2d3eie -2d3eif -2d3eig -de3im -4d3einb -de3in4d -4d3eine -de5in4fo -de5in4fr -d3eing -dein6gen -4deinh -4d3eink -4d3einl -4d3einm -4d3eino -4d3einr -4deins. -4d5einsa -4d5einsä -de5in4se -dein6sta -dein6str -dein6ter -5d4e3in4v -4d3einw -de3io -de3i4si -d4e3ism -dei4sp -de3ist -2dekz -del5ach -de4l3am -dela4n -del5ano -de4l5ant -dela4t -del5ate -del5auft -de4l5aug -deld2 -de5le. -de4lef -de4l5ei4g -4d3elek -4deleme -del5end -del5erk -del5ern -del5er4r -del5erz -4d3elf. -4d5elfe. -delfen6s -4d3elfm -deli4ne -del5inn -delk2 -del4l5an -del4lar -delle4 -del4l5eb -del4lei -dell5eic -del4l5er -delmi4 -del4n3e -de4l3ob -de4l3op -del6s5ach -del4san -del4sas -del6sein -del6s5int -del6s5pan -del6spar -del6spat -del4spr -de4manh -dem4ar -dement4 -de6m5entg -demi4e -4d3empf -d4en. -dena4b -den4ade -de3n4am -de4nan -de5n4arr -de5n4au. -de5naue -dende4n -4d3endh -4d3endk -den3d4r -dend4s -dend4u -den3e4b -de4n3e4d -de4n5end -de4n5eng -de4nep -den5erb -den5erei -4d5energ -de4n5ero -de4n5erö -den5ers -de4n5ert -de4nerw -den3f2 -4d5en4ge. -6d5engeln -6d5en6gels -den6gen. -de4nid -de4n3im -den4k5ak -den6kerl -den4k5ob -de4nob -d4ens -den4sal -den6stie -den6stop -dens5tor -den4sur -4d3entd -dentengene6 -4d3entf -4d3entg -den3th -4d3entk -4dentla -4d3entn -den6trop -4dentw -4dentz -den6z5erf -den4zym -2deol -de3p2f -dep2l -depo4t -de3ra. -der3ab -de5rabi -der5a4de -der5aen -de5raet -de5raeu -der3af -der3a4g -der3ak -der5alb -der5alt -de5rampe -dera4n -de5ran. -der5ane -der5ant -der5anz -de4r3ap -dera4s -der5asi -der5asp -de4r3än -4d3erbs -4derdb -de4r3e4b -de4r5eck -der5edi -6dereigni -der6eini -der5eis. -de4rele -der5ell -de4remi -der5ende -de4r5ene -der5enge -de4r5ens -der5ente -der5entg -der6er. -derer6de -derer5e -der4erf -de4r5erh -der5erre -de6rersa -dere4t -der5eta -de4r3eu -derf4 -der5fl -derg2 -6dergebn -de4r3id -de4r3i4m -der5inb -der5inner -de6r5innu -derin6teg -de4r5iss -derk2 -der4nal -der5o6ber -deroe4 -der5oef -der5onk -de4ror -de3rot -derö2 -derr2 -ders4k -der5stau -dert5ende. -der6ters -6dertragen -de4ruh -der4ump -der6weck -4d3erz. -4d3erzv -de4s3a4g -de4s3ah -de4sak -des5amt -des5ang -des5ant -des5anz -de4sap -de4s3au -de6sche6f -de4s3eb -de4s3e4h -de4s5eil -de4s5ein -des5elt -de4s5en5e -des5eng -des5er5e -des5erf -des5erg -des5erh -des5erk -des5erl -des5erm -des5ern -des5ers -des5erz -dese4t -des5eta -de3shu -desi5er. -de4s3in -de4sos -de4spa -des6park -de4spot -des4pu -dess2 -dess4e -des6sens -des6ser. -des6seri -des6sern -des6sers -de5staf -de5stall -dest5alt -de5stec -dest5eige -dest5einsa -de5stell -des6temp -des6t5erg -de5stern -des6tert -des6tina -de5stras -des4tum -de2su -des3un -de3ta -deta4c -4d3e4tat -deten4t -4d3e4thi -de4tik -det2s -2d3etw -de3u2f -de3uh -2d3eul -de3um -de3u2n3 -de3url -de3us -d4eut -4d3e4vid -dex3em -dexen4 -de4x5end -de4x3er -de4xis -4dexpe -4dexpo -2d1f2 -dfang3 -dfen2 -dfin4s -df4la -2dg2 -dga2t -d2ge. -dger4e -d4ge3sh -dge6tang -dge4tau -dge4t3e -d3gl -2d1h2 -dhau6eri -dhek2 -d3hu -dh4un -dia5l4ei -dialer4 -di3arr -dia3s2 -dib2 -di3ce -di3chl -di3cho -dich7ter -dichts4 -di4ck5el -2d3i2co -4d3i4dea -4d3idee -di4den -2di2dy -di3e2d -di2e3i -di5elek -die4n5e4b -dien5e4v -die4ni -die4no -di5ens. -dienst5art -dienst5rä -dienter6mi -die2p -die4rei -diese4 -die4th -dige4s -dig2n -dig4ta -dig4t3r -di4lar -di4l3au -dilf2 -4d3illu -dil4s3t -2d3ilt -2d3imb -2d3imp -di3na -2d3ind -2dinf -d3infe -d3infl -d3info -d3infr -3ding -din4g3a4 -2d3inh -2d3inj -4d3insc -4d3inse -4d3insp -4d3inst -4d3insu -4d3insz -2d3int -2d3inv -di4odi -dion5in -dion3s4 -di3ora -dios2 -di1p2 -di3pt -di4rak -di4ran -di3re -4d3i4re. -4d3i4ren -4d3i4rin -4d3i4ris -2d3irl -2d3irr -di3s3a2 -di4sag -di4sas -di4sch5r -diskan4 -2diso -di4som -di5s4per -2d3isr -diss2 -dist4 -di3s4ta -dis3te -di4t3ak -di4tal -di4t3an -di4t3au -dite3c -di4t5er5e -di4t5erl -di4t5erm -di4t5ero -di4t5erö -di4t5ers -di4t5erw -dite3s -dit5ins -di4tob -di2t3r -dit3s -di2t3u -di3ve -di3vi -di2vr -2dj -2d1k2 -dkab2 -dkraf4 -dkraft5 -2d1l -dl2a -dlan4dr -dlang5s -d3le -dl4er -dle4r3a -dlerin6t -dli2f -d3lo -d3lö -dlungs5 -dl2v -2d1m -2d1n -dn2a -d3n2e -dne3p -dnet4s -dn2j -dnus2 -dnuss3 -do3a2 -d1ob -3d2oba -do4bera -do4berh -do4beri -do4bern -do4berr -2dobj -2dobm -2dob2s -3doc -do3chi -doch4t5r -do3e2b -2d3oe2d -2d3oe2f -2d3oek -2d3oel -doen4 -doe5ne -doe2r -doer3i -doe4s3c -4d3o4fen -2d3off -2d3ohr3 -3dok -3doli -do4mal -do4man -do4mar -do4m5ing -2domn -do2mu -3don. -3don2a -don4au3 -donaus4 -doni3e -3donn -2dony -doo3g -do2or -2d3opf -do3ra -4d3o4rak -4d3orbi -2d3orc -2d3ord -3dorf -dor4f3a -dor4f3ä -dor4fei -dor4f3i -dor4f3l -dor4fo -dor4fr -dor4f3u -2d3org -4d3orna -4d3orni -2d3ort -dor4t3a -dor4t5ei -dor4ter -dor4t5he -dor4tr -3dosi -dos2k -2d3osm -do3sp -dos5sa -d3os2t -dosta4s -doste4c -dosten4 -do4stu -2d3ouv -dow2s -d2o1x2 -2d1ö2d -2d1ö2f -2d1ök -2d1öl1 -dör4fl -dö2s3c -2d1p2 -2dq -dr2 -3dra. -2drab -2d3rad -d3raed -4d3raet -d3raeu -3drag -d2rah -4drahm -2d3rak -3dral -d3ramm -d3ramp -dr4an -4d3rand -4d5rangl -dran5k -2d3rap -dra4pu -d3rase -d3rass -4d3rast -dra4t3a -dra4th -dra4tin -d3raub -4d3rauc -4d3raum -4d3raup -4d3raus -4d3raut -2dräd -d4räng -2d3rät -2d3räu -3dräue -2d3re. -d4rea. -4dreal -d4reas -d4reck -2dred -2dref -2dreg -3dreh -dre4h3a -dre4hi -dre4h3o -4dreic -d4rei3e -d4reik -4dreine -4dreins -d4reir -4dreise -drei5s6tr -d4reiv -d2rej -2drek -2drel -dreli3 -2drem -2dren -dre3na -d2reo -2drep -2drer -d2res -4d3res. -dre6schm -dre6schw -4d3rese -4d3resi -dres6san -dres6s5ei -dres6sel -dres6s5er6 -dres4st -4d3rest -d3resu -2dret -2drev -d2rew -2drez -d2ré -2d1rh -2d3ric -4d3rieg -drie4n3 -3drif -4d3riff -d4rift -4d3rind -4d5ring. -2d3rip -4d3risi -4d3riss -d2rit -drit6terf -4d3ritu -d2ro -2drob -4d3rock -d3roec -droe4s5c -4drogg -3droh -4drohr -5drohu -dro4id. -4d3roll -3dros -d3rose -2drot -2d3rou -2d3rov -d3row -2d3röc -drö4s3c -2d3rub -3d2ruc -2d3rud -4d3rue3b -drue7cke -d3rueh -2druf -2d3ruh -4drund -d4runt -2d3rut -2d3rü3b -drü5cke -2d3rüh -2d1s -dsa5cha -d2s3ad -d4s3aem -d2sak -d4s3alk -d4s3all -d4s3ane -d4s3ang -d4sanm -d4s3ant -d4s3anz -d2sar -ds5assi -dsau2 -d4s3auf -d4s3aus -d4saut -ds2äu -ds2ca -dsch2 -d4schin -dsch4r -5d4schun -d2scr -d2s3e2b -dse2e -dsee3i -d2s3ef -ds3ehr -ds4eign -ds5einl -d4s5eint -d4s3ele -d4s3emb -d4s5e4mis -ds5ende. -ds5enden -dsen5er -d4s3eng -dsen3s -d4s3ent -d4s3er3e -d4s3erf -d4s3erh -d4s3erk -d4s3erl -d4s3erm -ds3err -d4s3ers -d4s3ert -d4s3erz -dse4t -d4s3eta -d4s3eth -d2s3ev -dsex2 -d4sexa -d4sexe -d3s4ha4k -dshi2 -d2sid -d4s3ind -ds3ini -d4s3int -d2ske -d3s4kel -d4skir -d4skis -d5sklav -d2sko -d3slaw -d4sli -d2so2b -d2s3oe -d2s3op -dso2r -d4s3ori -d4s5ort. -d2sö -ds4ped -ds4pen -ds4perb -d4speri -d4sphi -d2spl -ds4por -d6sporto -d4sprae -d4sprä -d4sprei -d4spro -ds2pu -dss2 -ds3sa -dst2 -d6s5taeti -ds5tauf -d4s5täti -d2ste -d3stec -d3steg -d3stei -d4steil -d5stell -d3s4tep -d5s4terb -d5s4tern -d3steu -d4sth -ds2ti -ds4til -d4stoch -ds4tol -ds4top -ds2tr -ds5trad -d5strei -ds5treu. -ds2tu -d2sty -d4suml -d4s3ums -d4sumz -d2s3un -d4s3url -2dt -dta2d -dta2n -dtan3e -d3t2as -dtas3t -d3t2ax -dtä2s -d3tea -d3tec -dteer3 -d3tem -dte3na -d3tep -dte3ra -d3tes -dte5sta -dti2d -dto2b -d3töp -dt3ral -dt3rec -dtre3s -dt1s2 -dt3sa -d3tur -d3ty -du3al. -du3all -du3alm -du3alp -du3alv -du3alz -du3ar -du4b3li -du3ce -due5bel -due3br -du3el -du3en. -du3ena -du3enb -due3p -duese4 -du3ett -duf4ter -duf4to -duf4t3r -dug2 -du2ga -2d1uh -du3ist -d3u2kr -du4l5ar4r -du4l5art -du4l3au -du4l3ei -dul3s -du2lu -2d3umb -2d3umd -2d3u2m3e -2dumf -2dumg -2d3umh -2dumk -2duml -d4ump -2dumr -4d3umsc -2d3umt -2d3umv -du2n -2d3una -2d3und -dun4de -2d3une -2d3unf -d3unga -4d3ungl -dungs3 -2d3uni -dun4kl -2dunr -4d3unsc -4dunsi -dun4st -dunst5r -2dunt -2dunw -2d3unz -du3os -dur4c -durch3 -2d3urk -2d3url -2d3urn -dur2s -4d3ursa -2d3urt -dusche4 -du6sch5ec -du6sch5ei -du4schl -du4schn -du4sch5w -dü3bel -d3über -düns3 -düse2 -2dv -dv4en -2dw -dwa2 -dwa5sche -dwasse4 -dwert5a -dwesten6 -dwes6tend -2dyac -dy3an -dydi2 -dygene4 -dy3la -dy3lä -dy2le -dy1o2 -dy2sa -dy4s3l -2dz2 -2e1a -ea2be -e2abi -ea2bl -ea2br -ea2c -ea3chi -eadli4 -ea2dr -eae2 -eaga2 -ea2ge -ea2gi -ea3g2l -ea2ka -e2akd -eak3e -e2akg -e2akr -eal3ac -eal3ak -e3a4lei -e5a4l5ent -e5alen4z -e5alerb -e5a4l5er4g -e5a4l5ert -e3alex -e2ali -e3a4l3in -eal4ler -ealmo4 -ea4lob -ea4l3on -ea4lop -eals4p -ealti4 -ea2l3u2 -eam1 -e2ame2 -eam3ev -ea4mid -eamm4 -eam3s2 -eam3tu -eanden4 -e2ane -eanet4 -e3anf -eanla6gente -eannet4 -ea4n3ob -e4ant. -ean4t3a4 -ea2q -ea2ra -ear3ac -ear3ak -ear3am -ea3rat -ea4r3ei -ea4r5ene -e4arer -e2ari -ear3n4 -ea2ro -ea2r3u -e3a2sc -e3asf -easin4g -ea2sp -eass2 -e2at -eate2 -eat5ens -eater3 -e3atm -eat3s2 -e3at3t2 -e2au. -eau4fe -e3auff -eau5f4re -eaug2 -ea4u3n -eaussen6dung. -eau5ste -ea2va -e2az -e1ä2 -2eb. -eba2g -ebah6ner -eb3anz -eba2p3 -e3bas -2ebc -e3bef -ebein4s -e4b5eise -ebe4lan -ebe4lau -ebe4ler -ebel5in -e3bem -ebe4na -ebe5n6au. -ebenen5e -e5benu -e5benz -ebe5rad -eber5as -ebe4rel -eberg5s -e3bes -ebe4s5e4h -ebe4ses -ebe4sor -ebet4s3 -e4b3inb -eb3lag -e4b3le. -eb3lem -eb4len -eb5les -eb4leu -eb4lie -e3bloc -eb4loec -e3blöc -ebni2 -e2bo2b -ebo3ef -ebö2s -2ebr -eb2re -e3bru -e3brü -eb4s3ac -eb4s5ang -eb4s3as -eb4s3au -eb4s5ele -ebs5end -ebs5ene -eb4sep -eb4ser -ebsi2 -eb4sid -eb4sis -eb2sp -ebs5panne -ebs3pe -eb6s5taet -eb4s5tät -ebs5tem -ebs5ten -ebs3t4h -ebs5tic -ebs5tie -ebs5treu -eb2su -e4bueb -e4bunt -ebu2t3 -e2büb -eby2t -e1ce -ech3ab -ech3ae -ech5ang -ech5aus -ech3ä -e3che -eche4c -eche3e -ech3ei -eche6reib -echer6fah -echer5o -echer6zie -e3chi -echi3e -ech3la -e3chlo -ech3lu -ech3m -e4ch3n -e2cho -3echo. -ech5obe -echoe4f -ech5org -ech3ö4f -e2chr -ech3ra -ech3rä -ech3re -echsau4 -ech6s5en6d -ech4s3o -ech4spa -ech4spo -echst5ri -echs4tu -echt5er6ha -ech7ters -echt6s5ac -echt6s5ag -echt6s5eid -echt6sen -echt6stie -ech3uh -ech3w -e1ci -e4ck. -e3ckag -e4ckef -ecke4n -e4ckep -eck5erfo -ecker6ken -eck5ermi -e4ck5erw -eck5erze -e4cks -eck5spo -3eckzä -e1cl -e3co -e3cr -ecre2 -ec3s -e3cy -2ed. -e2da2b -ed2ac -eda5chu -ed2ad -ed2dr -ede4al -ede3e -edel5au -e3dens -eden4sa -eden4s5e -eden4sp -eden6s5tau -eden6stem -eden6s5tu -ede3o2 -edere4 -ede6r5eic -eder5er4 -ede6rinn -eder3o4 -eder3ö -ede3sk -ede5stei -edes4ti -ede5str -edia3l -edi3p -editen4 -edi4tin -ed2o -ed4oc -edost3 -edpol4n -e3d4rei -ed3rik -3edris -ed4s3al -ed4sco -ed4seh -ed4s3es -edsi2 -ed2sk -ed2s3o -ed4s3ti -ed4sto -ed4s3um -ed3te -edte3e -edte5in4 -edt2h -edue3b -edu2f -e2d3ug -e2dum -ed2ü -edü3b -edy1 -edys2 -2ee -ee3a2 -e3e4ben -eeb2l -ee3bs -ee1c -ee2ce -e3e2ck -eede3s4 -eedi4e -ee2dr -eed3s2 -ee1e2 -eees3 -e3eff -eef2l -ee3fo -eef3s -eeg2 -ee4hep -e3ehr -e1ei -ee3ig. -ee3imp -ee5in4se -ee3i4sc -eei4sp -e3e4lek -eele4n -eele4s -e3elf -eem4a -ee5man -e3emb -e3emp -ee2na -ee3n4ac -ee3n4ad -ee3nae -eena4g -ee3nas -een3er -een4es -een3e4v -e3en4ga -een4o -ee5nor -een3sh -ee1o2 -ee1p -eer3ad -eer3al -ee5ram -eer3as -e5erben -e3erbt -eer5eis. -eer5eisa -ee4r5en4g -ee5r4e4s3 -eere4t -eer5eti -e3er4fi -ee3rhi -e3erhö -e2eri -e3erlo -e3erlö -e3ermä -eero4be -ee4r5oef -ee4r3oh -e3eröf -ee3rös -eer3sh -eeru2 -ee4r3uf -eer4und -eerz2 -e5erzeu -ee1s2 -ee4seb -ee3sh -ees3ka -ee3sl -ee3sp -e3es3s -e3e4tat -eet5ein -ee2th -ee4tik -ee3tr -ee4t3ro -ee1u -eeu2f -eeun2 -eewa4r -e1e2x1 -e1f -2efa -e2f3ad -e4f3aeu -e2f3a2g -ef5akti -ef3ana -e2fap -e4farc -e2fat -ef3auf -ef3aus -e4fähn -e2fäu -2efe -ef3ebe -efecht4 -e4f3edi -e4f5eing -ef5eink -efe4l5ei -efel7ler -efe2n -efen3e -e4f5ener -e4f3enk -e4f3ent -efere4 -efer5eg -efe4rei -efer6gebn -eferin6d -e4f3eta -e2fex -ef4f3as -3effek -ef4fle -ef4fli -efig3s -efi4le. -e4f3ins -ef3int -e2fj -ef2le -ef4lue -e3f4lü -2efo -e2fob -e2f3o2f -e4f3ora -ef2r -ef3rat -ef3red -ef3reg -e5fris -ef3rol -ef3rom -efs2c -efs2p -efste4h -ef3str -eft5anz -eft5erkr -ef4top -ef4t3ri -ef4t3ro -eft2s -ef3ums -2eg -egab4r -ega4la -ega4l5in -ega2m -ega5sta -eg2äh -egd2 -ege5in4s -ege5in4t -ege4l5ac -ege4l5au -ege4lec -ege6l5ei6er -ege4ler -ege4l5in -ege4lo -egel6s5tur -egena4 -ege4nag -egen5ec -ege6nero -ege4rak -ege4ral -ege4ran -ege4r5as -ege6reck -ege6rend -egeren6g -egerer6k -ege6rers -egerin6d -ege6rin6g -egerin6t -ege6s5tro -ege3u -egg2l -eg3ins -e4g3lac -egler5e -eg3nei -egori4e -eg3rän -eg4sal -egsauto6 -egs2c -egse4r -egsgene6rat -eg3spe -eg4stä -egus3t -e2gy -e1ha -eh3aka -eha3kl -ehalt4s -e4h3ans -e4h3ant -eh3anz -e4h3app -eha4sc -eh3aug -eh5ausw -eh5ausz -e1hä -e1he -eh3eff -e4h3ein -eh3e4la -eh5elek -ehelf4 -eh3elt -3e4hema -eh3emb -e4h3e4mi -e3hen -ehen4se -ehens5ei -3ehepa -eh3epi -eher4an -eher4fa -eh5erfah -eher4fo -eher6gebn -eher4ke -eher4le -e6h5ersta -eher4zä -ehe5str -e1hi -e2h3i2d -eh3ill -eh3imb -eh3inf -e4h3inh -e4h3ins -ehi2r -ehi2t -ehla4c -eh3lae -eh3lam -ehl3ap -eh4l3as -ehl3au -eh3lä -eh4l5e4be -ehl5einh -eh4lel -ehle4n -eh3leu -ehlkop4 -ehl3o4r -eh4l3öf -ehl6s5ein -ehl4sin -2ehm -eh4mab -eh4mant -ehm6ens -ehmen6sp -ehmest5 -ehm5estr -eh3mu -eh3nad -ehn3e4c -eh3nel -eh3no -ehn6s5eid -e1ho -eho2l -eh3oly -eh3ope -eh3org -eh3osm -e1hö -eh3öff -ehö4r5er -ehr3ak -ehr3al -ehr3an -ehr3ap -ehr3än -ehre4c -ehr5eck -ehr5ei. -ehr5eic -ehr5eie -ehr5ein -eh4r5eis -eh3ren -ehre4n5e -ehrens6t -ehrer6fa -ehrer6la -ehr5er6tr -eh3reu -eh3rie -eh3roc -eh3roe -ehr3of -eh3rom -eh3rot -eh3rö -ehr4s5a4g -ehr6sein -ehr4ser -ehr6stras -ehrungsgene6 -ehs2 -eh3sh -eh3sk -ehst2 -eh3sta -eh5stei -eh3str -eh3stu -2eht -eh3t2a -eh5ther -e1hu -eh3unf -e4huni -ehu3s4a -e1hü -eh3w -e1hy -2ei1a2 -2eiä -ei4b5aer -ei4b3ak -ei4bär -eibe3e -ei4b5ele -ei6b5erla -ei4bid -ei4b5las -ei4bli -ei4b5rau -ei4brea -ei4broc -eibu4t -ei4b5ute -ei3ce -eiche4n -eich3r -ei3cl -e2id -ei4dar -ei4dau -5eidech -ei4d5eis -eid5erfue -ei6d5erfü -eides4t -eide5ste -eidi4e -4eidn -eid4str -e2i1e -eie4be -eie2d -eie3f4 -eie2l -eie2m -2eie2n -eienge4 -eie4r3a -eierei6s -ei5ereu -5eiersu -eie2t -ei3f4lo -eif5lut -e2ig -2eig. -ei4g3ak -ei4g5ans -4eige. -eige5in4 -e5igel. -5eigene. -ei4glo -ei4glö -ei2g3n -3eigne -3eignu -ei2go2 -ei4g5rad -ei4g5rat -2eigs -eigs4p -eig5str -2eigt -2eigu -4eigw -ei4kak -ei4k5ank -ei4kli -ei4klo -ei4kni -ei4l3ab -eila4n -ei4l5ane -ei4l5ang -ei4lant -ei4l5anz -ei4lar -4eil3c -eild2 -ei4lein -eiler6geb -eiler6kl -eilf2 -ei4l5inf -ei4l5ins -eil4ip -ei4lob -ei4l3op -ei4m3aa -ei4mab -ei4m3a4g -eim5all -eim5alp -ei4m5ans -ei4map -eima4ta -eima6ter -eima4to -eim5aus -e4ime -ei4m5ent -ei4m5erl -ei4m3i4d -ei4moe -eim5org -2eimp -eim3sa -ei4m3um -ei4mur -eina4b -ein3a4d -ein3ae -4einam -ei4n3an -ein3a4s -4einat -ei2n3ä -einde5ra -ein6d5erk -ein4duk -ein4dus -4eineb -eine5in6f -eine5inn -einen6zy -ein6er. -eine5ras -ein5erbe -ein5erkl -ein5erkr -ein5er6la -ei6n5er6sc -ein5ertr -ei4n5ess -ei4n5e4ti -ei4n3eu -ein6fant -ein4fek -ein4fiz -5einflus -4einfo -ein4fo. -eing2 -e3ing. -e3ingt -4einhab -ei4n3i4d -4einie -e3i4nit -eink2 -ein6karn -ein3n2 -ein4nen -ein6nere -ei2n3o2 -5einord -ein4seb -ein6seln -ein4sid -ein6stab -ein6stal -ein6star -ein4sul -ein4sz -ein4teg -ein6terf -ein6terp -5eintrag -ein6trig -5eintrit -eint4s -ein5tsc -2einu -e4inver -ein4ves -ein3z4s -ei1o -eio2b -eio2p -eio2s -2ei1p -eip2f -2eir -eir2c -e3irr -ei3ru -ei4s5aer -eis5ala -eis5alt -eis5ane -ei4s5ant -eis5anz -eisch5er6k -ei6schertr -ei6scher6z -ei6schin -eisch5war -ei6s5elas -ei6s5en6te. -eise5ra -ei4s5erf -ei4s5erg -ei4s5erh -ei4s5er4l -ei4serm -ei6s5er6sa -eis5ersp -ei6s5erst -ei4s5erw -ei4s5erz -eises4s -ei4sex -eis3id -ei3s4ky -ei4soe -ei4som -ei2sö -ei3spe -ei3spr -eis6s5chen -ei5stei -ei5stel -eisterin6f -eis4tol -eis4tri -ei5stue -ei4s3um -e2it -2eita -ei4t3ab -eit5aer -eit5alb -eit5alk -ei4t3an -ei4tap -ei4t3ar -ei4tas -ei4tat -2eitä -ei4t3är -4eite. -eit3e4h -ei4t5emi -4eiten -eitende6m -ei5t6er. -eit6err -ei4tess -ei4t5eti -ei2t3h -4eiti -ei4t3in -eito4r -ei4trau -eit5rin -4eits -eit4s5a4g -eit4sal -eitt4 -eit5tr -ei4t3um -ei4t3ur -eit3z2 -2eiv -ei4zar -ei4z5eig -ei4z5inf -ei4z5ins -ei4z5int -e2jak -2e3ju -2eka -ekaden6z5 -eka4l5oe -ek4ar -ek2ä -1ekd -e1ke -eke2n -e1ki -e1k2l -ek2n -eko4man -ek4or -ekor4da -2ek2r -ek2sa -ek4s5te -2ekt -ek4t3ae -ekt5akt -ek4tan -ekt5ank -ek5tante -ek4t3at -ek4tau -ek2t3ä -ek4t3eb -ekt5eig -ek4t3e4l -ekt5ende -ek4tep -ekt5erf -ekt5erg -ekt5erk -ek4t5erl -ekt5ero -ek4t5err -ek4t5erz -ek2th -ek4t5ins -ekt5int -ek4t3ob -ekt5ret -2ek2u -ekue3b -ek2ü -ekü3b -e3k2w -1ekz -e1la -e2l3aa -el3abb -ela4ben -e3labi -e4labk -el3abl -ela4br -el3abu -e4lada -el3add -el3aef -e2l3af -e4laho -e2l3ak -e3lake -ela2l -e4l3ama -el3amb -e4l3ame -el3amm -e4l3ana -e4lanb -e4landa -e4lanf -e4lanh -ela3n4i -e4l3ank -e4lanm -el3ann -e4lanp -el3ans -el3anz -e2l3ap -e2l3ar -ela4ri -el3a4si -el3a4sp -el3atl -e4l3atm -el3atr -e4l3att -el3au. -el5aufg -el5aufl -el3auk -e4l5ausb -el5ausl -el5ausr -e1lä -e3läd -el3äf -3el4bis -elb4lei -elb2r -elb3sl -elb6star -elb4ste -elb6stim -elb4st5o -elb4stu -el1c -elda4r -el4d5ari -el5d4eic -el4d5er4f -eld5erhe -eld5erho -eld5erhö -el6d5er6pr -el6d5er6sa -eld5ersp -eld5erst -eld5ertr -el6d5er6we -el4do4b -elds2 -eld3st -2ele. -4elea -elea4r -e6l5e6bene -el5echt -e3lecl -el3edi -ele4g5er4 -2eleh -el5ehe. -4elei -e6l5ei6ern -el3ei4f -el3ei4l -e4l5einb -e4l5einf -e4l5eing -e4l5einh -e4l5eink -e4l5einl -el5einn -e4l5einr -e4l5eins -el5einto -e4l5einz -el5eisf -el5eisz -el3eiw -e5lekti -5elektr -e2lel -el3elb -el3ele -el3eli -el3ell -el3els -el3elt -e2lem -e3lem. -e3lema -el3emb -3eleme -el3e4mi -e3lemm -el3emp -4elen. -elena4 -elen6klo -e4l5ense -e4l5entf -e4l5ents -e4lentw -el5enzi -el3epe -4eler -e5ler. -eler4e -e6l5er6fah -e6lerfin -e6l5erfol -e4l5erga -e4l5ergä -e6lergeb -elergene6 -eler5h -elerin6f -elerin6t -e4lerke -e4l5er4lä -e4l5ernä -e4l5eroe -e4l3erö -el5erre -el5ersa -el3erw -e6lerwei -e6lerzae -e4lerzä -eles4p -el3ess -ele5ste -el5e4tag -ele4tik -elet4ta -el3eur -e5leut -ele2x -e3lex. -el3ex3e -el3exk -el3ext -elf2a -3elfd -elf2l -elf3st -elgi5er. -elgi5ers -el3g2l -elg2r -e2lid -e3lie -eli5ef. -eli5eff -eli5efs -elif3 -e4l3i4ma -e4l3imb -el3imp -e4linfe -el3inj -eli5no -el5inso -e4l3inv -el3i4on -e4l3iso -eli4t3h -elk4a -el4k5anl -elke4n -elk2r -elk5sta -ell5akt -el4larb -ellar4c -el4l5arm -ellar6ma -el4l5art -ell5aufb -el6l5auft -4ellä -el3le. -ell5einf -ell5eing -ell5einh -ell5einr -ell5eins -ell5einz -el4l3el -el3len -ellens4 -ell4er -eller7ge -eller6tra -eller6zeu -elle3s -el4lid -el4l5inv -3ellip -el4l3or -ell3s4k -elm4stu -2elo -e2l3oa -e4loel -eloe6se. -e4lonk -el3opf -el3orb -el3orc -el3o4ri -e4l3orn -el3ort -e4l3osz -e3lot -e2lov -elö4se. -el4sb -els5eins -elsgene6 -el4sob -el4strü -el4sun -el4tans -elt5art -el4t3e4k -elt5eli -elt5entr -elt5er6be -elt5erd -elt5er4f -elt5erhe -elt5erk -elter6ke -elt5er6le -elt5erlo -elter4n -elt5ero -elt5er6sc -elt5er6tr -elter6zeu -el3thü -el4t3i4b -elt3se -elt5s4po -elts4tr -e1lu -e2lum -el3umb -el3umm -e2l3u2r -el3use -elu2t -el3ute -e4l3uti -el3uto -e1lü -e2lya -el4z5aep -el4z3ar -el4zäp -elz5eig -el4z5ene -el4zoe -e1m -e4mago -e4m5alar -e4m3alk -e4manb -e4manf -e4m3anm -e4manor -e4manp -e4manr -e4m3ans -e4manw -e4manz -e4m3asp -ema3to -em3aus -em2ä -3embol -3embry -em4dae -em2dä -em2dr -em4due -em4dum -em2dü -e2meb -e2mef -e2meg -eme4n3e -emen6gel -e4mengt -emen6t5ak -emen6tan -emen4t5h -e2meo -e2meö -e2mep -em3e4po -eme4r3a -emerin6t -emer4le -emes3t -e2m3ex -emi4e3i -emie3s -4emil -2emin -emin4a -e4m3inf -e4m3inh -e4m3inn -e4m3ins -e4m3int -e4m3inv -3e2mir -emi6scha -e5mitg -emi3tr -em4m3ap -emma3u -em4m3ei -e4moel -emo3ra -emo3s -5e4motio -3empfa -3emp3fä -em3pfl -3emphy -empi4re -em4p3le -emp5lin -empo3s -em4san -em4s3au -ems5ele -em4spa -ems4por -em4stal -em4s5tau -em3stu -3emuls -em3una -em3urs -e3mus -en1a -e3na. -e2naa -en3ab3i -ena2c -en5ach. -e3naep -ena5er. -e4naho -en3a4ke -e3nal. -e3nale -ena3li -4ename -e4namp -en3ams -ena2n -e4nant -ena4pa -en3ark -ena4sc -e4natl -en5atom -enat4s3 -e4natt -e4nau4f3 -e6nausta -enauto6re -ena2x -ena4zi -en1ä -e2näl -e2när -4enärm -4enbom -en4ce. -en3dac -enda4d -end3ak -en4d3al -en4d5ang -en4dark -end5eins -ende5int -ende4l5ä -end6er. -endes4p -ende5sta -ende5sti -ende5str -en4d3ex -en4do4b -3endog -en5d4ort -en4dox -en4dral -end5rom -end5s4au -end3s4l -ends4p -end2ü -2ene. -e5nece -ene4ck -e4n3eff -ene2h -en3ehe -e5nehm -e5neien -4eneigu -en3elb -en3elc -en3e4le -en3elf -e4n3eli -e4n3elt -4enem. -en3e4mi -4enen. -enens4 -e4n3epe -en3epi -en3epo -e2n3e2q -4e5ner. -ene3ra -e4n3erd -ener5ei -ener6ei. -en5ereig -e4n5erek -en3erf -ener4fi -e4nerfo -en4erfr -e4nerg -en5erge -5energi -en5ergu -e4n3erh -e4n5erla -e4n5ermi -e4n5ermo -en5ermu -4enern. -e4n5erna -ener5ob -ener5oef -ene5roes -ene5rös -e4n3err -4eners. -e4n5ersa -e4n5ersp -en3erw -e4nerwi -e4n3erz -4enes. -en3ess -e4n3eth -en3eul -4eneut -e4n3e4va -e4ne2x -enf2a -enf5einh -en5fes -3engad -3engag -5engelh -engel6st -enge6ne. -enge6nen -enge6net -engerin6s -5englis -en3gna -en3gno -eng5oel -3engp -eng3s4c -eng5stä -enhof5s -2enic -eni2d -en3ide -e3nie -eni5er. -eni5era -eni5erp -eni5ers. -eni5erv -en3i4ko -en3ill -eni2m -en3ima -en3imi -enin3a -eni2o -e4n3ion -2enis -eni4sa -eni5sta -e3niv -enizi5e -en4k5aus -en6kenla -en4k5er4f -en6k5erha -en6k5erkr -enker4l -en6k5ersa -en4k5ort -enk5spa -enk5spo -enk4su -enk2ü -4enlic -en4nef -en6n5er6fa -3enniu -enn5sa -e2n3oa -en3o4be -e4nober -en3obo -e4n3o4br -e4n3oe4d -e4n3oel -e2n3of -e2n3oh -en3oli -en3olm -en3oly -eno2n3 -en3opf -e4n3opi -eno2r -en5oran -en3orc -e4n3ord -e4n3ori -en3ork -e4n3ost -en3osz -e5noti -eno2w -e1nö -en3ö2d -enre4gu -en5rei -ensa5es -ens5amte -en4sanf -en4sang -en3sat -ensau4g -ensau4s -ensche6f -en6schlic -en4s5ehr -5ensemb -en4s5en5e -en4s5enz -ens5erei -ense4t -en4s5eta -en5s4law -en4s5off -en4s5ont -en5spen -ens4pu -4enst. -enst5alt -en6starb -4enste. -4ensten -ens7teru -4enstes -4enstl -4enstm -4enstn -en4stob -4ensts -4enstv -en4sumf -ens5umv -ens5umw -4ensym -en4tanm -ent5anst -entar6tik -en4tela -en4telä -en4tema -ent5emo -en6t5en6de. -enten6den. -en6t5en6des. -ente4n5e -5entera -ent5erben -en4t5erf -5entero -en6terre -ent5er6sc -ente4t -ent5eta -3entfl -4entfo -3entgi -5entheb -en4t3id -3entla -5entloh -3entlu -3entlü -5entnah -en4t3ob -ent5oel -en5tré -5entrieg -5entropi -5entsag -3entso -3en4tw -6entwert. -6entwerte -6entwerts -4entwet -3entz -4entzuw -en1u -e3nu. -e3nueg -enu5ett -enu4ma -enu2r -2enu2t -e4n3uto -e3nüf -e3nüg -e3nül -e3nüp -enü3st -e1ny -enz2ä -enze4ne -enz5erfo -en4z5erh -en4z5erk -enzer6ku -enz5erla -enz5ermi -en4z5ero -enz5erte -enz5erwe -en4z5erz -en4zf -en4z3i4d -enzo4l -4enzuf -4enzyl -3enzym -4enzys -2eo -e1ob1 -eo4ber -e2obo -e2ob2r -eo1c -e3ochs -eod2 -e1oe2 -eo3edi -eo3em -eo3en -eo3ers -e3off -e3okk -eo3la -e3o2ly -e1on -e3o4n3ae -e3o4n3an -e5onatom -e3o2nä -e2ono -eo1o -e3o2pe -e3opf -e3ops -e1or -eo3rau -e3orb -e3ord -eor5eis -e3org -eort2 -eo1s2 -e3os. -eo3s4e -e3oste -eoti3e -eo3ul. -eo3uls -eou2t -e1ö2 -ep2a -epa2g -epan6zerf -epas6s5er -ep3ega -ep3ent -ep3ex -2epf -eph3el -eph3em -e1pi -3e4pid -3e2pig -3e2pik -3e4pile -epin3g4 -e3pio -3episo -3epit -ep2n -e2poc -epoli3 -3e4pos. -ep2p3a -ep4pei -ep2p3f -ep2p3l -ep2pr -2e1pr -ep2s3c -ep3sh -ep4t3ae -ep4tal -ep4tau -ep2tä -epter4s -ep6t5erst -e3pu -epu2s -e1pü -2eque -3equip -er3ab. -4e3raba -e4r3abb -era4ben -er3abr -er3abt -er3ach -e3rad. -er3add -e5radi -e4radj -er3adr -erae4b -era5ebe -e3raed -er3aef -er3aeh -er3ae4m -er3aer -era5er. -er3aes -er5aeuß -era5fra -era4gen -er3a4gi -e3rago -er3ahl -era4ho -er3aic -e3rake -e3rakl -era4kro -e4r3akt -e3ral. -e3rald -e3rale -eral4eb -era6lent -e3rali -eralin6d -era6ling -eralin6sp -er5alke -er3amb -er3amn -e4ramp -e4r3a4na -erana5t -e5rand. -e6rander -er5andr -e4ranf -er3ang -e4ranga -e4rangr -e4ranh -er3ank -e4ranl -e4r3a4no -e4r3ans -e6rantei -e3ranu -e4ranw -er5anzu -er5anzü -e3rao -e3rap -er3apf -er3apo -era4rie -e4rarr -e4rars -e4rart -e3ras. -er3as4b -er3ask -er5ass. -er5assi -er3a2ß -e3rat -e4ratel -e4r3atl -e4r3atm -er3au -e3raub -e3rauc -erau4f3 -e4raufs -e3raum -e3raup -erau4se -e4rauss -e4raust -er3a4va -e3raw -e4r3ax -e1rä -er3äb -e3räd -er3äf -er3äh -er3ä2m -er3äp -e2r3äs -eräte3 -er3ätz -erbe5ers -5erbeti -3erbig -erbin4f -erb4lo -er5brec -5erbreg -er1c -er5ch4e -erche5i -er3chl -5erdaep -erd5ant -erd5anz -3erdäp -3erdb -erdens4 -erdes4t -3erdg -er4d5inn -er4dob -erd4os -3erdöl -erd3st -2e3re. -er5e4ben -er2e2c -er5echs -ere4del -er5e4dit -ere3en -er3e4he -e3rehi -e3reho -er3ehr -4e3rei. -4e3reia -4e3reib -erei6be. -erei4de -e3reie -e5reiga -e5reigeh -5ereigni -4ereih -4ereik -4ereil -er3ein -e4r5einh -e4reinl -e4reins -e4reint -er5eisar -e4r5eisb -er5eisd -er5eisf -er5eisr -erei5str -er5eisv -er5eisw -4ereit -erei6t5erk -4ereiu -e3reiv -4ereiz -er3ele -er5e4lit -4e3rem. -er3emb -er5e4mig -ere4mis -er3emp -e4r3ems -e4r3emu -4e3ren. -e3rena -er5endg -erengene6 -eren5sk -e4r5entn -e3renz -eren6z5en6d -eren6z5er -er3epi -e4repos -4e3rer. -4ererb -e4r5erde -erergene6 -e4rergo -e4r5erho -erer4hö -e4r3erk -e4r3erl -erer4lo -erer4lö -4erern. -e4r5erna -e4r5erne -e4r3ero -4er6ers. -er5ersat -e6r5ersche -e4r5ersp -e4r3ert -e4r3eru -4ererv -e4r5erwa -e4r3erz -2eres -e3res. -e4r3ess4 -er5essen -ere5star -ere4tab -e4r3eth -eretri5 -e4retu -er3eul -er3e4va -e4r5e4vid -erf2a -5erfahru -erf4e -erfen4s -5erfolg. -5erfolgr -erf4ta -erg5ant -er4g5are -5ergebn -er4g5ein -er4g5elf -6ergenau -5ergenb -6ergeni -5ergenp -3ergom -er4g3op -erg3s4o -erg3s4p -erg5sta -erg5sti -erg5sto -erg5str -erg5stu -5erhaltb -er3ho -e1ri -e4r3iat -e3rib -2eric -erich6t5er6s -e4r3ico -4erie -erie3b4 -erief4 -erie5fl -erie4ne -erien5en -eri5er. -eri5ers. -e3rif -er3ima -er3imb -er3imm -er3imp -4erin. -e4r3ind -e5rind. -e3ring -er3inh -e4r3i4ni -e4r3in5k -er3inl -6erinnen -5erinneru -5erinnr -er3ins -e5rins. -e4r3int -e4r3inz -e3rio -e4r3ion -e3ris -4erisc -er3isl -er3iso -e4r3isr -3eri3tr -4erkaj -er5kat -erk5ends -5erkranku -erk4sen -erk4ser -5erkundu -5erläut -5erlebn -5erledi -er5leg -4erlöh -5erlös. -erls2 -er3ma -er4m5alg -er5man -erme5sz -er3mo -erm2u -ernab6sa -erna5che -er4n5alg -er4n5all -er4n5alt -ern5erbe -ern5erei -ern5erst -4ernet -erne4te -er4n3ob -erno4r -er4n5ori -4ero. -e3roa -e3roba -e3robb -e3robi -er3o4bl -er3obm -e3robo -er3o4br -er3obs -er3oe4d -e3roeh -e3roen -er3oer -e5roes. -eroe4s5c -er3off -e3rofr -e3rog -e3rohl -er3ohm -e3roi -e3rol -e4r3oli -e4r3oly -e3rom -ero3ma -er3omb -e3ron -ero3n4a -ero4nan -er5opal -er3o4pe -er3opf -e3ropo -er3o2r -eror4a -eror4e -e3roro -4e3ros. -e3rosa -e3rose -5erosio -e5rosit -ero5sto -e3rotr -er5otter -e3row -er3ox -e3rozy -e3röc -er3ö2d -er4öh -e3rös. -erö4s3c -erp2 -er5pen -5erprobt -5erprobu -er3rae -er4rant -er3rä -5erregt -erri5er -5erringu -5ersatzo -5erscheinu -ersch4l -ersch4m -ersch4r -ers2e -er3si -er4sos -5ersparn -er7stati -5erstatt -er7statu -erst3h -er3sto -er4t3a4d -er6t5akts -er4tax -erte4c -ert5eck -er4t5er4f -er4t5er4h -er4t5er4k -er7tersc -ert5erst -er4t3ho -ert5spr -2e1ru -eruch4s5 -e4rueb -eruf4s3 -e4r3uhr -er3u2m -erum3a -erums4 -er3und -e4rundu -er3unf -er5unga -er5ungl -erungs5 -e4r3unk -er3up. -3e4rupt -eru2r -er3u4se -er3uto -er3uz -e1rü -ervenen6 -5erwerb. -e2rya -er4z5erk -erz3te -erz4wi -es3abl -e2s3a2d -e4s3aem -e4s5aerz -e4s3all -e4s3alt -e4s3ami -esam4t5r -es5andr -es4apa -es3art -e4s3ato -e2s3a2v -e2s3ax -es3b -esbi5er. -e3scen -e4s5cham -esche6mi -e6schent -e6sch5er6f -e6sch5er6l -esch5op -e2sco -es2cr -es3da -ese2c -ese3er -es3ehr -ese3i4m -e3s4ek -3eselc -ese4len -ese4ler -esen4ac -ese4neu -esen5th -ese5ref -e5seri -eserin6t -es5erke -es3erö -esers4 -eser3u4 -e5serv -ese5str -eseu2 -ese2x2 -es3ext -es3f -e3sham -es3han -es4har -es3he -eshi2 -es3him -es3hor -es4hut -2esi -esi5ers. -e2s3i2k -e4simp -e4s3inf -es3ini -es3int -e2sk -es3ke -es3kl -e4sky -es3lo -es3m -e2s3o2b -e2s3od -eso4de -es3ofe -eso3pa -e4s3orc -e4s3ori -es3ost -e1sö -2e1sp -es4pec -es4poe -es6portp -es6portv -es5rei -4ess. -es4s3ad -ess5alt -essau4s -3essay -4essä -4essc -es3sch -esse5er -esse3g -esse6l5ers -es3ser -es6serfah -ess5er6ho -esser6hö -esser6sae -esser6sat -es6ser6sä -4essk -4esso -es4sof -es4sph -es4spu -es4stec -es4stop -essur4k -e4st. -estab4b -e4stabg -e5stael -e3stah -est5anst -es5tant. -est5anza -esta4s -est5asc -e4s5tat. -e5statu -est5auf -e4staum -estauto6r -es4tec -e5stein. -e5steine -e6st5eing -est5eink -est5einl -est5einr -est5einsc -est5eint -estell5a -es4t5emi -est5empo -e4s3ten -esten6ga -est5engl -es6ten6se -est5entr -est5ents -ester6ken -est5ernä -ester6n5o -est5eroe -es4t5er4ö -est5er6tr -ester6weit -es4t5es4s -es4thy -es4t3id -es5tij -e3stil -est5ing -es4tins -e4s5tipp -estmo6de. -es4tob -e3s4toc -e3stol -es4top -es4t5org -es4t5ort -es2tr -es5trac -es5trag -e5strang -est5rate. -e5streb -e5streck -e5strick -e5strik -es5tros -e3strö -e4strun -e3s4tub -e3s4tue -e4stueb -est5ums -es4tu4r -est5uri -est5urk -e4stüb -es4tüm -e3sub -e3suh -e4sumg -e4s3ums -e4s3umw -es4und -es3unt -e3sup -es3urb -es3url -es3urs -es3urt -esu2s -es3w -e3sy -e2sz -es3ze -eß3ele -eße3re -e4ß3er4l -et5a4ben -et2a3c -eta3e4b -et3aeh -et3agi -etal4l5o -et3a4mi -eta3na -e4tanh -e5tanj -et3ano -e4tanp -e4t3arb -et3art -eta4t3r -et5aufs -et3auk -et3äh -2ete -et3eff -et5e4he. -e4t3eim -et5eins -e4t3eis -etel4n3 -etend4 -eteng4 -ete4n3i -e4t5ents -e4tentw -eten6z5er -e4t3e4pi -ete4r3ä -ete6r5eis -etergene6 -e4t5erhö -eterich6 -eter6loe -e4t5er4lö -eter5oe -et3erö -e5t6erta -e3tes -eth4a -3e4thik -3ethn -3ethol -et3hon -e2t3i2d -eti4kar -eti4kl -eti4kr -eti2m -eti3na -et3ind -e4t3inf -etin4g -e4t3inh -et3ini -eti4ta -eti4th -et3obs -et3o2f -eto4n5al -eto4nar -eto4ner -etons4 -e4t3orc -e4t3ord -e4t3org -et5rade -etra5e4d -et3rec -et2ro -et2sa -et3sac -ets3ae -etscher5e -etsch5wu -ets5eng -et4sim -et3sö -ets3pr -ets4toe -ets4tu -et2su -et4t3a4b -et4tad -et4tae -et4t3ak -et4t3a4m -et4t5ang -et4tanm -et4tans -et4tap -et4t3at -ett5auf -et2tä -et4temb -ette4n -et4tesp -et4t5ess -etto5en -ett5ope -et2tö -et2tr -ett5rei -ett3ru -et4t3um -2etu -3e2tui -e4t3ums -et3ups -et3use -ety2l3 -3e2tym -2etz -et2za -et2zä -et4z3eb -et4zed -et4z5eig -et4z5ene -et4z5erw -etzes3 -etze4t -et4z5eti -et4zoe -et2zw -eu1a2 -eu4b3le -eu4b3li -euch4ta -euchter6s -euch4to -euch4t5r -euder4e -eudi4e -eu1e -eue2d -eue3fa -eue2m -eue3p -eue4rei -euer5eif -euer5eig -euer5eis -euerer6r -euerer6s -euerer6t -eue4ret -eue4ro4 -euer5of -eu4esc -e3u4ess -eu3f2r -eu2ga -eug3an -eu6g5endm -eu6gense -eu4gid -eugin4f -eu4g5in4g -eu2gl -eu4gre -eugs2 -eug3sp -eug5sta -eug5sti -eug5stu -eu3hu -eui2 -eu3id -euil6le. -eu3in3 -eu3ir -3eukal -e2ul -e3um. -e4ume -eum3p -eum4s3p -eu3nas -eun2e -eune4b -eung2 -eu4ni. -eu4nio -eu4nis -eu1o2 -e3up. -eu3p2f -e3upg -3euph -eu4r3an -eurasi5e -eu4r5ast -eura3t -eur3c -e4u3re -eure5in4 -eur5eis -eurin4f -eur5in4s -e4uro -euro3s -eur4sau -eur4so -eur6s5tae -eur4stä -2eus -e3usar -eu6sch5ei -eu6schel -eu6sch5erz -eu4schl -eu4schm -eu6schue -e3usd -e5useff -eu3s4ei -e5usevan -e3usg -e3usk -e3usn -eu3spr -eus4s3c -e3usst -eu3sta -eu3sto -eu3s4t4r -eu3su -eu3tei -eute5in4 -eute5r4a -eut2h -3eu3tha -eut4o -euts4 -eut6scha -eut6schm -eut6schn -eut6schr -eut6schw -euve4 -2eux -eu1y -eu2za -eu4zec -eu2zo -eu2zw -e1ü -2ev -3e2vak -eval4s3 -evan4z3 -ev4en -eve3r4i -e2vl -ewal6t5er -e2w3au -ewebe5i -eweb4s -ewei4sc -ewer4g -ewer5te -ewi2e -e3win -ewinde5s -e3wir -e3wit -ew4st -ewuerzes6 -ewürzes6 -2ex. -3exam -2exan -ex4ans -3exek -e2x2el -ex3ele -2exes -2exf -e1xi -3e2xil -e2x3in -ex3l -ex3or -1exp -2expu -2exs -2exta -4extet -ex4tin -ext5rei -2extv -2exu -e2xum -ex3v -2e1xy -ey1 -eya2 -ey2dr -ey4ere -ey2ne -eyo2 -eys2 -2ez -e3za -e4z3enn -eze3re -ez3erk -eze3s4a -ez2o -e3zoh -ezu2e -ez2w -é1 -é2es -égi2 -élu2 -ém2 -ér2 -éu2 -è1 -ène1 -2ët -ê1 -ê2t -1fa -fab2 -2f3abb -fa4ben -2fabf -2fabg -2f3a2b3l -2fabn -f3a4bre -2fabs -2f3abt -2fabw -2fabz -fa2ce -fach3a -fa4cheb -fa4ch5ec -fa4chel -fa6cheng -fa6chent -fa6ch5en6z -fa6ch5erf -fa6cherk -fach3i -fach3o -fach5sk -fachs4p -fa5chung -fad3a -fa4dan -fa4del -f3ader -fa2di -f3adl -2f3a2dr -fad3t -fa2e -fae2c -fa3eck -f3aem -fa4er -f3aerm -4f3aerz -faesser6 -faes6s5ers -faes6serw -fae4ßer -4f3aetz -f3aeug -2f3aex -fah6l5ent -fahler6z -faib2 -fa4kad -fa2ke -fa3la -fal4k3l -fal4l3a -fal4l5ei -faller4 -fal6l5ere -fal6lerf -fal6lerg -fal6l5erk -fal6l5erm -fal6l5err -fal4lo -fal6scha -fal6schei -fal6sch5l -fal6schm -fal6sch5w -fal4t3r -fal4zw -fa4mei -2f3amp -f3amt -3famy -f4an. -fa4nar -fan4d5er -fan4dre -fan4d5ru -fan4drü -fan4gei -fan6gert -fan4gra -fani2 -fa4n3in -2f3ank2 -2fanl -2fann -f3a2no -2fanr -2fanw -2f3anz -f1ap -fa2po -fa3r4a -far4b3a -far4b3ä -far4bel -far4b5er -far4b5in -far4b3l -far4b3o -far4b3r -far4b3u -far4m5an -4f3armi -2f3a2ro -far4r3a -far4rh -2f3art -2f3arz -fa3s4a -faser5e -fa3sh -4f3assi -4f3astr -fa2ß -f1at -fa2to -2fatt -2faue -2fauf -f3aug -f4aul -f3ausb -f3ausl -3fav -1fä -fä1c -2fäf -fäh4r3u -2f1äm -2fäq -2f3ärm -2f3ärz -fässer4 -fäs6serk -fäs6s5ers -fäs6serw -fä4ßer -2f3ätz -2fäug -2fäx -2fb2 -fbau3 -fbilder6 -fbil6derh -2f1c -f3ch2 -2fd2 -fdi2e -f3ds -1fe -featu4 -2f3ebb -fe4ben -fe2c -fecht6e -fechts5e -2f3eck -fe4del -federen5 -fede4ro -fedi4e -fe4dit -fe2dr -feein5 -2feff -fef4fr -fefi2 -fef2l -2f3e2he -fehl3a -fehle4 -feh4l5ei -feh6lerinn -feh4l3o -f3eib -2f3eic -fei4l3a -fein6d5ei -fei6n5erz -4f3einl -4f3einn -4f3einq -fe5ins. -fe5inta -fe5into -fei4nu -4f3einw -4f3einz -f3eis -4feise -3fek -fek4ta -fek4t3r -3fel -fe2la2 -fel3ae -fel3at -fel3au -fe2l3ä -f4elb -fel5bi -felda4 -fel4dac -fel4d5ar -fel4d5e4m -fel6d5end -fel6d5er6k -fel4dra -fel3e4c -4f3e4lef -fe4l3eg -fel5ein -4f3e4lek -fe4l3en -fe4l3er -fe4les -feli4n3 -fel5ins -fell5ans -fel6lein -fel6l5ers -fel4lin -fe2l3o -fel6spir -fel5sti -6f5el6tern -2f3emb -fem2m -2f3emp -f4en. -fe4na4g -fe2nä -fend2 -fende4f -fen5end -fe4n5ent -4f5energ -fen5er5o -fen5erö -fene4t -fen3f2 -4fengp -fe4nid -fen3in -fen3i4t -fenk2 -fenn2 -fe2n3o2 -fen4sal -fen7schl -fen3sh -fen5skl -fens4po -5fenst -4f5ente. -4f5en4ten -4f3entf -4fentla -4f3ents -4fentw -4fentz -fe2n3u -f4er. -fer3ab -fer3a4d -fer3ae -fer3al -fe5rant -fer5anz -fer3as -fe2r3ä -4ferd. -ferde3 -4ferde. -4ferden -ferdes4 -4ferds -fe4r3e4b -fer5eck -3ferei -fe4rein -fe4r5eis -fer5eiw -fe4rek -fer5ell -fe4r5erd -fe4r5er4g -fe4r5erw -fe4r3e4v -fer6fahru -fer6fuel -4f5er4fül -6fergebn -fe4r3i4d -ferien5 -ferin4f -ferin4t -feri4on -fe4riso -fer6lebn -fernab6s -fern6au. -ferner6k -fer3oe -fer3of -fer3ö -ferr2 -fer6regt -fer3s4m -fers4p -4f3e4rup -fest5a4b -fest5ak -fest5al -fest5an -fest5ar -fest5ei -fes6t5end -fes6tero -fes4th -fes4tin -fe4s4t3o4 -fest5ru -fe4tag -fet5ero -4f3e4tik -fet4t3a -fet4t3h -fetti5s -fett3r -fett3s -2feu. -4f5eu5cha -feuer5o -2f3eul -2f3eut -f1ex -2fexp -1fé -2f1f -ffa5chu -ff3a2d -ffang5s -ff3ans -ff3ark -ff3arm -ffa2t -ff3au -f2f3e2b -ffe2e3 -f2f3ef -f4f3eig -ffei4n -ffe5in. -ff5einb -ffe5ini -ff5eink -ff5eins -ffel3a -f4felem -ffels4 -ffe2m -f4f3emi -ffe4rec -f4f3erh -f4f5erze -ffe2t -ffet3r -f2fex -fff2 -fffen4 -f2f3im -f2fj -ff3län -ff3lät -ff3lec -f2f3o2b -ffo2r -ff3ori -ff3ott -f2f3ox -ff3rak -ff4ren -ff3roa -ff3rol -ffsa4 -ffs3al -ff3sam -ffs3au -ffs5end -ff4stau -ffs5tie -ffs5tut -ffs5tür -ff3ti -f2f3um -ff3urs -f2fy -2fg2 -fga4s5er -fgas5te -fgeb2 -fge3na -fger4 -f3gh -fglim4 -2f1h -fh4au -fho4l3a -1fi -3fi. -fi2ar -fi3ask -fi3at -fici3 -fi2do -fid2s -2fi2dy -fie3b4l -fie4br -fi3er4f -fi4gec -fi2g3r -fi4kanf -fi4kanr -fi4k5ans -fi4kanw -fi4k3as -fi4k3el -fiker4s -fi6k5erst -fi4kin -fi4kle -fi4klo -fi4klö -fi2kn -fiko2 -fi4k3ob -fi4k3op -fi4kor -fi2k3r -fi4l3an -fi4lau -fi5laufb -filauf6n -filb2 -fi4l3ei -filen4 -fi4l5end -fi4lent -fi4lep -fi4les -fi4l3in -fil4ip -fil4ma -film5as -fil4mä -fil4m5ed -fil4mei -filo2 -fi4lob -fil3st -fi2mi -2f3imp -f4i3na -fi4nant -fi4nar -fin4dia -fi4nel -fi4net -finge6ro -fingst5r -2f3inh -2f3inj -4f3inse -fin4sp -fin4ste -fin5sti -fi3ol -2f3i2ri -fi4sch5a -fi4schä -fi6sch5ei -fi4schl -fi4schn -fi4sch5o -fi4schr -fi4sch5w -fi3s2h -2f3i2so -fi3s2p -fis3t -fiste4 -fite2 -fit3el -fi3ti -fi4tin -fiti3s -fito2 -fi4t3or -fi4t5rat -fi4xel -3fiz -fi2za -f2jor -2f1k2 -fkaken4 -fka4t3i -2fl. -fl2a -f3lab -3f4laec -f3laed -4flaen -f3laeu -f3lamp -flan6sch5l -flanzer6 -f3lap -f4lasc -f3lats -f3lauf -flauma4 -3f4läc -2f3läd -2flän -f3läu -fl2e -2f3le. -2f3leb -f2lee -4f3lein -flei6sch5r -4f3leit -flek5t -2f3ler -2f3li. -f5lich. -f5liche -2f3lig -2f3lil -f2lim -f3lind -fli4ne -4f3ling -4f3lini -4f3lins -f2lit -f3loef -f3lok -2f3lon -3f2lop -3f2lor -flo3ra -5floss. -3floß -3f2lot -flo2w3 -2f3löf -f2lög -f2luc -f4lueg -f3luem -f3luf -f4lug -flu4g3a -flu4ger -3flugz -f3lup -fl4ur -flur3i -3f4luss -flut3o -f2lüg -f3lüm -3flüs -f2ly1 -2f1m2 -fma2d -fmarschach6 -f3mu -2f1n2 -f3ne -1fo -foa2 -fodi2 -2f3oe2f -2f3oek -f3oele -fo3eli -foer4s5 -f3oe2s -2f3o2ly -fo4nad -fo4n3al -fo4nam -fo4n3an -fo4nap -fo4nar -fonat4 -fo4nau -foni4en -fo4n3in -fo2nu -f1op -2fo2pe -fo3rat -4fordn -ford3s -2f3org -3form -for4mae -for4m5a4g -for4mas -for4m5au -for4mä -for4m5ei -for4min -for4mor -forni5er. -for6schl -for4st -forst5ei6 -for6st5r -for4t5ei -for4t5er -for4t3r -fort3s4 -fo3r2u -fo3sta -fo3to3 -fo5toe -1fö -föde3 -2f1ö2f -2f1ök -2f1öl -för2s -förs5t -f1ö2s -2f1p2 -2fq -fr2 -f3ra. -frach6ta -frach6t5r -2f3rad -fra4dei -4f3raed -f3raet -f3raf -frage5i -2f3rah -2f3ral -fr4a2m -fram3a -f2ran -f3rand -f3rann -2f3rap -f3rase -fra3st -fras5ta -3fraß -f3rat. -f3rats -3frau. -f3raum -fraus4 -frau5se -2f3räd -f2rän -2f3rät -f3re. -f4rean -f5recht -fre4e -2f3ref -f3rege -f3regi -f3reh -f4rei. -freib4 -4f3reic -f4reie -frei3f4 -frei3k4 -frei5str -2f3rek -2f3rel -3f2rem -f3ren -2f3rep -3f4req -f4resc -4f3resi -fres4st -f3rest -f4rete -f2reu -2f3rez -f1rh -2f3ric -fri3d -2f3rig -2f3rin -frin6g5er -f3rip -3f4risc -fri6scha -f4rist -2f3roc -2frol -3f2ron -fro4n3a -fro2s -f3rose -frost5r -3f2ruc -f3rud -f5ruehr -f3rues -fru6fere -f3ruh -f3rum -2frun -f3rut -f3rühr -früh3t -f3rüs -2fs -f3sac -f2sad -fs3aer -f2sak -f3sakr -f4s3all -f4s3alt -fs3amp -f2san -fs3ana -fs3ane -fs3ang -fs3anz -f2sar -f2s3as -fs3a4te -f4s3auf -fsau4n -f4saut -f2sär -fs3äus -f2sca -f2sce -fsch2 -f5schal -f4schan -fscha4o -f5schau -f4schef -f5schra -f5schri -f2scr -f2s3e2b -fse2e -fsee3i -fs3ehr -fs3ein -f5seit -f2s3e2l -fs5endes -fs3e4n3e -f4s3ent -f3seq -f2s3er -fse3r4u -f5serv -fse4t -fs3eth -f2sex2 -f2si2d -fs5iden -fs3imp -fs3inf -fs3ing -fs3int -f2ska -f2sko -f3s2ky -fs1o -f2sob -fs3och -f3soh -f2sop -fso2r -f1sp -f5spann -f4spas -f4spel -f2sph -fsport5e -f4spot -fs5prie -fs4pul -fs3s2 -f3sta -fs5taeti -f4stag -f4s3tak -f4stale -f4s5tank -f4stas -f4s5täti -f3steg -f3stei -f4steil -f3stel -f5s4tern -fs3tes -f3steu -f3s2ti -f4s3tip -f4stit -f3s2to -f4ston -fs4tras -fs4tre -fs5tren -f3s4tri -f5strom -f3strö -f5s4tuec -f3stüc -f4s5tüte -fs3ums -f2s3un -f2s3ur -f2s3ut -f5szene -2ft -f2ta. -f4t3aeu -ft3ala -f5talg -ft3all -fta2n -f4t3ana -ft3and -ft3ang -f4t3ano -f4t3ans -ft3apf -f2t3a2r -ft3att -ft3auf -f2t3äu -fte2c -ft3eck -ft3e4he -ft3eig -ft3ein -f4t3eis -f4t5elek -f4t3eli -ft3emb -fter3a -f4t5erke -fter3o -ftersau6 -ftes4p -f4t3es4s -fte5stei -ft3eth -f4t3e4ti -fte3u -ftgu4t3 -f2t1h -f3t4hem -f2t3id -f3tik -f2t3im -f2tin -ft3ind -ft3inf -f3ting -ft3inh -ft3inn -ft3ins -ft3iso -ft3le -f2t3o2b -ft3oef -ft3of -f2tom -fto2p -ft3ope -ft3ora -ft3org -f2t3ot -ft5raed -f5t4ran -ft3rau -ft3ril -ftritt4 -ft3ruh -fts1 -ft2sa -ftsa4g -ftsau4 -ft4sän -ft3sch4 -ft4se4h -ftse4l -ft4sem -ft4send -ft4seng -ft4sep -ft4ses -ft4seu -ft2si -ft2so -ft4staf -ft3sti -fts5tick -ft6stier -ft4stor -ft6streu -ft6strop -ft4stur -ftu3en -ft3ums -ft3unb -ft3und -f4tunf -ft3urk -ft3url -ft3urt -ftwa4r -ftz2 -1fu -fuch4s5a -fuchs5t -f2ue -2f3ueb -fuen4f5a -fuen6fei -fue4r -f2uh -fuku3 -2ful -fuld4 -f3ult -2f3umb -fu4mei -fu4m5erz -fu2m3i -f2umm -2fums -fun6derg -fun6d5erh -fund3r -fund5st -2f3unf -4f3ungl -2f3u2ni -3funk -fun4ka -fun6kerk -fun4k3l -fun4k3o4 -fun4k3r -fun4ku -2f3unl -2funm -2funr -2f3unt -2funw -f2ur -fu4re. -2f3url -f2us -2fusc -fu4schw -fus4sa -fus4sei -fus4s5er4 -fus4sp -fu3s2t -fu4ß3er -3fut -1fü -2füb -fühl4s5c -fühl4s5e -fühl4st -fü2r -2fv -2fw -f1y -2fz2 -fzeiten6 -fzei6t5end -fz3t -fzu4gin -fzug4le -1g -3ga. -2g3aal -2g3abd -gabe3e -gabe3i -3gabel -gabe3r -2gabf -2gabg -gabi4t -gab5itu -2g3a2bl -gab5le -gab4ri -gab4ru -gab4rü -4gabsc -5gabst. -4gabstu -4gabstü -4g3abtr -2gabw -gaby2 -2gabz -ga1c -gade3s -g4a3di -2g3adl -gae5is. -ga3emp -4g3aend -2g3aep -ga3erh -g3aerz -gaes3 -gaeste5 -ga3eta -ga3fl -3gai -ga3il. -ga3in -ga1k -ga4kad -2g3akt -ga4kus -ga3la -gala3e -g3a4lar -4g3alau -ga4l5en4d -g3algo -4g3allt -4g3alta -4g3altd -g3a2lu -ga4m3al4 -4g3amaz -ga3me. -ga4meb -ga4mec -ga4mep -ga4m3in -gam5ma -gamo2 -gam3sc -gam3t -ga2mu -4g3anal -ga4nan -gan5ato -ga4nau -g4anbr -gan3d2 -gan5erh -gan5erk -gan5ers -2ganf -gan4gar -gan6g5e6be -gan4g5el -gan6g5er6b -gan4gr -gan4g3u -gan5inn -gank2 -4g3anke -g5ankun -2ganl -4ganmu -ga4n3ob -ga4nor -2ganp -2ganr -4g3ansi -g5ansti -2ganw -ga3ny -gan4zw -2g3app -2garc -garde3 -4g3area -ga4rena -g3arg -ga3rin -2g3ark -2g3arm -ga3r4o -gar2s -4g3arti -2g3arz -ga2s3a -ga2sä -gasche4 -ga3se. -ga4s3ei -ga4s3el -ga4s3e4m -gas5endr -ga4s5ene -ga4s5ent -ga4serz -gas3e4t -ga2si -ga2so -gass2 -6gassess -gas5tank -gas4tel -gast5elt -gas6tens -gas4tin -gas6t5rae -gas4t5rä -gas6trol -ga4sum -gat2h -ga3tha -2g3atm -gau4eri -2gau2f -ga3ufe -2g3aug -g4auk -ga3unt -4g3ausb -4gausd -4g3ausf -4gausg -4gausr -4g3auss -4g3ausw -g3ausz -2g3aut -g1äh -2g1äm -2g3änd -2g1äp -2g3ärz -gä4st -gäste3 -2g1ät -2gb2 -g3ba -gb4au -gbau3f -g3bä -gbes2 -gbi2e -g3bl -g3bo -g3bu -g3bü -gby2t -2g1c -2gd -g2dab -gd2ad -g2d3ak -gd3all -g2dan -gd3anz -gd3art -gdä1 -g2d3ei -gd3els -g4d3ent -g2d3er -gd3eth -gdi2e -gd3ins -g2do2b -g2dop -gd3roc -gd3roe -g2d3rö -gd3s2 -gea2d -gea2n -gea2p -ge3at -ge4at. -ge4ate -4g3ebbe -geb4le -geb4lin -gebot4 -ge1c -ged2 -gedi2 -4gedig -ge1e2 -ge3eg -geer3e -ge2es -geest3 -ge3ev -gef2 -ge5far -2g3eff -gegen5s4 -ge3g2l -ge2gn -geh5aben -ge4h5erl -geh5ste -ge3im -gei2n -4g3einb -gein4di -ge5in4fo -4g3eing -gein4ha -ge3ini -4g3einl -ge3in4n -4g3einr -gein6ten -gein6ter -ge3in4v -4g3einw -ge3ir -geise4 -gei3sh -ge3iso -geis4s5c -geist5r -2gek. -gela4c -gela4n -ge4l5ano -ge4l5ant -ge4lanz -g4elb -gel5bi -gelb5s -gel4del -gel4dem -gelder4 -gel6d5erh -gel6derp -gel6ders -gel6dert -gel6derw -gel6drue -gel4drü -ge3lec -ge4lef -4g3e4lek -ge4l5end -ge4l5en5e -ge4l5ent -gel5erd -gel5er5e -gel5erf -gel5erg -ge4l5erk -ge4l5erl -ge6l5erna -gel5er5o -ge4l5ers -gel5erz -ge4less -gel3f -gel4fe. -gelfen4 -gelgene6 -gel5int -gelk2 -gel3la -ge4l3om -ge5lot -gel4sal -gel6s5chor -gels4t -gel5ste -gel5sti -gel5sze -gel6tent -gel6terh -gelz2 -gem4a -ge4ma. -2g3emb -2g3emp -gem2u -gen4aes -ge5n4au. -ge5naue -ge5naust -gen6auste -4genda. -gend5in5 -ge4n3e4g -gen4eh -ge4n3el -ge4nemi -ge6n5ende -ge4nep -ge4n5er4b -ge6n5erei -ge4nerf -gen5er5in -ge4n5erk -ge4n5erl -ge4n5ern -gen5ersc -gen5erst -ge4n5ert -gener6wa -gener6wä -ge4n5esk -ge4ness -gen3f2 -geng2 -gen4ge. -gen6geln -gen6gels -genger4 -ge4nim -ge4n3in -genk2 -gen3n2 -ge4noc -gens5amt -gensch4 -gen6semb -gens5ert -gen5ska -g3entf -4gentfa -4gentfe -gen5t4he -4g5entst -4gentw -ge4nun -gen6z5art -gen6z5eig -ge3oh -ge3oo -geo4ri -ge3ou -ge3p2 -g4er. -ge3ra -ger3ab -gerabdru6 -ger5adm -ger5akr -ger5ala -ger5alb -ger5all -ger5alp -ger5alt -ger5amp -ge4r5ant -4gerdg -ger5ecke -ge4r5eid -ger5eig -6g5ereign -4g5e4remi -ger5ende -ge4r5en5e -ge4reng -ge4r5en4s -ge4r5ent -ge4rerf -ge4r5erh -gerer6ku -ger5ersa -ge4r5er4w -gerer4z -ger4hoe -4g3er4hö -gerin6de -gerin6di -gerin4f -4g5erklä -germi4 -ger4nau -ger3ne -4g3ernt -ger5obe -ge3roc -ge3rod -ge3roe -ge3ros -ge3rot -ger3o4v -ge3rö -ger6regu -4g5er4seh -gers4p -4g5er4wac -ger6wart -ger6weit -6g5erwerb -ge2ry -gerz2 -ge1s2 -g4es. -ges5anz -ges5auf -gesch4 -ge4s3eb -ge4s3ec -ge3sei -ge4s5ein -4g5e4sel. -ge4s5elt -ge3sen -ge4s5er5e -ge4s5erf -ge4s5erz -ge4sex -ge4s3ou -ge4s3ph -ge3spu -ges3sa -ges3se -ges3sp -ges3s4t -ges4tan -gest6ei -ge4s5ter -ges3th -ge4s5tur -get5ange -ge4tant -ge4thi -4getik -getin5 -ge3to -4getow -get2r -ge4t5res -get3sa -get3s4p -gets4t -get3t -4getui -2g3eul -ge3unk -ge3urt -ge3u2t -2g1e2x1 -2g1f2 -gfa5chu -g3fe -gfen2 -2gg -gga2t -g3ge -ggeb2 -gge4r5er5 -ggi3en -gg2lo -g2gn -g2gof -gg2r -ggs2 -2g1h -gh2a -gh4ae -ghafe4 -3ghale -gham3 -gh4äh -3g2het -gh1l -g3ho -3gh2r -ghs2 -ght1 -gh2ti -gh2tr -gh3w -gi1a -gib2l -gich2 -gicht3 -gi2e -gie3b -gie3d -gie3f4 -gie3g2 -gie3i -gi5enab -gie5nac -gie3n4e -gienein6 -gi3enk -gi3enr -gi3ens -gie6r5eis -gie5res -gi5ergi -gie5sp -gie3st -gif4t3r -gift3s4 -3gig -4g3i4gel -gi2gu -gik3a -gi4kan -gi4k3el -gi4me. -gi4mef -gi4mes -gi4met -4g3impf -gi3na -2g3ind -gin4di -2g3inf -gin4ga -6gingeni -gin4g3r -2g3inh -gi4nit -gin6nenb -4g5inner -gin4sa -4g3inse -4g3insf -4g3insp -gin6standh -gin4ste -2g3int -2g3inv -2giok -3gis -gi4sch5w -4g3isel -gi3sta -giste4 -3git -gi3tu -giu3s -2gj -2g1k2 -g3ka -g3kä -g3ko -gko3ef -gkraf4 -gkue3b -gkü3b -2gl. -gl2a -2g3lab -g3lach -4gladu -g5laend -glaes5c -2g3lag -2glah -4gland -glan4dr -glan4za -glan6zen -glan6z5er -2g3lap -g4las. -gla4sa -gla4s5el -4g3late -4g3lauc -g3laue -4g3lauf -4glaun -4glaus -g3laut -2g3lav -2glaw -4g3länd -g2läs -gläs3c -4gläuf -2glb -2gle. -g3leb -4glebi -g4lebo -g4lebö -gle3c -g3led -gleer4f -g2lef -2g3leg -g4leha -4glehr -4gleib -g4leic -4g3leid -4glein -g4leisi -glei6terk -glei6t5er6sc -glei4t5r -g2lek -g2lel -g2lem -g5lemmi -2glen -g2lep -2gler -glerei6g -g4les. -4glese -g2let -4gleuc -g4leur -g2lew -2glh -2gli. -3g2lia -2glib -2glic -3g2lid -3g2lie -4g3lieb -4g3lief -g3lieg -2glif -g2lik -2glil -g2lim -2glin -glings5 -g2lio -gli3on -gli3o4s -2g3lis -4glite -g4litt -g2liz -4g3lize -gl2o -g2loa -g2lob -g2loc -4g3loch -4gloes -4gloew -g2lof -4gloks -g2lom -g2lon -g2lop -4glorb -2glos -g2lot -2gloy -4glöch -2glöh -2glös -2glöw -2gls -g2luc -g3luch -g4luec -4gluef -2gluf -2glun -2g3lus -3g2lüc -2glüf -g3lüg -2glw -3g2ly -2g1m -gm4a3b2 -g3me -gm4en -gmen4tr -gmen4tu -gmi1 -gm4un -2gn. -2g3nac -g3naep -2gnag -2g3nah -gna2k -gna4l5er5 -gna4l5in -2g3nam -gn3ana -2gnaq -2gnar -2gnau -g3näp -2gnb -2gnc -2gnd -gn2e -g3neh -2gnei -2gnel -gne2n -gnen3e -4gn3ent -gner6geb -gne4ta -gne4t3r -2gneu3 -2gnf -2gng -2gnh -g2nie -g2nif -g2nin -4gninf -4gn3ing -4gnint -2g3ni2s3 -gnise4 -gniser5 -2gnk2 -2gnl -2gnm -g2no -2gnob -2gnop -gno3r -4gnori -2g3not -g3nov -2gnp -2gnq -2gnr -2gns2 -2gnt -3gnu. -3gnum. -gn4umf -4gnumg -4gn3ums -2g3nun -2gnut -2gnv -2gnw -g2ny -2gnz -go2al -goa3li -g1ob -2g3obj -g2ob4l -gob2s -go1c -2g3o2e2f -2g3oeh -4goel. -go3enz -g1of -go2fe -2gog -2g3ohr -go1i -go3k -gol2a -gol4d3a -gol6d5eng -gol4d3o -gol4d3r -gol4fa -gol4fel -gol6ferl -gol4f3l -gol4f3r -golf3s4 -g3o4liv -3gon -gon2a -go4n3an -gon5ato -2gope -go3pfl -go4pos -2gopt -go3rap -2g3ord -2g3org -g4oro -2g3ort -go2si -go3s2l -go3ste -2g3osz -go5thae -got6t5erb -got6t5erg -got6t5erl -got6t5erw -got4th -go1y -2g1ö2f -2g1öh -2g1öl -2g1p2 -gp4la -g3pu -2gq -g3qi -gr2 -gr4ab -gra4ba -gra4bi -gra4bl -gra4dei -gra6dend -4g3radl -4g3raed -graes5c -2g3rah -2g3rak -grammen6 -gram6m5end -gram6m5er -g3ramp -gra3na -g5rand. -4g3rann -2gr4ar -gra4ra -gra4r3e -gra4ru -gra4s3a -gras5st -g2rat -gr4au -4g3raub -grau5f -4g3raum -grau5sk -2gräd -gräs3c -4g3räub -2g3re. -2grea -g2reb -2g3rec -4grede -4gredu -g2ree -2g3ref -gre4fr -4grege -4gregi -4gregl -4greic -4g3reih -4greim -4grein -g2rem -g2ren -4g3renn -5grenz -gren4za -gren6z5ei -gren6zer6f -gren6zer6t -gren4z5o -g2rer -g2res -4g3rese -gres6ser6 -gres4st -2grev -2grez -2g3ric -gri2e -4g3rieg -4g3riem -g5riese -2grig -gril4l5a -4g3ring -4g3rinn -grit2 -g2ro -gro4b3a -gro4b5ei -gro4bl -gro4b3r -2groc -4g3roec -4g3roeh -groe5le -2g3roh -2g3rol -gron2 -4g3rose -gros4s5a -gros4s5c -gros6s5el -gros6senk -4grout -2g3röc -2gröh -grö3le -grue5be -4g3ruec -4gruf. -4grufe -g4ruft -2g3ruh -gr4un -grun6dere -grun4g -2g3rut -grü3be -2g3rüc -2gs1 -gsa2b -g3sack -g2sa2d -g4saeh -g2sa2k -g4sall -g4salt -g4sama -g4samt -g3sani -g4sant -g2s3a2p -g2sa2r -gs3ars -g2sas -g4sato -g3sau. -gsau4e -g4sauf -gsau4r -g4saut -g2sa2v -g2säc -g2säh -g2sän -g3sät -gsby2 -gsch4 -g5schal -g5schat -g3schä -g4sche4f -g5schei -gs4chen -g3schl -g3schm -g3schn -g3schr -g3schu -g3schü -g3schw -g2sco -g2se2b -gse2c -gse2d -g3sege -gs2eh -gs3ehr -g3seil -gse4kl -gse2l -g3sel. -gse4me -gse4n3e -g4sent -gsen5th -g2se2p -g2ser -gser3e -gser5in -gser5oe -g2ses -gse4t -g2seu3 -gs3e2v -g2se2x2 -gsgene6t -gsi2d -g3sie -g3sig -gsi2k -gsi2n -g4s3ita -g3s4ki3e -gs3li -g2so2 -gso3c -g3s2ol -g3soz -g3s4pac -gs4pate -g3spek -g3spit -g5sporn -g5s4pule -gsrue4c -gsrü4c -gs3s2 -gssau4g -gst2a -gs5tabl -g5stabs -g3stad -g5staer -g3stah -gs3tak -gs5tank -g5stark -g5start -gs3tas -gs5tat. -gs5tate -g5stati -g5statu -gs5tau. -gs5tauc -g3stäb -gs3tät -g5steig -g5stein. -g5steine -g5stell -gst5ent -gster5e -gs6t5erei -gs4terz -g3s4tew -gs5the -gs4thy -g3stic -g3stir -gs3tit -g5stock -gs4toer -g3stör -gs4tras -g5s4trat -g5streb -gst4ri -gs4t5rit -g5stroem -g5strom. -g5stroms -g5s4trop -g5ström -g3stüc -g2su2 -g3sub -gs3un -gs3ut -g5szene -2gt -gt3auf -g3te -g3ti -gti2m -gt3inh -g3to -gt2st -gt3stu -gtu3en -gtu5ers -gu3al. -gu3am -gu3ant -gu3ar. -gu3ark -gu3ars -gu2d3r -2gueb -2gued -2g1u2f -2g1uh -gu3in. -gu3ino -gu3ins -gu3is -2g3ulm -2g3umb -gum4e -2gumf -2gumg -2gumk -2guml -2gumr -2g3ums -2g3umw -2g3una -4g3unem -2g3unf -4gunge4w -4g3ungl -2g3u2ni -2g3unk -2gunr -gun4st -2gunt -2g3unz -gure2 -guren3 -2g3url -gur4t3h -gur4t3r -gurt3s -3gus1 -gu2sa -gu2sä -guschi5 -gus4s5er -gus4s3o -gus4sp -gus4st -gu4st -gust5a4b -gust5ein -gus6t5en6de -gus6terl -gus4t3h -gus3to -gus4t3r -gu2t3a2 -gutan5e -gu4ted -gut3ei -gu4t5er4h -gu2t3h -gu2t3i -gu2t3r -gut4sp -gu2tu -gutwa3 -2güb -gün2 -3gür1 -2gv -g3ve -2gw -g3wr -3gym -2gz2 -gzeu4gi -gzeugin6 -g3zh -ha2a -ha3ak -ha4b3ei -h3abla -ha4blä -h3a4blu -2habn -h3a2b3o -h3a2br -2habw -hach3a -ha4chec -ha4ch5en -hacher6be. -hacher6ben -hach5eri -ha3chi -hacks4 -ha4cku -2hada -ha4del -hade4n -haden6s5o -ha4d3in -h3adle -hado2 -h3a2dr -ha3dri -had3st -haede4 -h3aeff -h3aehr -ha3elb -ha3e4n3e -4ha3er. -h3aerg -4h3aerz -hae6s5chen -4h3aest -4h3aeug -hae5us. -haeu4s5c -hae5usp -h5affae -h3affä -haff4l -haf3r -haf4ta -haf6t5erl -haf4t3o -haf4t3r -haft4se -haft6stem -2hagi -ha2gl -h3ahl -h3ahm -2hail -2haka -ha4kad -ha4kes -ha2kr -h3akro -2hal. -hal3ab -ha3l4al -ha3lam -halan4c -hal5ang -h5alarm -ha3lat -hal4ba -hal6bein -hal4bel -hal6b5ern -hal4bin -hal4bl -hal4bo -hal4b5ra -hal4bri -hal4bro -hal4b5ru -hal4b5rü -halb5s -2hale -hale4n -ha6l5ents -2halh -2halk -h5alkal -hal4lan -hal4l5au -hal6l5ere -hal6lerf -hal6lerg -hal6l5err -haller6s -hal6l5ersc -hal6l5er6w -hal4loe -2halp -hal4sei -hal4sp -hal4sta -hal4stu -hal4tal -hal4t5an -hal4tau -hal6t5erfo -hal4th -hal4t3r -haltungscha6o -h3a4mei -h5ameri -hamot4 -h3ampf -2h3amt -h4an. -2hana -hana3e -ha4nal -ha4nan -2hanb -h4anbe -2hanc -han4d3a4 -h4ande -han4d5er -han4dob -han4dr -hand5ru -hand5rü -hand3s4 -2hane -ha4nem -han4f3i -han4f3l -han6g5end -han4gro -2hani -han6kerl -han4ko -han4kr -2h3anl -2hano -ha4nor -2hanr -4h3ansc -h5anten -h3anth -2hanz -h5anzei -h5anzug -h3apf -ha2pl -h3a2po -h3a2pr -har2a -hara3d -2h3arb -2harc -h4ard -har4da -h3area -har4fa -har4f5ei -har4fr -2harg -h3argu -ha3rh -ha3ri -h3arm. -h5armem -h5armen -h5armer -h3arms -har4ne -h3a4rom -4h3arra -2hars -har4tal -har4th -4h3arti -har4tri -har4za -har4zo -4harzt -2hasa -ha4schm -has2h3 -hasi5er -ha3spo -has4sa -hasser4 -has6s5erf -has4sp -hass3t -h1a2ß -ha2t3a -hat5ein -ha2t3h -h3atl -ha4t3ra -ha4trä -ha4t3re -ha4trou -2hats -h3attr -hat4zer -haub2 -h3audi -h3aufb -4h3aufd -h3aufk -h5aufla -hau5f6lie -hau5flu -4h3aufm -4haufn -h3aufs -4h3auft -4h3aufz -2haug2 -h5auge. -h3aukt -hau4men -hau4mer -hau4sa -hau4s3c -hau4seh -hau4sel -hau6s5en6t -hause4r -hau6seri -hau6serp -hau4so -haussen6 -haus6sen. -haus5sk -haus5tau -hau4s5ur -hau4ta -hauten4 -hau6teng -hau6terh -hau6terk -hau6tern -hau6terw -hau4tin -hau4tr -haut5s -ha4ve. -havil4 -hä2c -häde2 -h3äff -2h3ähr -2häi -hä2kl -hä2la -h3ärg -härm4s -hä3ro -2h3ärz -hä6s5chen -2h3äst -2häug -4hä3us. -häu4s3c -4häusg -4häusk -4hä3usp -2hb2 -hb4ar -hba4ras -hbar5sc -hber4e -2h1c -hchar4 -hcre2 -2hd2 -hdan2 -hdi2e -h3ds -he2ad -hebe5in -he2bl -3hebs -3hebt -he3cha -he5ch4e -h3echs -heck5sp -hed2g -he4dit -hee4la -he3e2m -hee2n -he3eng -hee4rei -hee2t -he3eti -he4fan -he4fau -he4f5erm -2heff -he4f3id -he4find -he4f5in4g -he4f3le -he4flo -hef3s2 -3heft -hef4tan -hef4tau -heft3s -he2fu -he4hef -h3ehr -hei2a -hei4da -hei6d5ert -h3eie -h3eif -h3eig -hei4kl -hei4lau -he4im -hei4mal -hei4man -hei4mar -hei4mau -hei4mei -heimer6f -hei4min -hei4m3o -heim3p -hei4mu -4hein. -hei4n3a -hein6dik -hei4n5e4b -hein5e4c -hei4neh -hei6nene -heinen6g -hei4n5er -h5eingab -h5einkä -h5einmi -hein5sk -4heio -4he3ism -heis4s5t -he3ist -h5eitei -he3i4ti -heit4s3 -h3eiw -hei4za -hei4zel -hei6zene -hei4zw -he5kar -hek2s3 -hek4t3a -he4l3an -hel5ass -he4l3au -hel3e4c -4helek -h3elem -hel5end -hel5ent -hel5ers -he4less -4heleu -4helic -he4l5ing -hel4l5au -hel3le -hel4m5an -hel4mei -3helms -he4l3oe -he4lop -he3lor -he2lö -helsau4 -2helt -helt4s -2hemb -hem4br -3hemd -he4mer -h5e4miss -2h3emp -he2na -hena4g -hen3au -he2nä -hend4r -hen5e4be -hen3ec -he4nel -he4n5end -he4n5en5e -hen5ens -hen5ent -he4nenz -he4nep -hen5erei -he4nerf -he4n5erk -he4n5ero -he4n5erö -hen5ersc -hen5erst -he4n5ert -he4ness -he4n5eta -he4n5e4ti -he4neu -hen3f2 -h3eng. -hen4gag -h5enger. -heng4le -heng4r -he4nil -he4n3im -he4n3in -henke6l5i -henk6lei -henk4lo -henk4r -hen6k5rin -henmen6s -henmi4s -hen3ne -hensch4 -hen6semb -hen5sla -h3entc -h5ente. -h3entf -4h3entg -h3entk -h3ents -4hentw -he4n3u -hen3z2 -he2o -he3on -heori4e -he3pf -2hepi -he3ra. -her3a4b -her3an -he5rane -4herap -he4r3än -her4big -herb4st -her3c -3herd. -3herds -he4r3e4b -he4r5eck -her3ef -4hereig -h5ereign -he5reigr -her6eini -he6r5eis. -her5eises -he4rel -he4r5en5e -he4r5ent -he3rer -he4rerf -he4r5erh -he6r5ersc -he4r5erw -her4fas -4herfo -h5er6folg -herg2 -6hergebn -h5erheb -h5erhit -h5erhoe -h3erhö -he4r3id -heri4e3 -4herif -he4r3il -herin4d -herin4f -he6r5in6nu -herin6st -herk2 -4herke -h5erlaub -her6lebn -her6lös. -her6löse -h3ernä -h5erneu -her5no -her3o4b -he3rod -her3of -he4r3oh -he3rok -heros3 -h5er6regu -hers4k -hert2 -her3th -her3tr -heru4t -he4r5ute -4herzae -her6zaep -her4zap -4h3erzä -her4zäp -her6z5eng -herzes4 -4herzeu -h5erzeugu -heseen4 -he3s2h -hesi4e -he3s2k -hes4ser -he3sta -he5s6temp -he5stro -he3stu -h3e4tap -heter4z -he3t2s -het3t -3hetz -he3u4fe -heu3g -he3ums -he3unf -heu3r4a -3heusc -he3xan -hex3em -2hexp -1hè -2h1f2 -hfel4la -hfen2 -hfi4le. -hfolgegene6 -2hg2 -hgeb2 -hgene6ri -hgin4s -h3glo -2h1h -hh4au -hhaustuer6 -hhaustür6 -hhoh2 -hhohl3 -2hi. -hialer4 -hi5a6lerw -hi3a2m -hi2ar -hi3a2s -h3iat -hi3ce -hich6t5er -hi4dea -hi4dio -2hido -hi3dr -hi2e -hied4s3 -3hiera -hie4rei -hier3i -hie4rin -hiesser6 -hiff4r -h3i4gel -hi2k3r -hi5la. -hi4l3a4d -hi4l3an -hil3c -hil4dau -hil4dr -hil4f3a -hil4fä -hilfe5s -hil4fl -hil4fr -hili4e -hi4lip -2hill -2hima -2hime -h3i4mit -h3imma -2himp -h3impe -h3impl -h3impo -hi3nak -hi3nal -hi3nam -hi3nap -hi3nas -hinau4 -hin4dac -hin4en3 -hing4a -hing4l -hing4r -2hi3n4i -hin3n2 -2hino -hins2 -hin4t5an -3hinw -2hi2o -hi3ob -hi3os -hip1 -hi2pa -hi4pei -hi4pen -hi4phe -hi2p3l -hi2po2 -hi2p3r -hips2 -hipt4 -hi2pu -2hire -hire3s -hir4m3a -hir4m3i -hir4ner -hirs2 -hir6scha -2his. -hi4schl -hi4schr -hi2se -h3i2so -his2p -hi3spo -hi3spr -his4st -hi5s4tie -hit2a -hi3tac -hi4t3an -hi4tel -hi4tro -hit3z4e -hi4van -hivar4t -hi2v3o -hi2vr -hi2vu -2h1k2 -hke3na -h3ku -hk4uh -2hl -h3labo -h5lache -hlach6t5r -h5laeche -h5laed -hlaf3l -hla4gä -hla4gei -hla4g3r -h3lake -hl3akt -hlammer6 -hla2n -hlan4d5a -hlan4dr -hl3ano -hl3ans -hl3arb -hl3art -h3las -hl3asp -h3late -hl3ato -h5laus. -h3laut -hl3ax -h3l4ay -h5läche -hl3är -h3läs -hlb2 -hlbe3n -hl1c -hl3d2 -hld4a -hle3e2 -h4legel -h4leic -h4leie -hlei4fa -h4leime -hl5einb -hl5eing -hle5in6ha -hl5eink -h4l5einl -h4l5einr -hl5eise -hl3ele -hl3emp -h5len. -hl5en6dun -hlenen5e -hlen5sk -hlens4t -h4l5entd -h4l5ents -h4lentw -hl4enz -h2lep -hler3a -hler5eig -h6l5er6nae -h4l5er4nä -hler6ze. -h4lesi -h4l3ess -hles4t -hle5sta -hle5sto -hle5str -h2leu -h3leuc -hlf2 -hlg2 -hlge3n -h2lie -h3lieb -h3lied -h3lieg -h2lif -h2lim -hl3imp -h4l3inf -hling4s5 -h5lings. -h4lingt -h4l5inst -hl3int -h4l3inv -h2lip -h4liss -h2lit -h3lite -hlk2 -hlo2b -hl3obe -h4loess -h2lo2f -h2loh -h2l3o2p -h4lor -hlor3a -hlo4re -hlos4s5t -h2lot -hl4samb -hl4sang -hl4sau -hlsch4 -hls2e -hl4s3el -hl4ser -hls5int -hl4sph -hl5s4tec -hl5s6tern -hls5tie -hl4sun -h3luk -h5lumpe -h3lurc -hl3urn -hlus6s5en -hluss5t -h3lüf -hlz2 -2hm -hm3abl -h4maec -h4maeh -h4mael -h4malw -h4malz -h4mand. -hmani4e -h4marc -h4maro -h4mause -h2mäc -h2mäh -h2mäl -h3me. -hmee2 -hme3eb -hmeer4s -h3mein4 -h5meise -hme5l4a -hme3le -h3men -hme5nad -hmen4sa -hmen6schl -hmen4se -hmens5em -hmen6spo -hme4rad -hme4ran -hmer5ei -hme4r5id -hmerin6t -h3mes -h3mex -h2mid -h2mie -h4milz -h4mink -h4miss -h2m3o2f -h2mol -h4morb -h4more -h4mors -h4mort -hm1p2 -hm2s3p -hm2st -hm3s4tr -hm2th -h2mud -hmuss4 -h2mut -hmut4s -2hn -hnabe4 -hna2c -hnacht6s5 -hn3amb -hn3a2n -h3nark -h3nas -hn3a4te -h3n4au. -hn3äh -h2näp -hn3är -hn3d2 -hn2e -h2nee -h4n3eff -hnei7der -hn3eig -hn3ein -h2nel -hne2n -hnen6sem -hn3epo -h5ner. -hne4r3a -h4n5er4de -h4n5er4du -hner5ei -hnergene6 -h5nerlö -h4n3ero -h4n3erö -hnet4s -h4netu -hn3ex -hn3f2 -hnflei4 -hng2 -h2nib -h2n3id -h2nie -hnik3a -hni4kan -hni4kar -hni4kl -hni4kn -h2n3im -hn3ing -hn3inn -h2nip -hni4sa -h2nit -hnit4tr -hnk2 -hn3o2b -h2nod -h2noe -h2no2r -hno3s -hns2p -hnst4 -hnsuch4 -hn4t5auf -hn4tho -h1nu -h2nuc -h2nue -h2nul -hn3ums -h4n3unf -h4nute -hnz2 -ho2ar -hobel5e -h5oberf -ho2b3l -hoch3 -ho4cha -hochen5e -hochs4 -ho6ck5ere -ho6ckerl -hoe2d -ho3edu -2hoee -hoe2s -hoes3c -h3oese -h3oest -ho2ex -ho2f3a2 -ho2fä -ho4f3ed -ho4f3ei -ho4fet -ho4feu -hof3f4a -hoffen6s6 -hof5fi -hof3fl -hof3fr -ho4f3in -ho2f3l -ho4for -ho2f3r -hogene6r -hoh4lei -h3ohm -ho3hö -h3ohr -2hoi -hok2l -hoko3 -hokos4 -h3okt -ho4lab -ho4l3ak -ho4l3an -ho4l3au -ho4lav -ho4l3ei -ho4lem -ho4l5ent -hol5gr -hol5int -hol2o -ho3lof -3holst -2holy -h3o4lym -3holz -hol6zene -hom4e -ho4me. -ho4mec -ho4med -ho4mel -ho4meo -2hon. -ho4nar -ho4nau -2hone -2hong -2honh -honi4e -2honk -2hons -ho3on -hoo2r -2ho2pe -ho4p3r -4hora -ho3rae -ho5ral. -ho5rala -ho5ralg -hor5alt -2hore -ho4rem -ho4rer -2horg -ho4rol -ho4rop -hor4ta -hor5tan -hor4ter -hor4t5rä -hor3um -hose4 -ho4s3ei -2hosm -ho2sp -ho3spr -hos4sa -host3r -h3osz -hoße2 -ho4ß5ene -2hot. -3hotl -hotli4 -hoto5p -2hot3s2 -hou1 -hou2s -2how -h1ox -2hö. -hö2c -hö3ck -h1ö2d -2höe -2höff -1höh -hö2s1 -h3öse -h3öst -2h1p2 -h3pu -2hr -hr3a2c -h3rad. -hr3adr -h5raeum -h3rago -hr4ai -hr3ala -h3ramp -h3r4ane -hran4kl -hra4no -hr3ans -hr3atl -hrau4ba -hraube4 -h3rauc -hr3auf -h3raum -hra2x -hr3ämt -h3räu -hrb2 -hr1c -h3re. -h3rea -hre4bl -h3rech -hre6ck5er6 -h3red -hr3eff -h3reg -h3reha -hr3ehr -h4rei. -hrei4ba -hrei4bl -hrei4b5r -h3reic -h4reie -h3reif -h5reihe -hr5eing -h4r5einl -h4reins -hrei5th -h3rel -hr3emb -hr3emp -hrende6s -hrenk4 -hren4zy -h3rep -h5rere4 -h4rerfa -hrer6fol -hr6erfor -hrer6gebn -hr5er4ke -h6r5er6leb -hrer5oe -hrer3ö -hrer6zae -hrer4zä -h3re2s3 -hresgene6 -hres5k -hress4 -hre2t -h4r3eta -h2reu -hr3eul -h2rev -hr3eva -h3rez -hrg2 -hrge3r -hrgu2 -hrgut3 -h3ric -h4rick -hri2e -h5riesl -hr3imk -h3ring -hr3inh -hr3ins -hr3int -h3risc -h3risi -hr3iso -hrit6tel -h5ritter -h3riu -h3robo -h3roch -h3roeh -h3roe4s3 -hro2f -hr3ofe -h3rog -h3roh -hro2l -hr3oli -hr3oly -h4romat -h4ron -hro4n5a -h3ropa -hr3o2r -hr3osm -h3rote -h3roto -hrot4t5a -h3rou -h3rö2s3 -hrp2 -hrr2 -hr4s3ac -hr4s5amp -hr4s5and -hr4s5ang -hr4s3au -hrs5eins -hr4sele -hr4sent -hrs5erf -hrs5erl -hrs5ers -hrse4t -hr4s5eta -hr4s3in -hr2sk -hr4s3of -hr4stri -hr4sum -hr4s3un -hr2sz -hrt5aen -hr4tak -hr4tall -hr4t3an -hr4t3än -hr4t3e4l -hr6t5en6de -hrten6gag -hrtengene6 -hr4t5erf -hr4t5erl -hr4t5erz -hr4t3ho -hrt5ing -hrt5ins -hrto2 -hr4top -hr4t5ram -hrt5rol -hrt6s5ein -hrt4sin -hrt4sto -hrt4stu -hr4tung -h1ru -h2rub -h4rueb -h3ruf -h2rul -h3rum. -hr3ums -hr3unf -h1rü -h2ry -hrz2 -hrzah4 -hrzahl5 -hrzeu6ger -2hs -hsa2b -h4s5achs -h4s5acht -h2s3a2d -h4s3aeb -h4s3aeh -h4s3aem -h2sak -h4s5alar -h4s3alk -h4s3all -h4s5alte -h4s3amm -h4sanb -h4sanf -hs3ang -h4sange -h4sanh -h4sanm -h4s5anna -h4sanr -h4s3ant -h4s3anz -h2sa2r -h2s3as -h4sath -h4s3ato -h4s3aud -hsau6erl -h4s3auf -h4saut -h2s3äb -h2säh -h4säug -hs2ca -hsch2 -h4schan -hsco2 -hs2cr -h2s3e2b -h2s3e2c -hse2e -hsee3i -h4s3ehr -h4s3eie -h4s5eind -h4s5einh -h4s5einl -h4seinn -h6s5einst -hsela4 -h4selad -hse4l5ag -hse4lan -hse4l5ei4 -hse4l5en -hse4ler -hse4l5in -hs5e4lit -hsels4 -hs5e4mis -h4s5endw -hsene4 -hse4n5eb -hsen5erg -h6sensem -h6s5entar -h4s3epi -h4s3erb -h4s3erf -h4s3erh -h4s5erkl -h4s3erl -h4s3erm -h4s5erna -h4s5ernä -h4s3er3o -h4s3erö -h4s5erpr -h4s3ers -hse3r4u -h4s5erwe -h4s5erwi -h4s3erz -hse2t -h4s3eth -hsex2 -h4sexe -hs4ext -hs4ha4k -hs4her -h2s3i2d -h4s3ind -h4s3int -h4s3ita -h5skala -h5skand -hs4nac -h2so2b -h4s3ofe -h4s3off -h2s3op -hs5ort. -hs6orti -hs5orts -h1sö -h1sp -hs4pach -h4sparl -h4sparz -h4spat -hs4pec -h4spel -hs4perb -h4speri -h2sph -h2spl -h4spro -hs3s2 -h4st. -h3stab -hs5tabl -h4stabs -h3stac -h3stad -hs5taeu -h3stah4 -hs5talb -hst5alt -hs5tank -h5stans -hst5anza -h3stap -h5stark -h5start -hs5tata -h5stati -h5statu -h5stau. -h4s3täu -h2ste -h3stec -h5stein -hst5einsa -h3stel -h4stele -hs3ten -hs4t5en4g -h3s4tep -hs3ter -h5s4terb -h5stere -h5s4tern -hs3tes -h5steth -hstge3 -h3stic -hs4tieg -h3stil -h3stim -hs4tin -h3s2to -h4stole -hsto4n -hs2tr -h5straen -h5straf -h5strah -h5stras -h5strat -h5strec -hs5treu. -hs5treue -h3stro -hstro6ma -h3strö -hs4tub -h3s4tue -h3stun -h2s1u2 -h3sub -h3sul -hs4ung -h3sup -hs3ut -h3s4wim -2ht -ht1a -h2ta2d -h4taer -ht5a6gent -h4taka -ht4akt. -ht4akte -h4tall -h4ta4lo -h4talp -h4talt -h2ta2m -hta2n -h4tan. -h4tanb -h4tan3e -h4tanh -h4tanna -h4tans -h4tanw -h4tapo -h2tar -hta4rie -ht5a4ris -h4tasi -h4tasy -h4tath -h4tato -h2tau -h3taub -h3taum -h2tax -ht1ä -h2tär -hte2c -hte5cha -ht5echs -ht5echt -ht3eck -h4t3ede -hte3e -ht3eff -ht3e4he -h4t3eif -h4t5eilz -h4t3eim -ht3ein -hte5in6fo -h4t3eis -h4t3e4ke -h4t5e4las -hte6l5ei. -h4t5elek -h4telem -h4t5elfe -h4t5e4lit -h4t5e4mis -h4t5e4mit -h5ten. -h4t5endo -hte6nere -hten6gag -htens4p -ht5entar -h4t5entd -ht5ente. -h4t5entf -h4t5entg -h4tentl -h4t5ents -h4tentw -hte4r3a -ht5erbe. -h6t5erben -hterer6sc -h6t5erfah -h6t5erfol -h6t5erfuel -hter6fuellt -h6t5erfül -hter6füllt -h6tergeb -hter6gri -hter6hael -h6t5er6häl -ht5erheb -hter6hiel -hter6höhu -h6t5erklae -h6t5erklä -h6terleb -h6t5erleu -h6t5erneu -h4t5ernt -h4t5er5ob -h6ter6prob -h6ter6spar -hters4t -h6ter6stat -ht5er6trä -ht5erwae -h4t5erwä -h6t5erzie -h4t3ess -hte5sta -hte5str -h4t3eta -hte4th -ht5etho -h4t3eur -h4t3eva -h4t5extr -h2t1h -h5therm -h3thr -h2t3i2d -hti6g5er6k -h2t3im -hti2n3 -h4t3ina -h4t3ind -h4t3ine -h4t3inf -h4t3ini -h4t3ins -ht3iso -h4t3isr -hti3t2 -h4t3ita -ht3l -htni2 -h4t3obs -hto2d3 -h4t3oef -ht5oele -h4t3oes -ht3o2f -h4t3oly -h4t3ont -h4t3ope -ht5opfer -h4t3opi -h4topt -h4t3ora -h4tord -h4torga -h2t3ot -h2t3ös -ht4ran -ht5rand -h4t3ras -h4t5rati -h4t5ratt -ht3rea -h5treck -ht5reic -ht5reif -h4t5reis -ht5reit -ht3rem -h4t3res -ht2ri -h4t5rieg -h5trik -ht5ring -h5trio -h5t4rit -h4t3riv -h6trocke -h4t3ros -ht3röm -ht3rus -h4t3rut -ht4s3ah -htsa4n -hts5ana -htsch4 -hts5chro -ht4scr -hts5endu -ht4s5eng -ht4sep -htse4r3 -hts5eri -hts5kr -ht5slaw -ht4soe -ht4sof -ht5s4pen -ht5spru -ht4staf -ht6s5tate -ht4stip -hts4tol -ht6streu. -ht6streue -ht6strip -ht6stuer -ht4s5tür -htu3en -ht3uh -h4t5umse -ht3und -h4tunem -h4tunf -h4t5ungl -h4t3urk -h4t3urs -htwa5re -htz2 -hu2b3a -hu4b3ei -hu4b3el -hu4b3en4 -hu4b5er4z -hu2bi -hu2b3l -hu2b3o -hu2b3r -hu2bu -hu1c -hue5ben -huef6ter -huehne4 -hue4r5ei -huer4fr -hu2f3a2 -hu2f3ä -huf5erk -hu2f3o -hu2h3a -hu4h3ei -hu2h3i -huh3l -hu2h3o2 -huh3r -h3uhr. -h3uhre -h3uhu -hu2k3a -hu2kä -hu4k3er -hu4k3in -hu2k3l -huko3s -huk3t2 -hu2l3a -hu2lä -hu4l3e4b -hu4l3ei -hu4lel -hu4lem -hu4l5eng -hu4lent -hu4l3er -huler4f -huler4w -hu4l5ess -hu4let -hu4l3in -hu2l3o -hu2l3ö -huls2 -hul5se -hul5ste -hul5ta -hu4lum -hum4a -hu5man -h3umb -2humf -h3umh -hum4pl -2h3ums -humu2 -2humz -2hun. -h3una -hunde5i -hunde5n -hunde5s4 -hund3r -hun2e -2hunf -h3ungl -hungs3 -hungsa4 -huni3 -2hunt -hup3fl -hu2p3h -hu2ra -hurg4 -h3urku -h3url -hur4ter -hur4t3h -hu4s3en -hu4so -hus4s3a -hus4s5er -hus4s3o -hus4sp -hus4sta -hus4ste -hu4t3ab -hu4tau -hut3ei -hut3ex -hu2th -hut3he -hu2t3i -hu2t3o2 -hu2t3r -hut5sc -hut4ta -hut4zeh -hut4zei -hut4z5en -hut4z5er -hü3bel -hü3ben -h3über -h2übs -hüf4t5er -hühne4 -hür4f3r -2hüri -hüs3 -2hv -2h2w2 -h3wack -hw4ar -h3weck -h3weib -h3weid -h3weih -hwei5n4e -hwein6s5a -hwen4ka -h3wick -h3wies -hwi2s -h3wisc -1hyd -hy2da -hy2de -hy3dr -1hyg -hy4lor -3hymn -h1yo -hy3os -1hyp -hy4pe. -hysteri6e -2hy2t -2hz -hzug2 -hz2w -ia1a2 -ia4ben -iab2l -i3ad. -iad2a -i3adn -ia3do -ia3ei -ia3el -i3ae2m -i3aer. -ia3erf -i3aeri -ia3err -i3aers -i3aerz -i3aet. -i3aeta -i3aetb -i5aetem -i3aetf -i3aetg -i3aeti -i3aetk -i3aetp -i3aetr -i3aet3s -i3aett -i3aetw -i3aetz -i3aev -ia3fl -i3agg -ia1h -iah4o -i1ai -ia3in -i3ak. -i3a4kei -i3akg -i3akh -i3akm -i3a2k3n -ia4kre -i3aks -i3akt -i3akv -i3akw -i1al -ial3a2 -ia4lar -ia2lä -ialb2 -ial5bl -ial3d -i3a4leb -i3alef -ia5lek -i3a4lel -i5alemp -i5alengp -i5a4l5en4s -i5a4l5ent -i5alen4z -i3a4lep -i5a4l5er4b -i5a4l5erf -i5a4l5erh -i5a4l5e4ri -i5a4l5erk -i5a4l5erl -i5a4l5erm -i5a4l5ero -i5alerö -i5a4l5erp -i5a4l5ers -ial5erwa -i5a4l5erz -i3a4l3e4t -i3alex -i2ali -i3a4lia -i3a4l3id -i3a4l3in -ialk2 -i3all -ial4ler -ia4lob -ia4l3on -ia4lop -ia4l3o4r -ialp2 -ialsch4 -ia2l3u2 -ialz2 -i3amp -iampe4 -ia3mu -i3an. -i3anab -ia4n3ae -i3a4nal -ian5alt -ia4nar -i3a4nau -i3a2nä -i3anc -ian4ce -i3and2 -i2ane -i3a4n3e4b -i3anei -i3anex -i3ank2 -i3ann -ia4nod -i3ans -ian4s3p -ian4st -i3ant -i3anz -ian4z3a4 -ia1o -ia2op -i1ar -i3ar. -ia4rab -iar5ein -i2ari -iarr2 -i3as. -i3asb -ia3sh -i2asi -ias2p -ias3pr -i3as3s -ia3sta -ia3s4tr -i3asz -i3at. -i2ate -i3atl -ia4tol -i3atom -i3ats -i1au -ia3un -i1ä -iä2m -iä2r3i -i3ä4tem -iä2ti -iä2tr -iät3s -i4b5achs -i4b3arm -i4b3art -i4barz -i4b3auf -i4b3aus -i4baut -i3bä -ib3ärm -ib4bli -ibeer4 -i4b3eig -i4b5einh -i4b3eis -ibel3i -ibend4 -i4b5ener -ibe3ra -ibe4rab -ibe4rae -ibere4 -ibe4reb -ibe4ren -i4ber4fa -ib5erfah -i6ber6geb -iber6lau -i6b5er6sche -ibe4tau -ib3ide -i4b3ins -i4b3lab -i4blad -i4bläh -ib4len -i4bleu -i4bluf -ib2o -i4boel -i2b3op -i4b3ort -i2böl -ib4rec -ib2ri -ib5roec -i4b3röc -ib4ser -ib3s4kl -i4bueb -i4b3unk -i4bunt -ibus3c -i2büb -2ic -ica2l3 -ic1c -ice1 -icein4 -i4ch. -icha2 -ich3a4c -ich3ag -ich3al -ich3an -ich3ar -ich3au -ich3ä -i3che -i4ch3e4b -ich3ei -i6chensem -i6ch5erde -icher5eig -icher6gebn -i4ch3eu -i3chi -i4chins -ich3la -ich3lä -ich3li -i5chlor -ich5lot -ich3m -ich5ma -i4ch3n -ich3oe -i3chon -ich5ort -ich3ö -i2chr -ich3ra -ich3re -ich6s5amt -ich6s5ern -ich4s5or -ich4spo -ich5stri -ich4tab -ich4tan -ich4tar -ich4tas -ich4teb -ich6t5eig -ich7ten. -ich4t5in -ich4toe -ich4t3r -i3chu2 -ich3uf -i4chur -ich3w -i1ci -i4ck5erf -i2cko -ic3k4os4 -ick3sa -ick3sk -ick3s4p -ick5sta -ick5sti -icktu3 -ic3la -ic3le -id2ac -id3aer -ida2n -id3ana -i4d3ani -i4danl -i4dante -i4danw -id3arm -id3art -id2ax -i2daz -idä1 -i2d3är -i2dee -i3deei -ide5inst -i4demul -ide5nad -idener4 -ide4n3i -ide4n3o4 -iden4zi -i4denzy -ide2o -ide5rae -ide5ran -ide4rel -ider6fül -iderin6f -iderin6nu -ide3ro -ide3rö -ide4ser -ides4p -ide5sta -ide5str -2idf -id4gea -idi3ae -idi2o -id5ion. -id5ione -i4diot -i2dol -3idol. -i3dor -id4o3s -i2d1r -id4ro -id4san -id4set -ids3pr -ids5tra -idue4 -idu3en -idwa3r -1idy -ie3a2 -ie4bab -ieb5ach -ie2bl -ie3bla -ieb4sen -ieb4s3i -ieb4so -ieb4ste -ieb4sti -ieb6stre -ie4bum -iech3o -iech3t -ie2ck -ieck4s -ie4d3an -ieden6sc -iede4r5a -iedi4e -ie2dr -ied4si -ied4spa -ie1e2 -ie3ev -i2ef -ie4f3an -ie4fau -ie4feb -iefe4m -ief5emi -ieff2 -ief5fl -ie2f3l -ie3flu -ie4fonk -ief5rea -ie4f3ro -ie4g3li -ieg2n -ie4gre -ieg4s3c -ieg4se -ieg4si -ieg4sp -ieg4s3t -ie4hap -ieh5eis -ie4h3in -ieh3la -ieh3r -i1ei -ie3im -ie3in5d -iela4r -ie4lau -ie4läs -ield2 -iel5ech -iel5e4ck -ie4l5eic -iel5eid -ie4l5eig -ie6l5eint -iel5eis. -iel5eise -ie4leng -ielen4s -ielen4z -ieler6fin -ieler6fol -ieler6ga -ieler6gä -ieler6geb -ie6l5erkl -ie6l5er6la -ieler6lebn -ieler4ö -ie6l5erru -ieler6wei -ieler6zae -ieler6zä -ieles4 -ie4less -ie4l5eta -ielgen6a -ielgene6 -iel3i4d -ie4l5inf -ie4l5ins -iel5int -ielo4b -ie4l3op -ie4lor -iel3sk -iels4p -iels4t -iel5ste -iel3sz -i5e4meti -i1en -iena4b -ie4na4g -i4enah -iende5s4 -ien3ec -ien5ekl -ie4nel -ien6erfu -iener6la -iene5sk -ien3f2 -ieng2 -ienge4z -ie4nid -ie4n3im -ie4n3in -ienk2 -i3enn -ieno2 -ien5sa -iensch4 -i5en5se -iens6er -ien3s4k -iens4p -ienst5aen -ienst5än -ienst5ein -iensten6 -iens6t5end -iens6ter -ienst5erl -ienst5erw -ienst5op -ienst5or -ienst5rän -ien6t5ero -i5enti -ien3tr -ie1o2 -ie4peg -ie3p2f -ie3ra. -ier3a4b -ier5aet -ie5raeu -ier3af -ier3al -ier3an -ier3as -i2ere -ie4r5eck -ier5eier -ier6eini -ier5eis. -ier5e4mi -ie5r4ert -i2eri -ie4r3id -ie4r3il -ie4r5ing -ierin5i -ierk4 -ier3l -iermi4s -i3ern. -iern4a -ier3ne -ier3ni -ier2o -ier3ob -ier3oe -ier3of -ie3roh -iero3n -ier3ö -ier4re. -iersau4 -ier4s5eh -ier4sko -ier5sta -ierts4 -ieru2 -ie4rund -ie4r3ut -ie4s3au -i3esd -ies5ein -iesend4 -iesen5s4 -ie3s4ka -ieso2 -ie4s3oe -ie2sö -ies2p -ies4sel -ies6s5ent -ies6s5er6l -ies4sti -iest4a -ie5stel -ie3str -ie3stu -i3esü -ie4ßel -ie4ß5ent -ie4t3ak -ie4t5ang -ie4tans -ie4t5anz -ie4t3ap -ie4tat -iet5ein -ie4t5ent -ie6t5er6ho -ieter6hö -ieterin6t -ie6t5erlo -ie4t5er4t -iet5hel -iet5her -ie4t3ho -ie4thu -i3e4thy -ie4t3ob -ietoe4s -ie4t3ö4s -iet5ram -ie4tren -ie4t3ri -iet5rud -i3ett -iet3z4a -iet3zw -ieu2e -ie3uh -i4e3um -ie3un -ieu4r3a -ieu3s -ie2w3r -i1e2x1 -iezo3 -2if -if3aen -i2f3af -if5ange -i3fari -i4f3arm -i2f3au -if3än -i2fec -if3eff -i4f5einh -i4felem -ifel4s5o -ife2n -ifen3e -ifen6te. -if3erh -ifes5tat -if4far -if2fl -iff3la -if3flu -if4f3ru -if4f3rü -iff2s -iffs5et -iff4sti -iff4stu -if3inf -i4f3ins -if3lac -if3lad -i3flae -i3flag -if3lau -i3flä -i3flec -i3f4lie -i3flue -i3flug -i3flü -if1r -i3f4rag -i3freu -i3f4rev -if3sa -if2t3a -if4t3ed -if4t3ef -if4t3ei -if4tent -if4t3ep -ift5erl -ift5erz -ifte4s -if4t5esc -if4t3ex -ift5int -if4t3op -if4t3ra -ift5rei -if4t3ri -if4t3ro -ifts4c -ifts6tem -ifts4tr -if4tum -if4tur -i2f3um -i1fy -i4ganb -i4g3ano -ig3anz -i4g3arb -ig3auf -i4g3aus -igd2 -igeb2 -i6gebrau -ig3e4ck -ige4dre -i4gefar -ige4füg -ige4hef -i4g3eig -ige5inf -ige5inh -i4g5eise -i4gel. -3i4geln -ige4mes -ige4mis -igena4b -ige4nag -ige4n5e4b -ige6nene -igener6f -ige6ners -igener6z -ige4nid -ige4pac -ige4ral -ige4rar -ige4rau -i4g5er4fa -igerin6se -iger6klä -ige3sk -i4g3ess -ige4woh -igg2 -iggas5t -igh3s -2igi -i2g3im -ig3ins -ig5laen -i4g3lau -i4g3län -ig3lec -i4gled -i6gleitu -i2gli -ig3lim -ig3lu -ig2na -i4gnae -ign3a4g -i4gnak -i4gnan -i2gnä -ign3ei -i6gnerge -i3g4neu -ig3nu -igo3p -i4grams -i4greg -i4grei -i4g3res -i4g3rin -ig3rom -igs4ae -igs4ag -ig3sar -igsau4 -ig4scha -ig4set -igs2i -ig3s4or -ig3s4pe -ig3spr -ig5stei -igs4top -ig4stö -ig4s5tür -ig2sy -ig4tin -igtu5er. -i1h -ih2a -i4h3ans -i4h3auf -ihe3e -ihe3in4 -ih3elt -ihe2n -ihenk4 -ihens4 -ihe3o -ihe3s -ihe3u -i2h3in -ih1l -ih3m -ih1n -i2h3op -ih1r -ihrau4 -ihs2 -ih3sta -ih3ste -ih3sti -ih3str -i4h3um. -ih3w -i1ia -i3i2c -i1i2d -i1ie -i1i2g -i1im -i1in -i1i2r -i1i2s -i3it. -i3ja -1i2js -i1k -ik5abla -ik5ablä -i4kada -i3kae -i4k3aeh -i4k3aer -ika4gen -ika3in -i4k3akt -ikalk4 -ikal5t -ik5alta -i4k3ang -i4kanl -i6kantei -ikanten6n -ik5anzu -ik5anzü -ika3pf -ik3arm -ika4tan -ik3att -i2k3au -i2käh -i2k3är -ikbu2 -4ike. -i2keb -ik3ebe -ike2c -i2k3e2d -i2kef -ik3eim -ikei4n3 -ike2l -4ike2n -iken3e -i4k3en4z -i2kep -i6k5er6be. -ike4ren -i6k5er6fah -iker6folgs -iker6hal -iker6ken -i6ker6leb -i4k3ero -i4keru -ik5erze -iker4zi -ike4t -i4k3eta -ik3eth -i4ketu -i2k3e2v -i3ki. -ik3imm -ik3inh -i4k3ins -ik3lab -ik3lad -i4k3lag -iklan4 -ik3lem -ik4leri -i3kles -i4klos -ik3nar -ik2o -ik3obs -i4k3oef -iko3nu -ikop2 -ik3o4ri -ik3öf -i2k3öl -i4kres -ik3rin -ik2ro -ikro3s -i2krö -ik3ru -ik1s -iks2p -iks2t -iks2z -ik4t3eb -ikt5erk -ikt5erz -iktes4 -ik4t5esk -ik4t3o4b -ik4t5res -i4kueb -i4kumf -ik3unf -i5kuns -i2kup -ik3urh -i3kus -iku2t -ik3ute -i2küb -i2ky -i1la -il3abb -il3abl -il3a4br -i4l3abt -i2l3ac -i4l3aer -ila3f -i2l3ak -il3a4ma -il3ana -i4lanb -ilan5er -i4lanf -i4lanh -i4l3anm -i4l3ano -il3ans -ilan6z5er -i4l3arb -il3art -i4l3asp -il5aufl -ilauf6ru -il5auft -il5aufw -il5aufz -il3aus -i1lä1 -i2l3är -ilch5au -ilch5ec -ilch3o -ilch3r -il4dae -ild5au. -il2dä -ild5ebe -il4d3ed -ild5ele -il4d5ene -ilden4s -il6dense -il4d5en4t -ild5erhal -il4d5ese -il4d5es4s -il4did -il4dil -il4dop -il4dor -il4drü -il5ebene -il3e2c -i2led -ile3gl -i4l3eig -il3ein -il5eise -il3ele -i4l3emb -i4l3e4mi -il3emp -i4l5entb -i4l5ente -i4l5entf -i4l5entl -i4l5ents -i4lentw -iler6be. -iler6ben -i6l5erbli -i4l5er4de -iler4ei4 -i6l5ereig -i4l5er4fo -i4l5er4fu -i4l5er4fü -i6lergeb -i4l5erhe -i4l5erkl -iler6las -i4l5er4lo -i4l5er4lö -iler6öff -iler6stat -i6l5er6wae -i4l5er4wä -iler6weit -iles2 -i4lety -i4l3eur -i4leva -il3ex -ilfe3i -ilf2s3 -ilf4sta -ili3as -i2l3i2d -ilie4n3 -ili4g5a4b -il3imp -i4l5init -i4l3inv -ili3o -il3ip. -ili3pf -il3ipp -il3ips -ilk2 -ill4ant -il4l5ein -il5lem -il3li -ills4p -3illum -il4mad -ilm5a4ge -il4mak -il4mam -ilm5ans -il4m3ap -il4meh -ilm5ein -il4m5ent -il4m5enz -il4m5erk -il4m5erl -il4mex -il4mik -il4m3in -il4mop -il4m3or -il4mou -ilms2 -iln2 -il3obe -ilob4r -il4oh -i2l3o2r -il3ox -il4san -ilsch4l -il4s5ent -il5stec -il5stemp -il5stern -il3sti -ils4to -ilsto4r -i1lu2 -i2lue -i2lum -i2l3ur -ilv2 -il4z3an -il4z3ar -il4zau -ilz5erk -ilzes4 -il4z5ess -il4zwa -im3abl -im3abr -im2ad2 -ima4get -ima3i -ima3kl -imaler4 -iman3d -i4manh -i4manw -im3apf -i4marc -im5a4ren -i4m3arm -i4m3art -ima3sz -i3mat -ima4t5in -ima4tra -ima4tur -i4maut -i2mäp -im3b -i2meg -im5eins -im5ein4w -i2mej -i2mek -i4m3ele -i4m3elf -i2m3e2p -imer4la -i4m3er4z -i4me3sh -imess4 -i4meti -i2mew -i2m3ex -imhau4 -i2m3im -i4m3inf -i4m3ins -im4mak -im4mant -im4m3au -im4mei -imme4n -im6m5enth -imm5erkr -im4m3id -im4mop -im4mor -5immuni -i2m3o2p -imo4re. -4impa -im5paa -imp4fa -imp4f5er4 -imp4f3i -impf5li -impf5lu -im4ple. -im4p5ler -imp2s -im3pse -3impu -ims2p -im2th -imt3s2 -2imu -imult4 -im2um -im3urk -i1mü -4ina. -i3nabo -ina4br -in3abu -ina2c -in5ach. -in3ack -in3aeh -in3ae4s -in3aeu -in4a3fr -ina5ge -inahe3 -4inal -i3nald -i3nale -inalen4 -inaler4 -ina6lere -ina6l5ins -in3amb -in3ana -i4nang -in3ann -ina4no -i4nanzü -4inap -in3ape -in3app -in4a4ra -i3n4are -in4arr -in4ars -in3as4b -inat3h -in3atm -in5atom -i4nau -in3aue -inauf3 -i3naz -i2n3äh -in3ä2s -inbun4 -4ind. -in4da4b -inda4c -in4d3al -in4d3an -4inde. -indei4g -ind5eis -4inden -4inder -inderei6s -inderer6t -in6der6sat -inde5spa -inde5sto -5index. -5indexe -3india -5indika -3indiv -ind2o -in4drec -in5drue -in5drü -ind4spe -ind4ue -3induk -3indus -ind2ü -2ine. -in5ebene -ine3ch -in5eck. -in5e6cker -ine3el -i4n3eff -in5e4hen -i3neim -i4n3elb -ine5nae -ine3nä -inen6gag -inen6ge. -inen3s4 -ineo2 -ine3p2 -i5ner. -i4n5erbi -i6n5er6hal -i4n5er4he -i4n5erke -iner6läu -i6ner6leb -i4n5er4lo -iner4lö -i4n5ersa -i4ner4tr -i4n5erz. -iner6ze. -iner6zen -iner6zes -iner6zeu -i4n5erzi -inesi4e -i4ne3s4k -ine5str -2in2et -ine3u4f -ine3un -ine2x -in3f2 -3infek -3infiz -3info. -4infre -in4g3af -in4g3a4g -in4gak -in4g3am -ing5ans -5ingeni -in5g4ler -in4g3n -in4gop -ing3or -in4g5rat -4ings -ing5sat -ingsauto6 -ing5s6por -3inhab -6in7haltu -4ini. -4i3nie -ini5erk -i4n3inn -ini3o2 -ini3s4e -ini3s4k -ini3sl -i5nitr -i5nitz -3injur -ink5aet -5inkarn -in4k3ät -inkels6t -ink5erd -in5kom -ink4sin -ink4ste -ink2u -in4kut -ink2ü -2inn. -4inne. -inne4n -inn5erfu -inn5erho -5innerns -inn5erwa -in4n5erz -inne4s5t -2innl -2inns -3innta -inoe4d -ino3et -in3old -i3nomi -in3opf -in3ord -i3n4ot -ino5ta -ino5te -i1nö -in3ö2d -ins5aen -in6samt. -in4s3än -insch4 -ins5ebe -in4s5eke -in4s5emb -in4s5erf -ins5erg -ins5erm -ins5ertr -3insid -in3sky -ins6port -instab4 -ins5tak -5instal -ins4tec -in6steil -in6st5einst -5instin -ins4tip -5instit -inst5rol -3insuf -3insul -in4s5umw -in4sur -4insy -int5an. -int5ans -4inte. -3integ -5interp -inter5z -int2h -inth3a -intons4 -4intö -5intrig -int3s -in1u -inue4b -i4nuh -i3nuk -3inumg -in4umt -in3unz -3inurs -inü4b -in5ver -3inves -4invo -i1ny2 -inz4el -inzel5a4 -inzel5e -inze4li -inzel6ler -inz5int -in3z2u -inzup4 -inz2w -io3a -io1c -i1od -i2oda -io2de -iodi4e -i2odu -io3e2d -ioe2f -i3oe2k -io3e2m -ioe2n3 -io3er -i3oes. -i3oest -ioe2t -iof2l -io1h -ioi2 -io3k4r -iok3s -i3ol. -i3olp -i3ols -i3om. -i3oms -i1on2 -ional5a -ion3an -i3o4n3as -i3o4n3au -i2onc -ion3d2 -ionen3 -io4nor -ions3 -ionsa4 -i2ony -io1o2 -i3opf -i3or. -ior3ak -i3oral -i3orb -i3orc -ior4e -io5re4n3 -ior6ens -i3org -io3r2h -i3orm -i3orp -i3orr -i3ors -i3ort -io1s -i3os. -io3sh -i3osm -ios2p -i2ot -i3ot. -iot4e -iote3l -io3t2o -i3ots -i3ox -i3oz. -i1ö2f -i1ö2k -i1öl -i1ön -i3ös. -i3öst -2ip -ipa2n3 -ip3ein -ipe2n -ipendi5e -i2pes -ip3fab -i3pfan -ip2hi -i1pi -ipi3el -ipie4n3 -ip2l -ip4pan -ip4pap -ip2p3f -ip2p3l -ip4pru -i1pr -ip4sei -ip2s3i -ip2sp -ips3tu -ip4s3tü -ip4ter -ipt5ers -ip4tro -i3p2ty -i1ra -i3r2ad -irae5us -ir2ak -3iraki -iral3a -iral5s4t -iral6stue -ir2an -i2rar -irat2 -i1rä -ir3äh -irä3us -irbe4l5a -irbel5i -ir4ble -ir1c -ir4ch3a -ir4ch3i -ir4ch3o -ir4en -i2rew -irgen4 -irg2s3 -irg6ste -i1ri -i4r3in4t -ir2k3l -ir4k3or -irks3c -irk2u -ir4k3ur -ir4m3ae -ir4m3a4g -ir4mak -ir4m3au -ir2m3ä -irme2 -ir4m3ed -ir4m3ei -ir4m3er -ir2m3o2 -ir4mum -ir4munt -irmwa4 -ir2na -irn3al -irn3an -irn3au -irn5erf -irn5erk -irn5ers -ir2no -i1ro -iroe4s3 -i1rö -irö2s3 -irpla4 -ir4rar -irr3au -irre4l -irre4p -3irri -irsch5ar -irsch5au -ir6schei -ir4schl -ir4schm -ir4schn -ir4sch5r -ir4sch5w -ir3s2h -ir4st3r -ir3su -ir4t3er4 -irtra4 -irt4s3e -irt4st -i1ru -ir2ue -irue3b -iru2m -iru2s -irus3e4 -iru5sta -i3r2ü -irü3b -isa2c -i4s3aen -isage4 -is3air -i2sak -i4s3alg -i3sam -i4s3amp -i4s3amt -i4sanb -i4sanf -i4s3ang -i4sanh -i4s3ann -i4sanp -i4sanr -isa2r -i4sare -is4arg -i4saro -is5assi -is3att -i2s3au -is4aue -i2s3än -i2sca -i2sce -i4schab -i4scham -ischa4o -i4schäh -ische4c -i4sch5e4h -isch5einh -isch5eint -ische4m -ische4n -i4schep -ische5re -i6scherkra -i5scheru -isches6s -i4sch5eu -i4sch5im -i6schind -i4schm -i4schna -isch5obs -i4schos -i6sch5rip -isch5ru -i6schueb -i4schum -i4schüb -i4sch5wo -i4sch5wö -isch5wun -i4schwü -is3chy -i2sco -i2scr -2ise -ise3a -i4s5eben -ise3c -ise3e4r -iseer5i -is5einsa -i4selem -isel5er -isel4l -i6s5el6ter -i4s3emb -ise4ne -ise5neb -isen5en -isen3f -isenk4 -ise6r5eic -iserin6t -is3erm -i4s5ernt -ise5r4ue5 -ise5rum -ise3r4ü3 -i4s3ess -ise5str -i4s5e4tat -is4eur -is4exi -isex5po -i2sid -isi4de -i2sim -is3imp -i4s3ind -i4s3inf -isin3g -ising4l -i4s3int -isi3st -is4kis -3islam -4isli -ismu4t -i2sob -is3oef -i2s3of -i2sol -i4somo -iso4nar -isonen4 -iso6n5end -isonet4 -iso4nor -i2sop -is5ort. -3i2sot -4i3sott -i2sou -isour4 -i2s3ov -i1sö -i3spal -is3pan -i2spe -i4s3pic -i4s3pir -is4ple -i3s4por -i3spra -i4spro -i3sprü -is3pü -iss3ac -is4sau -iss5aus -is4sed -issen6sa -issen6sp -issen6s5tu -issen6s5tü -iss5er4f -isseri6e -iss5er6ke -iss5erz -issi4l -is5s4kat -iss3l -isslich6t -iss4par -iss4por -is4stec -is6steil -iss4tu -i4st. -ist3a4c -is3tal -ist3ap -i3stat -is3täu -i2ste -ist5einh -ist5einl -is5t4e4n -isterin6t -ister6ras -iste4s -ist4hal -i3stil -i3stim -istin4f -i3stoc -i3stoe -i4stöp -is3tör -i5s4tram -istri5en -i5strik -ist5rit -istu5er. -istum4 -istungsex5po -is3tüm -isuch3 -isu6cher6 -isu2m -isum3p -i3s4up -i4s3urk -is3url -i4s3urt -i2sü -isvil4 -i2sya -isz4t3 -i4ß3erl -i4ß5erse -ißlich6t -it3ab. -it3aef -i4t3aes -itaet4s5 -ita3h -itai2 -i4t3aka -ita3kl -i6t5akte. -i6t5akten -i4takts -ital3a -italent6 -italer4 -ita6l5erh -ita6lers -ita6l5erw -ita4l5in -ita4lo -ital3t -it3a4mi -itan5at -it3ang -it3a4po -it3app -i4t3arb -it3a4re -it3art -it3asp -i4t3att -i5tauc -i4t3auf -it5aus. -i4taut -itauto6r -i2t3äf -it3änd -i2t3äs -ität2 -itäts3 -2ite -i4teben -it5echs -it3e4ck -ite3er -it3eff -it3eig -i4teige -it3ein -i4t3eis -itel5ae -itel5an -itel3ä -it5elef -itel5eh -itel5ei -i4t5elek -i4telem -i4t5e4lit -i4t3emb -ite4mis -ite2n -iteng4 -iten6gag -itens4 -i6ten6sem -i4tentl -i4t5ents -i4tentw -i6t5ereig -ite4rel -iterer4 -iteres4 -i6ter6fol -itergene6 -iterin6d -iterin6s -iterin6t -iter6klae -i6t5er6klä -i6ter6led -iter3o4 -i5terr -i4t3esk -ites4ka -ite5sta -ite4tik -i4t3eva -i3t4hag -it3hem -itho3s -i3t4hr -i2thu -i2thy -i2tia -i3tic -i2t3id -3i2tii -itik3a4 -iti4kan -iti4kl -iti4kr -it3imm -iti2n3 -i4t3ina -it3ind -it3ini -it3int -itionsex5p -i4tiso -iti4v3a -itiven4 -it3o4be -i3to3c -it3of -it3opu -itor3a -it3orc -i3tra. -it3rab -it5rade -it5raeu -it3raf -it5rand -it5rang -it3ras -it3rau -it5räts -i4t3räu -it3reb -it3rec -it5reic -it5reif -it3ren -it3res -it5rieg -it3ris -i4t3rob -it3roc -i4t3rol -it3rom -it3rö -it3ruc -i4t3run -i5trup -i4t3rut -its5alb -its3an -itsau5d -itsch5ro -it4s3e4h -it4s5eif -it4s5ein -it4s5ela -it4s5ele -it4s3es -it4set -it4staf -it4stec -its4tue -it4stur -it4t3ac -it4tän -it4t3eb -itt5eige -it4t3il -it4tl -it4t3o4b -it4t3op -itt5rad -itt5rän -itt5rei -it4tri -itt5rol -itt4s5e4h -itt6s5ein -itt4sti -itt4str -i3tud -it3uh -i4tunf -i4t3unk -i4tuns -ituran4 -itu4rer -it3urg -i4turp -itut4 -ity1 -it2za -it4z3e4c -itzen5e -itzer6fo -it6z5ergr -itz5er6ho -itzer6hö -it6z5erla -it4zoe -it4z3op -it2zö -it2zw -2i1u2 -i2u3l -ium1 -iuma2 -ium4s5en -iun2 -i3ut -i1ü -2iv -i2v3ab -i2v3ad -i2v3ae -i2v3af -i2v3a2g -i2v3ak -iva4lo -iv3ang -iva6tent -ivater4 -iva6t5erf -iva6term -iva6t5erw -i2v1ä -i2veb -i2vec -i2v3ef -i2vek -ive2l -iv3ele -iv3elt -i4vemi -i4vemu -ive4no -i2vep -iver4ha -iver4he -iver4lo -iver4lö -iver4oe -i4v3er4ö -ive3s -i4vesc -i4vese -i4vesh -i4vest -i4veti -i2veu -i2veü -i2v3e2x3 -i2v3im -i4v3ind -iv3ins -i4v3int -i2vl -i3vol -i2v3op -ivo3re -iv3ro -i2vue -i2vun -i2v3ur -i2vü -i3wo -iwur2 -i2x3a2 -ix3ele -i3xi -ixt2 -ixta2 -ix2u -i1y -2iz -iza2b -i4z3aen -i2z3ag -iz5anke -iz3anz -i2z3ap -iz3art -iz2as -i2zat -i2z3au -i2z3än -izein4t -izeit5s4 -i3ze2n -i4zentz -izen6zer -izen4zw -i4z5erle -i4z5er4sc -i4z3er4z -izi3na -iz3irr -izo2b -i2z3oe -i2zo2f -i2z3or -i2zö -i4z3una -i2zwe -i3zwec -iz3wic -i2zwo -i2zwö -iz3za -í1 -ï1 -1j -j2a -ja1a -jaal2 -jab2 -ja1c -jacht5s4 -ja3ei -ja3er -ja1f -jaf2l -jag4da -jah4r5ei -jahr4s3 -ja1i -ja3l2a -ja3le -ja3ne -jani3t4 -ja1p -ja3ru -ja3st -2jd -je2a -jean4s -jee2p -je2g -jek4ta -jek4tei -jekten4 -jek6tend -jek4ter -jek4t5in -jektor4 -jek6t5ort -jek4t3r -jek4t3u -je3na3e -je2p -jeri3 -je2s3t -je2t3a -je2t3ä -je2t3e -je2t3h -je4t3in -je4tor -je2t3r -jet3s2 -jet3t -je2t3u2 -ji2v -2j1m -joa1 -jo2b1 -joba2 -job3e -joch3a -jo3che -jo3chu -jo3ck4 -jo2d3a -jo3el -jo3gh -jo2i -jona3t -jong4 -jo2p3 -jo3ra -jor2z -jo2sc -jos3t -josta3 -jou2l -2jt -jty1 -j2u -ju2b3l -jude2 -jugen4 -jugend5 -ju2k -jun4g3a -jungens6 -jun6g5ente -jun4gl -jun4g3r -jung5s4c -jung5s4p -jung5s4t -ju3ni -jun3t -jus3t -ju3te3 -2jv -1ka -ka4ans -ka3ar -2k3abb -kab4bl -2kabd -kabel5a -kabel5i -kabel5o -4k3a4ben -2kabf -2kabg -2kabh -2kabn -2kabs -2k3abt -2kabw -2kabz -ka1c -2k3ace -4k3achs -2k3add -k2ade -kade3i -kadein4 -kade4n3 -2k3adm -ka2dr -2kadv -ka3e2d -k3ae4mi4 -4k3aend -ka3ent -k3aepf -4k3aerg -k3aerz -kae4s3c -ka3esk -4k3affi -ka3fl -ka3fr -kaft4 -k2age -4kagen -k3agg -2k3aho -kaim2 -kai3s4 -kai5se -ka3k2l -4k3akt. -4k3akti -4k3akts -4kala. -kal3ac -kal3a4d -kal3an -kala4s -ka3lat -ka4leb -ka4l3eh -kal5eis -kale4n -kalen6du -kalen6se -kal5erei -ka4l5erf -ka4l5er5i -kal5ins -kal4kan -kal4kle -kal4klo -kalk3r -kal4kri -kal4kru -4k3allt -ka4l3on -ka4lop -kalori6e -ka4l3os -4kalp. -k3altä -kal4t5ex -kal4th -kal4tre -kalts4 -ka2lu -ka4mat -kamp6f5en -kamp6f5er6f -kamp6ferp -4k3amte -ka4n3an -ka4n3a4s -ka4nau -4kanda -kandal5a -4kandä -4kandin -4k3andr -ka4nel -ka4n5ent -4kanfä -k4anfu -k4anfü -kani5er. -4kanim -kank2 -4k3a4nom -4k3a4nor -k2anp -kan3s2 -4k3ansä -4k3ansi -6k5antenn -ka3num -kanu3s4 -ka2o1 -ka4p3ho -k3appa -kap3st -4k3arbe -k4arbr -2karc -k2ard -kard4a -4k3area -ka4r5ent -k2arg -k2arh -ka3ri -kari5es -k2ark -2karm -k3arm. -k3arme -k3arms -ka3rö -k2arp -kar4pf -k2ars -k3arsc -k4arta -k3arti -4kartig -4kartik -k4arto -kart3s4 -k2aru -kar3u4f -ka3rum -ka3rü -k2arw -3karz -4karzt -ka4schm -ka5s4e3i -kasi5er. -kas3s4a -kassen5 -k5assis -kasso3 -4k3ast. -kas3ta -kas3te -4k5aster -kas5til -kas6tras -ka3sz -ka4t3a4b -kat5akt -kat5ans -k2ate -ka4tea -ka4tent -ka4tep -ka4t5erk -kat2h -ka3tha -k2ato -ka4t3ri -kat3t4 -k4atz -k2au. -k4aue -kau4eri -kau4f3a -kau4f5er -k5auffa -k5aufga -4k5aufla -k5aufli -kau4f3o -kauf6s5ag -kauf6s5en -kauf4sp -kau4fu -3k4aum -ka3umr -k4aus. -4k3ausf -4k3ausg -k3ausk -4k3auss -kaussen6 -4k3ausw -kau3t2 -4kauto -kauto4r -1kä -3käf -k1äh -2k3ä2mi2 -2k3änd -2k3äpf -2k3ärg -2k3ärz -kä2s3c -2kb2 -k3ba -k3be -kbo2n -kby2 -2k1c -k3ch2 -kcre2 -2kd2 -kdar4m -kdi2e -keb2 -ke4ben -2k1ec -2kedi -ke4dit -ke3er -2k3eff -kefi2 -kege4la -kegel5e -k3egg -ke2gl -ke4he. -ke4hen -kehl3s4 -2keho -kehr3a -keh6rert -keh6r5er6z -kehr4s5o -kehr6s5po -kehr6s5tur -2kehu -2k3eic -2k3eig -kei4l3i -keils4 -keil5ste -kei4ma -keim3p -2k3ein -keinen4 -4k3eise -keit2 -keits5c -ke4l3ab -ke4l3a4c -kel3ae -ke4laen -ke4l3am -ke4l3an -ke4l3as -ke4l3at -ke4l3au -ke2lä -kel3än -keld2 -ke4l5eim -ke4l5ein -kel5eis -4k3e4lek -ke4l3en -kelen5e -ke4l3er -kelgene6 -ke4l5inf -kel5ins -kel5int -ke4lio -ke2l3o2 -ke2lö -kel3öf -kelsch4 -kel3s4k -kel3sl -kel5s6tern -k3emb -2k3e2mi -2k3emp -ke2na -kena4b -kena4g -ken3au -ke2nä -4k3endg -kend4r -kend4s -ken5dsc -4kendst -ke4n3e4b -ke4nel -ken5end -ke4n5en5e -ken5eng -ke4n5ens -ken5ent -ke4n5enz -ke4nep -ken5erk -ken5er4n -ke4n5ero -ken5erö -ken5ers -ke4n5est -ken3e4t -ken3f2 -keng2 -4ken4gag -k5en6gel. -k5en6gels -ke4nim -ken3in -kenk2 -4kenlad -4kenläd -ken3ne -ke2no2 -ken3om -kensch4 -4k5en4sem -kens6tei -kens6temp -4k5en4te. -ken6ten. -ken6tene -ken6tenp -4k3entf -4k3entg -ken3t4h -4k3entl -4k3ents -4kentw -4kentz -ke1o2 -ke3pf -ke2pl -k3e2po -ker3ae -ker3ak -ker5all -ker5alt -ke4rap -ker3as -ke2r3ä -k5erbit -4kerde -ke4r3e4b -ker5eck -ker3ef -k5ereign -ke4rein -k5e4remi -ke4r5end -ker5ens -ke4r5ent -ke4r5erd -ke4r5ers -kere4z -ker4fas -4kerfol -ker6folg. -ker6folgen -4k5erfül -ker6gebn -kergene6 -4kergu -ker6hoehu -k5er6höhu -ke6r5in6nu -kerin6st -kerin6te -kerk2 -k5erlang -ker6langu -k5erlau -ker6lebn -ker5nab -ker4nar -kerner5 -ker6nere -ker6ners -ker6n5eur -k5er4nie -ker3o4b -ke5rode -4k5erodi -ker3oe -k4eror -2k3erö -kersau4 -ker6schü -kert2 -ker4tue -k3er4tü -ke4ruh -4k3erz. -4k5erzes -k5erzeugu -4kerzie -ker6zieh -ke4san -ke4sel -2kesh -ke3sha -ke4spa -kes4pe -4k3es4sa -kess4p -ke3sta -ke2s3u -ke4t3a4b -ke4t3an -ke4t3au -kete2 -ke4t3eb -ket3ei -ke4t3el -keten3 -ke4t3er -2ke2th -ke2to -ke4t3re -kets2 -ket4tau -ket6t5erz -ket4t3h -ket4to -2keud -2keup -2k3e2va -2k1e2x1 -2k1f2 -kfen2 -k3fi -kfi4le -kfin4s -k3fu -2kg2 -kgas5ta -kgeb2 -kgespen6 -k3gl -k3go -2k1h2 -khau6sen -k3he -ki1a -ki2ad -ki2ag -kia2k -kia2r -ki1c -2kicl -ki3dr -2ki2dy -kiefe4 -kiel3a -kie4lei -kie4l3i -kie4l3o -kierer6geb -4ki3ern -kie4sei -kie4so -kie4s3p -kiess4 -kie4ze -ki3f2l -ki3f2r -kik2 -2kila -kilan4 -2kilä -2kile -2kili -4k3illu -3kil4o3 -ki3m4o -2k3imp -ki3nad -kin3al -ki2nä -4k5index -4k3indi -4k3indo -4k3indu -2k3inf -kin4g3a -kin6g5ent -kin4g3r -2kinh -kini3k4 -kini3s -k3ink -kino3 -kinos4 -4k3inse -kin3sh -4k3insp -4k3inst -2k3int -ki1o -2kior -kio2s -kio2v -kip4pr -3kirc -kir6scha -ki3s2h -2k3i2so -2ki3s2p -kissens6 -ki3sta -ki3s4to -kit6schl -kit6sch5w -2kiv -2kiz -2kj -k3jo -2k1k2 -kkab2 -kka3la -kka3lö -kking3 -k3kn -kko3ef -kk4or -kkue3b -kku3s -kkü3b -kl2 -2kl. -2k3la. -2k3laa -2k3lac -kl4ae -4k3laed -4k3laeh -k3laeu -2k3lal -kla5me -kl4an -4kland -klan4ga -klan6ger -klan4g5r -4klat. -klaue4 -k3lauf -4k3laug -5klavie -2kläd -2k3läh -kl4är -2k3lät -2k3läu -2k3le. -kle5ar. -kle5ari -k2leb -kle4ba -kle4b3r -2kled -k2lee -4kleer -k3leg -2k3leh -4k3leib -k4leid -klei4er -kleinen6 -4k3leit -k3lek -k2lem -k3lem. -kle2o -kl4ep -4klero -kles3t -k2let -kle3us -2k3lex -4klich -k2lid -k4lien -k2lif -2k3lig -k2lim -4klin. -k4li3na -4klind -k4lino -k2lip -k2lir -k4lisc -4klist -2k3liz -k2lo. -k2lob -2kloc -4kloec -4kloef -k4loest -k4loet -2klog -2kloh -2k3lok -k2lom -k2lop -klor4 -4klose. -4klosem -4klosen -4kloser -4kloses -klos4s5t -k4lost -4klot. -4klote -4klots -2klöc -2klöf -2klöh -k4löst -k2löt -k1lu -3klu2b3 -k2lud -k2lue -4k3luec -k2lug -k2lum -klungs5 -klus5s4 -2klux -2k3lüc -2kly -2k1m -k3ma -kma4li -kmes4st -kmi2t -1kn2 -4k3nach -2k3nad -k3naep -2knah -2k3nam -k3napf -kn4ar -2knav -k3naz -2knäp -kne4bl -kne3e -knen4a -2knes -kne4ta -kne4t3r -4knetz -2kneu -2kney -k2ni -kni2e -4k3nier -knies4 -4k3nis. -4kniss -2k3niv -k2no -kno4b3l -4knorm -kno2s -2k3nov -2k3num -1ko -ko3ad -kobal4 -kobalt5 -ko4beli -kober4e -2k3obj -kob3la -kob4sk -ko3che -koch3i -ko3edu -k5oeffn -ko3eli -ko3erh -ko3erk -ko3erm -k3oert -ko3ev -ko4fen -koff6e5i -4k3offi -koh3lu -ko3hö -2k3ohr -ko1i -koka3s4 -kol4a -ko5lau -kol3c -kol2k3 -kol4l5e4b -kol4lei -kol6lerw -ko3lo -2k3o2ly -ko4m3er -2komn -ko4mp -ko4mun -ko4nau -kone2 -ko4nel -ko4nem -ko4nod -kons4 -kon5su -kon4tae -kon4tä -kon4t3h -ko2nu -koo2 -ko3on -2kop. -ko4pal -ko4p3an -2kopä -ko4pf -kopf3a4 -kop4f5en -kopf5erk -kop6f5err -kopf3l -2kopg -ko4p3he -kopi4e -2kopk -ko4pli -2kopm -3kopp -ko4p3re -2kop3s2 -ko3pte4 -2kopv -2kopz -ko3r2a -kor4an -kor4bar -kor4b5er -kor4bla -kor4b3r -2k3orc -kor4dar -kor6d5erfo -kor6der6g -kor6d5erw -kor4dr -ko4r3el -2k3org -kor4k3a -kor4ker -kor4k3r -kormo3 -kor4nac -kor4n5ae -kor4n3ä -kor4n5e4b -kor4n3o4 -ko4rob -4korpi -k3ortu -ko3r4u -k2os. -ko4scr -kos4pr -ko2st -ko5stei -kos6t5er6z -kot4a -ko4t5ent -kot5erb -ko3t4i -koti4n3 -kot3s2 -kot4tak -kot4tan -kot4th -3kow -2k1ox -1kö -kö4res -2k3ört -2k1p2 -k3pa -kpi2t3 -k3pl -2kq -k3qu -1kr2 -2k3rad -kra4da -kra4dei -4k3raed -k5raeum -2k3rah -k5raket -kr4am -kr4an -4k3rand -krang4 -5krankh -kran5s -k3rats -kr4au -k3raub -4k3rauc -4k3raum -2kräd -k3räte -k3räub -4k3räum -2k3re. -4kreak -4k3real -k4reat -k2reb -kreb4sc -kreb4s5o -2k3rec -4kred. -4krede -5kredi -4kredn -4kredu -2kref -2k3reg -4kreib -4kreic -kre3i4e4 -kreier4 -4k3reif -4k3reih -4k3reim -4krein -krei6s5ei -krei6s5in -kreis5t -k2rem -krem4l3 -k2reo -4krese -4krest -4kresu -k2reu -2k1rh -2k3ric -4k3rieb -5kriec -4k3ries -k3rind -4k5rings -4k3risi -kro3a4d -krob4 -krobe4w -5kroch -kro3e4b -4k3roeh -kro3el -4k3rohs -2k3röh -2k3ruc -krue3b -2kruf -kru4fer -krum4ma -4k3rump -2k3run -krü3b -2k3rüs -k2ry -2ks -ksa2b -ksab3a -k4s3aeh -ks3aem -k3saen -ksa2f -k2sa2k -k4s3amt -k2san -ks3ang -ks5anka -ks5ankä -k5sanke -ks3ant -k2sap -ksa2r -k2s3a2s -ksau4e -k4s3au4f3 -ksau4g -ksau4re -k4saut -k2s3a2v -k2säh -ks2än -k2sce -ks2ch2 -ksche4f -k2s3cr -k2s3e2b -k2s3e2c -k3s2ed -ks3eie -ks4eif -k4s5eind -k4s5einf -k4s3e4le -ks3elf -ks4end -k4s3eng -ks4enk -ks5ense -k4s3en4t -k4s3erf -k4s3erg -k4s3erh -k4s3erk -k4s3erl -k4s3erp -k4s3err -k4s3ers -k4s3ert -k4s3erw -k4s3erz -kse2t -ks3eta -k2s3e2v -k2sex4 -kshi2 -k2s3i2d -ksi2n -k5sinf -ks3ing -ks3int -k2s3is -kso2b -ks3oe -ks3ofe -k3son -k2s3op -kso2r -ks3org -ks3ori -k4s5ort. -k3spaz -k3s4pec -k3spek -k3s4pel -ks4pes -k4sprä -k3spri -k4spro -k3spru -k3s4pul -ks3s2 -k4st. -k3stab -k3stac -k3stad -k4stag -k5stall -ks5tanz -k3s4tar -kstat4 -ks5tat. -kstatt5 -k3stau -k3stec -k3stei -k4steil -k3stel -k3s4tep -k5stere -ks4tern -k3steu -k3s4tew -k3stic -k5s4tieg -ks4tiel -k5s4ties -k3stil -k3sto -k4stod -ks4toe -ks4tol -k4ston -ks4top -k4stot -ks2t2r -k6straen -k4strän -k4s5tres -ks5troc -kstro4m5 -k4strop -k3s4tub -k5s4tuec -k5stuer -k4s3tüt -k4s3um. -ks3umb -k4sumf -k4s3ums -k2s3un -k2s3ur -2kt -kt3abr -k2t3ad -kt3akr -k4t3alg -kt3al4l -ktal3s4 -kta2m -k4t3amo -kta2n -kt3ana -k4t3ang -kt3ano -kt3ans -kt3a4re -kt3arm -kt3ars -kt3art -kt3arw -k5tasc -kt3asp -kt3atl -k4t3auf -kt5aust -ktauto4 -k3te. -kt3eff -kt3ein -k4t3e4la -k4telem -k3te2n -kten3e -ktens4 -k4t5entf -k4tentl -k4t5ents -k4tentw -kten3z -kt3e4pi -kte5r4an -kter5ei -k4t3erh -kte4rik -kter5oe -k4t3erö -k4t3e4ti -ktgan4 -ktge3n4 -kt1h -k4thel -k2thi -k2tho -k2thy -k2t3i2d -kti4me -k4t3ind -kt3ini -k4t3ink -kt5insa -kt5inse -kti4s3e -k4t3iso -kt5i4ter -ktiven6d -k4t3oef -kt3of -kto4n3a -ktop3a -kto4rei -k4torga -ktorgene6 -kt3ott -k4t3ram -k4t3ras -k4t3rau -kt3räu -kt3rec -kt5reic -kt2ro -ktro3e -ktro3s -kt3rue -k4t3run -k2t3rü -kt1s2 -kt3s4a -kt5sae -kt3sä -kt3se -ktsen3 -kt4s5ene -kt2sk -kts5org -kttei4 -k4tunf -k4t5ungl -k4tuns -k2tup -ktu4ran -ktu4ras -ktu4rei -ktu4rer -ktur3z -ktz2 -1ku -ku4al -ku3an -ku1c -kuck2 -kue5bel -ku3en. -kuen4s -kuer4s -ku3fa -kuf3s -kuge4l5a -kugel5e -ku2gl -ku2h1 -kuhle4 -2k3uhr -ku3in -kul2a -kulan4z -ku3li -2k3ulm -2kulp -kul4t3o -kul4t3r -k3umba -2k3umd -ku3m4e -2kumg -k2umk -2kuml -kum4san -k3umsc -kum4sei -kum4si -kum4sp -k3umw -2kumz -kun5d4a -kunde5i -kundein6 -k3une -k5ungeb -k3unk -2kunm -kun4s4 -kunst3 -2kunt -2kunw -2k3up. -ku3pfl -kup3s2 -2kupu -kur5ans -kur4bl -kur5ein -2k3url -ku2r3o -kurr2 -kurs5an -kur4sau -kur4s5ei -kur4ser -kur4sin -kur6s5or6te -kur4s3p -kur4ste -kur4str -kur5tsc -kur4zel -kurzer6k -ku4sak -kusa4r -ku4schl -ku4s3el -ku4s3er3 -kus3ev -ku2sp -kus3pi -kuss4t -ku2su -2kut. -kut4a -kut3ei -kut6schw -1kü -kü3bel -2kübl -kü1c -kü2li -kür2s -2kv -k3ve -2kw -k1ya -ky3la -2kz2 -kze5le -kz4en -kzeu4g5i -k3zu -kzue4r3 -1la. -1laa -la3ar -la3at -la3ba -la4bad -3labä -2labb -lab4bri -2labd -4l3aben -la4ber4z -2labf -2labg -2labh -l3a4b3i4t -2la2bl -2labn -l3a4bo. -l3abon -l4abor -2l3abp -la3b4ra -lab4res -4labruf -2labs -3labs. -4labtei -la4bus -2labw -2labz -la3ceb -la3cel -lacher4 -la6ch5erf -lachste6 -lachter6f -lach6terp -lach4t5o -la5chun -3lack. -lacks4 -l3ada -2ladd -3ladem -la4des. -4ladj -2l3adl -l3adop -2l3a2dr -3ladu -2ladv -la2e -l4aed -2laef -3laehm -4l3aehn -3laemp -l3aemt -4l3aepf -2laeq -l3aerz -lae4sc -3laeuf -4l3aeug -lae3um -2l3aex -la4f3ae -la2f3ä -la4f3ei -la4f3er -2laff -l4afi -laf3ra -laf3s2 -2laf3t4 -la2g3a2 -lag5eis -lagen5d -lag6ers -2lagg -la4gil -la4gin -4l3a4gio -la2g3l -la2g3n -lago2 -la4gob -la4gop -lag5s6eid -lag5stan -lag5stei -lag5sto -1lah -1lai -1laj -4l3a4kad -la4k3ar -l4ake -la4kes -la4kin -3l4akka -l4ako -la4kro -l3akt. -l3akte -l3akts -2l1al -3lala. -lal3ab -3l2ali -lam2a -3l4amä -2lamb -la4mei -lam4en -lament4 -la4mer -lam4m3a -3lammf -2l3amn -2lamo -la4mor -l4ampe -4l3ampl -l3ampu -lam3s -2l3amt -lamt4s -3lamu -la4mun -la4nae -4l3anal -4l3a4na3t -la4nau -2la2nä -3land -landa4d -lan4dam -lan4dan -lan4d5au -landei6g -lan6d5en6t -lan6d5erh -lan6d5ers -lan6d5erw -lan6d5erz -lan6ding -lan6d5inn -lan4dob -lan4dra -lan4d5ru -lan4d5rü -4l3anek -lanel4 -l3anem -lan5ente -lan5erd -la4n5er4f -la6n5erst -4langa -lan4gae -lan4gan -lan4gar -lan4gat -lan4gä -4langeb -lan4gei -6l5angel. -6l5angeln -lan6gerl -lan6g5ers -lan4g3i -4langri -langs4 -lang5sa -lang5sc -lang5se -lang5si -lang5sp -lang5ste -4lanhä -4lanim -4lanke -lan4k3l -2lanl -2lann -4la4nod -la4nof -la4nor -l4anpu -l4anro -2lans -3lanse -3lansw -l4ant. -lan4tan -l5antig -4lantw -lan3um -2lanw -l4anwar -l4anwer -3lanx -lan4zel -lan6z5erf -lan6zerg -lan6z5erh -lan4zin -lan4zw -2l3apf -la2ph -2l3a2po -4lappa -lap4pl -l4ar. -la4r3ab -la4r3ac -la4r3a4g -la4r3an -la4rau -4larbei -4l3area -lar3e4b -la4r3ei -la4r3el -lar3g -lar5ini -lar5ins -l3arkt -4larm. -4larmat -lar4mau -lar4mei -l5armem -l5armer -4larms -4l3a4rom -3larp -lar6schl -lar4ter -l3arti -lar3tu -3l4aru -3l4arv -lar5za -4larzn -lar3zw -la4s3aa -la4sae -la4sam -la4san -la4sas -la2sä -lasche4 -la4s3ei -la5ser -la6s5er6ke -2lasi -la4sin -la4sis -la4sor -lass5er6kl -5lassern -5lassers -las5sto -lass4tr -3lassu -la2st -last5ae -last5an -last5ar -last3ä -last5ein -4lastiz -las3to -la4stu -3lastw -3lastz -l4at. -l4ata -lat5ang -la4tar -3l4atä -la3t4e -3late. -l3a4tel -late4n -3later -late5ra -3latè -2lath -la4ther -la3t4i -2latl -l3atla -l3atmo -la4toe -la4tol -4l3atom -la2tö -la4t3ra4 -la4tro -lat6schn -lat6schw -4latta -lat4tak -lat4t5an -lat4teb -lat6t5end -lat4tex -lat4t5in -4lattr -lat4t5ro -lat4zei -lat4zer4 -3laub. -lauben6s5 -lau4bla -lau4bra -lau4bre -3laubs -laub4se -lauch5a -l4auer -lau4f3a -lau4fel -lau4fer -lau5fer. -lauf5ere -lau4fin -lau4fo -lauf5s4t -laug4l -l4aui -2lauk -3l4aun -4laun. -l4aur -l4aus. -lau6scha -lau6schl -4lausd -4l3ausf -4l3ausg -4lausr -4l3auss -laust4 -4lausw -4l3ausz -lau4tae -4lauto -lauto6ri -lave2 -laven3 -1law -lawa2 -2lawe -1lax -la4xel -lay3s -lä1c -1läd -2läf -2lägy -3lähm -2l3ähn -3lämp -2l3ämt -lände5s -2l3äpf -2läq -2lära -lär4mar -lär4mat -lär4mei -lär4m5em -lär4mun -lär4st -2l3ärz -lä2sc -4läser -3läuf -2läug -2läx -1là -2lb -l4babw -lb3alp -lb3ana -lb3ang -l4b3ant -lb3arb -lb3a4ri -lb3ärm -lbb2 -lbby2 -lbei6ge. -l4b5eise -l4b3elf -lbengene6 -lber4ei -lbe4rem -lbe4ren -lbe4ris -lbes2 -lbe4tag -lbe4tap -l2b3i2d -lb3lag -lb3las -l4bled -lb4len -lb2lö -lb3oef -lb3oel -l2bof -l4b3ora -l2b3ov -l2böl -l4brea -lb5ries -lb4s3au -lbs4t -lbsta4 -lb4stab -lb4st5ac -lb6staende -lb4stam -lb4stan -lb4stau -lb6stände -lbst5er -lb4str -lb2u -lbuch5s -lbu2n -l4b3une -lby2t -2lc -l1ce -l4ch. -lch3al4 -lch5arm -lch3ei -lche4n -l5chen. -lch5erh -lch5ern -lch5ersa -lch5ert -lch5erz -lch3eu -l3chi -lch3la -lch3lä -lch3li -lch3n -lch3ob -lch3re -lch3sh -lch3sn -lch3s4t -l2ch3u -lch3w -l1ci -l2ck -l1cl -l1cr -2ld -l2da2b3 -l2da2d -ld3aef -ld3aer -ld3aes -ld3aet -l2daf -l2da2g -l2d3ah -l2d3ak -l2d3al -ld3ams -l2d3a2n -ld3arb -ld3arr -ld3art -l4d3ass -ldas4t -ld3asy -ld3ato -ld3att -ldau5sta -l3däm -ld3är -ld3äs -ld3ät -ld1c -lde4ben -ld5eck. -ldefen4 -l2d3e4i -ld5elef -l4d3elf -ld5e4lit -l4d3emb -ld5e4mis -l4d3emp -l4d3ems -lde4ni -l4dentl -l4d5ents -l4d3enz -lde5rae -l6d5er6fah -lder6gebn -l6d5erlas -lder3r -lde4sel -lde5skr -ldes4sa -lde5sta -lde5stel -lde5str -ld3eur -l2d3ex -ldhof5s -ldi2a -ldi3ae -l4d3ide -ldi2e -ldien4 -ld3ill -ld3i4mi -ld3inf -l4d3ink -l4d3ins -l4d3ion -ld3iso -ldo4br -l2d3oh -l4donk -ldo2p -l4d3ope -l4dopt -ldo2r -l4d3ori -ld4os -ldos3t -ld3rab -ld5raen -l4draeu -ld3rag -ld3ral -ld3ras -ld3rat -ld3rau -l4d3rän -l4drec -ld3rie -ld3rob -ld3rod -ld3roe -l4d3rol -ld3rom -l2drö -ld3run -ld3rüc -lds5amt -lds4kl -ld3sko -lds2p -ld5stel -lds4to -ld3tu -ldu2 -l4dueb -ld3ufe -ldu3i -l2d3ul -l2d3um -l2düb -ldwa3r -1le -le2ad -le4an. -le4anm -le2as -le3atl -leauto4 -le2av -leben4s -lebens5e -le5ber -2lec -lech5au -le4ch5ec -le6chens -le4chi -lech5oe -lech5o4f -leck5se -3leco -ledi4e -le4dit -le2dr -3leer -lee4ret -le3f4a -2l3eff -lef2l -lef4o -le4gabt -le4g3ae -le4gang -le4g3ar -leg5ast -le4gau -le2gä -lege3s -le4gesl -4l5egge. -4l5eggen -legi5en -le2gl -le2g3n -leg2r -legs2 -leg5sta -leg5ste -leg5str -4lehe. -3lehm -leh6mest -3lehn -3lehr -lehr3a -leh4rei -lehr3o4 -le4id -lei3dr -lei5eri -2leif -lei4fan -lei4fei -leifen6s -lei6fer6g -4l5eigen -3leih -lei4hau -leil4 -le4im -lei4mau -4leimb -4leimh -leim3p -le5impo -l4ein. -4leindr -lei5nec -lei6nerb -lei6nerk -lein5erz -6l5einfal -4leinn -4leinsa -4leinto -4leintö -4leinwe -2leir -lei6sch5a -lei6schei -lei6sch5l -lei6schn -lei6scho -lei6schw -6leisens -lei6s5ern -4leisf -4leiss -leis6s5er6 -lei5stro -4leisz -2leiß -lei4ß5er4 -lei4tae -lei4t5a4g -lei4t5ak -lei4tal -lei4tei -leit5er6kr -lei4t3o -leit5sk -leit5s4p -leits4t -2leiw -2l3ek3s -4lektr -2lekz -3lela -le4l3ab -le4l3ac -le4l3au -le4lef -lel5ein -4l3e4lek -4leleme -leler4 -lel5erd -lel5erk -lel5ers -lel5erz -le4lev -lel3ex -4l3elf. -4l3elfe -le4lim -le4l3in -le4lor -lem2a -lem5anz -le4mau -lem5ein -lem4en -lem5erf -lem5erk -3lemes -3lemet -le4m3id -le5mil -4lemmt -le4m3o4r -l3e4mot -2lemp -lemt2 -le2mu -l3emul -l4en. -lena4b -lena4d -le4nan -le4n3a4t -le2nä -lende4s -4lendl -4len4dog -l3endp -4lendun -le4n3e4b -len3ec -le4n3ed -le4nel -lenen5erg -le4n5eng -4l5energ -le4n5erk -len5erl -len5ero -len5ersc -len5ert -le4nerw -le4net -le4n5eur -4leneuv -len3f2 -4l3eng. -4len4gag -4l5enge. -len6gem. -len6ges. -4lengp -leng4r -len6k5lag -len6k5lei -len6klin -len4kob -4lenniu -le4n3on -le4n3or -le4n5ott -len6s5ein -4lensem -lens4po -6l5enteig -l5entfe -4lentla -4lentlu -4lentlü -4l3entn -6l5en6trop -4l5entrü -4lent4wa -4lent4wä -4lentwi -4lentz -lent6zug -5lentzuw -le4nz -len5z6er -len4zin -len4zun -len3zw -lenz6wei -lenz4wo -4l3enzy -le3o2f -le3o2s -le4pen -3le3p2f -2l3e2pi -4l3epoc -l4era -le3ra. -ler5abi -ler5aeu -ler3a4g -ler3al -ler3a4s -lerauto6r -ler3az -le2r3ä -4l5erbe. -4l5erben -ler6blin -4l3erbs -4l3erbt -le4r3e4b -lere3c -ler3ef -ler5eigen. -6l5ereign -le4r5ei4m -ler5eis. -le4r3el -le4remi -le4r5eng -le4r5ens -le4r5en4t -ler5er5e -le4rerf -le4r5erg -le4r5ers -le5res -le4r3e4t -7lergenau -7lergeni -l3ergi -4l5ergol -4l5er4heb -l4eri -le4r3i4d -leri4e -lerin4d -lerin4g -lerin4s -lerk2 -4l5erlas -6l5erlaub -6lerlebn -4l5er4leu -ler4mäs -ler4mäß -ler4nal -ler4nar -ler4nau -lerner4 -ler3o4b -ler3of -le3ros -4l3erot -4l5eröff -ler6regu -4l5er4ric -ler6rung -ler4set -lers4k -ler6s5tur -ler3sw -ler3t -6l5ertrag -leru2 -le4rup -4lerus -le4r3ut -l4erwec -6l5erweit -4l3erz. -4l5erzeu -4l3erzg -4l3er4zo -le4s3al -lese5in4 -lese5ras -lese5s4k -les5ess -l4e3s4h -lesi3e -le3s4ki -le3sko -les4kop -4lessi -lester6i -4l3estl -lestra4 -le3stu -2lesw -2lesy -4l3e4tap -4l3e4tat -4lethi -4lethn -lets2 -letsche6 -let4tau -let4tel -let4top -lett3r -lett3s4 -2leue -le3u2f -leu4ka -2l3eul -le3umb -le3u4m3i -le3umo -le3ums -3leunt -le3url -4l3euro -3leut -4l3e4val -4leve. -l3e4vol -2lex -3lexik -le4xis -lext2 -2l1f -lfa5chu -lf3aka -lfang3 -lfan4gr -lf2at -lf3aus -l2fec -lfe3e -lfein4f -lf5einh -l4feis -l4f5erei -lfe4rel -lferin6d -lfe5sta -lfe5str -l2f3im -l2fj -l3f2jo -lf2la -lf3län -lf2lö -lfpi2 -lf2r -lf3ram -lf3run -lf2sa -lfs3au -lf2sä -lf4spe -lf4s3ti -lf2su -2lg -l3ga -lgär4s -lge3in4 -lgene6ri -lge6ners -lgens4 -lge4rab -lge3u -lgge3 -l3g2h -lgi3en -lgin4s -l4g3lam -l3g2le -lg2lö -l3go -lg4san -lg4sau -lg2se -2l1h -lhauser6 -lhe3x -3lhi. -lhu3s2 -1li -li3abe -li3ac -li3ak -li3an4g -li3ato -liär2 -3lib2 -libi3e -li3ce -4lichet -lich3r -lich4ta -lich4to -lichts4 -licht5sp -2lick -l2id -lid4a -li4dea -4l3idee -lider4 -li4d5ers -lidi4e -l3i4dio -li3dri -3lie. -lie4ba -liebe4s5 -lie4br -li3e4f3a -5liefer -li3e4fi -li3efk -li3efl -li3efm -li3efp -lie4fr -li3em -lie4na4 -liene4b -liene4v -lie4no -lie4r5ei -li5erstu -lie4s3c -lie4s5t -li3f2l -li3f2r -3lift -lif4tu -lig5ang -liga3s -li4gatt -4l3i4gel -lige4n -li4g5er4s -li4gin -li2g3l -lign4 -lig4ne -li4g5rad -li4gre -li4gro -ligs4c -ligs4p -lig5str -li3ker -lik3l -lik4li -lik4spr -lik4tau -lik4ter -lik4th -lik4t3i -likto4 -lik4t5or -lik2u2 -li3la -li3li -2l3ill -li3m4a -lima3c -lima3e -lima3s -2limm -limma4 -3limo -2limp -lin5all -lin3a4m -lin3an -lin4d5ei -lin4del -4lindex -4l3indi -4l3indu -li4nef -li4neh -li4nep -line3s -lin4gl -2l3inh -3l4inhe -4linik -lin5iti -2linj -lin4kar -lin4k5au -link3l -lin4kla -4l5inner -li4n3ol -lins2 -4l5insel -4linspe -4l3insz -2lint -3lintu -3lintü -li3n4um -li2o -li3oe -li3ons -li4onse -lio3r -lip3he -3lipt4 -li4rak -li4ran -2lirr -lir4t3r -3lis. -li3s2a -li4sau -li4schl -li5schm -li4schn -li4sch5r -li4schu -lis2h -li3shi -2l3isl -4l3isol -4lisot -liss2 -lis3sa -li2st -lis4tra -l2it -lit2a -li4t3a4d -li4t3ak -li4tal -lit5all -li3t4e -lite3i -litein4 -lite5rar -lit2h -3lito -li4tri -li4tro -lit3s2 -li2tu -li3tus -lit4za -lit4zi -lit4zu -li4vea -live4n -2livg -livi3e -2lixi -li4z3ae -li4z3an -li2z3ä -lizei5t -li2zo -2lj -2l1k -lk2a -l4k3abl -l4k3aeh -l4k3alg -l4k3alp -lka4n5er -lka4n5in -lk3arm -l2käh -l4k3eif -lkens4 -lke3re -l3kla -l4k3lad -l3kle -lklore5 -l2knu -lko2f -lk3ofe -lkraf4 -l4k3rob -lk3roc -lks1 -lk2sa -lk3sän -lk2se -lk3s4ke -lk2su -lkue3b -lk2ü -lkü3b -2l1l -ll3abb -lla4ben -l4labk -ll3abl -l4l5abri -l4l3abt -ll3aff -ll3aft -lla3ga -l6l5a6gent -lla3gl -l4l3a4ma -ll3amb -ll3amo -ll4anb -lla6nent -lla4ner -ll3ann -lla4no -l5lans. -ll5ansä -ll5anzü -l4l5appl -l4larc -lla3rö -ll3atl -l4l3atm -ll5attr -l4l5auff -l4l5aufg -l6l5aufko -ll5aufl -ll5aufr -ll5aufsi -ll5aufst -ll3auk -llau4m -ll3aus -l4lausk -lla2x -ll3axt -llb2 -ll3c -ll3d2 -lld4e -ll5ebene -ll3ech -lle3er -lle6gest -ll3eif -l4l3eig -ll5einsa -l4l5eise -lle4la -lle4lei -lle4ler -l4l3e4mi -ll3emp -lle4na -ll5endl -ll5en6dun -lle6nens -lleng4 -llen6ge. -lle4n5in -llenk4 -llen6sem -l4l5entf -l4l5entg -l4l5entk -l4l5ents -l4lentw -llen4zy -lle4r3a -ller5eis -l6l5er6fas -ller6geb -l6l5ergen -l4lergi -llerin6f -llerin6t -ller6kla -ller6klä -ller6kun -ller6laub -ller6müd -l4l5ernt -l4l5eroe -l4l3er4ö -ll6ers. -lle4ses -lle4spo -lle4th -llet5he -llet6terf -ll3ex3e -ll3ext -llf2 -llg2 -ll3ide -ll3ido -lli4gan -lli4go -l4l3imb -ll3imp -l4l3inf -llings5 -ll3inj -l4l3inl -l4l5inst -ll3int -lli5on. -llk2 -lll4a -ll3m2 -lln2 -llnes4 -l4l3obe -ll3obs -lloe6sen -llo4ner -l4l3ope -ll3opf -l4l3orb -l4l3ord -l4l3o4ri -ll3ort -l4l3osz -l2l3ou -ll3ox -llö4sen -llrue4c -llrü4c -lls5amte -llsau4e -ll4s5en5e -ll4s3es -llsgene6ra -lls5int -ll3sla -ll3sli -ll5s6temp -ll3sti -ll4stor -ll5szen -llti4m -ll3umm -ll3urg -ll3urn -ll3urs -llus3e -llus5t4r -l2lüb -llz2 -2l1m -l2m3a2b -l4mada -l4m3aep -lm5aerz -lm5aest -lm3aka -lm3alm -lma2m -lm3ama -l4m3apf -lm3arr -l4m3art -lm5attr -lm5auss -lm5ausw -l2mäp -lm3ärz -lm3äst -lm1c -lmd2 -l2m3e2b -lmeeres6 -l2m3ef -lme2h -lm3ehe -lm5eins -l4m3emu -l4m5ends -lme4no -l6mensem -lmen4zy -l2m3e2p -lmer2 -l4m3er4b -l6m5ereig -l4m3erf -l4m3er4r -l4m3erz -l4messa -l2m3i2d -lm3iko -lm3inh -lm3ins -lm3int -l2m3of -lmo2p -lm3ope -lm3orc -lm1p2 -lm3s2k -lm3s2n -lms2t -lms5tur -lms2z -lmt2 -l2m3uf -l4m3ums -l4munt -l2m3ur -2ln -l1na -ln2ab -lna2r -l4nare -lnd2 -ln2e -l3nei -l2n3in -l1nö -l1nu -lnus2 -l1nü -l1ny -1lo -3lob. -lo3ba -4l3o4ber -lobe4s -2lobj -lo2bl -2l3obm -lo3che -lo4chel -loch3o -lochs4 -lod2g -lodi4e -loe2b -2loed -loe3du -loe2f -l3oefe -l3oehr -2loek -l3oel -2loep -lo3er. -loe4t3a -2loé -l1of -2lofe -lo4fen -2loff -lo3fj -lof2l -loga2 -lo4gar -lo4gei -lo4g3el -lo4g5ers -lo2g3l -lo4gor -lo4gre -logs2 -log3sp -loh4e -lo5her -loh4ner -lohn5erh -lohn5erw -2l3ohr -loh4rer -loi2r -2l3okk -lo2k3r -2l3okt -4l3oliv -2l3o2ly -lo4mel -lom5ing -2lomn -lo4n3al -lo4nau -lon3er -lon4gu -lon4sae -lons4p -4l3onto -lo3ob -lo2op -lo4pal -lo4per -lo3pi -lop4p3a -lop4pr -2lopt -lo4r3am -lo5rand -lo2rä -lor3do -lo3re -lorein4 -lo5r4en -2l3or3g2 -lori4di -lori6en. -lori6eng -lori6enm -2lort2 -l4orti -lo2s3a -lo4ske -2loss -los4su -lo5stei -loste4r5 -4l3osth -lo3sti -lo4stö -los3tr -lo3s4tu -l3oszi -2loß -lot5ein -lo4thr -3loti -loti4o -lotte4n -lot4tr -2lotz -lour4s -2l3ouv -l1ov -2lo2ve -l2ovi -2lox -1lö -lö2b -3löch -2löck -2löd -lö2f -2l3öfe -l3öffn -2l3öhr -2lök -2l1öl -2löp -2löss -l3östl -3lösu -2löß -lö2th -2löz -2l1p -lp2a -lpeng4 -lpete4 -lp2f -lpf4e -l5phas -l4p3hir -l4p3hoe -l4p3hor -l2p3hö -l2p3hu -lpi2e -lpi2p -l4ple. -l3p2n -lp3sta -lp3sti -lptu5r4 -l3pu -2lq -2l1r2 -l3ra -l3re -lre3st -lroe4s3 -lro2h -lrö2s3 -l3ru -lrue3b -lruecker6 -l3rü -lrü3b -lrücker6 -2ls -lsa4b3a -l2s3a2d -l4s5aeus -l4saka -ls3alg -ls3alt -l4s3amp -l4s3ana -l4sanb -l4s3ane -l4sanf -ls3ang -l5sani -l4s3ank -l4sanm -ls3ann -l4sanr -l4sant -l4s3anz -ls4apa -l4sa4ri -ls4ark -ls5assi -l4s3au. -l4s3auf -lsau6ger -lsau4m -lsau6rer -l4s3aus -l4saut -ls4äug -l4s3äus -lsby2 -ls2ca -l6sch5eid -lsch5eint -lsch4le -lsch4lo -l4schmü -l6schore -lschs4 -l4sch5wo -l4sch5wö -l2sco -l2s3e2b -l4s3eie -l4s5eind -ls5einf -l4s5eint -l4s3e4li -l4s3emb -l4s5en4ge -l4s5enzy -l4s3epi -l4s3er3e -l4s3erf -l4s3erg -l4s3erh -l4s3erk -l4s3erl -l4s3ern -ls3err -l4s3ers -lse3r4u -l4s3erw -l4s3erz -lse2t -l4s3eta -l4s3eth -lsex4t4 -l2s3id -l4s3imp -l4s3ind -l5skand -l5s4kele -ls2ky -l3s4law -l3s4nac -l4s3oef -l4s3off -l2s3op -l4s5ort. -l4s5orts -l1sö -l2söf -l3s2öl -l1sp -lspani5 -ls4pare -ls4park -l4sparl -l4späs -ls2pe -l4spiz -l4spoe -ls4pon -ls4por -l2spö -ls5prae -l4s3prä -ls4pul -l4spun -ls3s2 -l3st2a -lstab4 -ls5tabl -l6s5taeti -l4stag -lstahl5 -l4s3tak -l4stalb -l4s5tanz -l4stas -l4s5tat. -l4s5tate -ls5tauc -l4s5täti -l3stea -l5steck -l3steg -l3stei -l4steil -l3stel -l6ster. -l5s6ternb -l5s6terns -ls3tes -l3steu -l3stic -ls4tie -l3stil -l3stim -l3s4tip -l3sto -l4stoch -ls4toe -ls4toi -ls4tol -ls4topf -l3s2tr -l4s5trac -ls5trad -ls5trit -lstro6ma -l3s2tu -l4sumf -l4sumk -l4suml -l4s3ums -ls3umv -ls3unk -2lt -lt3abr -lt5ach. -l3t4a3d -lt3aeh -l4t5aerz -lta3f2 -ltag2 -lt3agr -lt3aka -lt3all -lt3alt -l4t3alu -ltampe4 -lt4anda -ltaner4 -lta6nerf -ltan6gab -ltan6ges -ltan4sa -ltan4sä -lt3a4po -lt3app -l6tarbei -ltar6beitn -ltare4a -lta5rhe -l4t5arma -ltar6me. -ltar6men. -ltar6meni -lt5art. -l5tarta -l5tartei -ltar6tik -l5tartis -l5tartr -l5tartu -lt3asp -lt3atl -l4t3ato -lt5aufb -lt5aufk -lt5auge -l4taut -lt3äh -l4t3ärz -ltbau5c -lt3e4ck -lte3e -l3tehu -lt5eink -lte5iso -l4telem -ltem4pu -l4temul -ltende6s -lten6gag -lten6gel -lten6spo -l4tentl -lten6t5ri -l4t5ents -l4tentw -lte4rat -lter6be. -lter6bel -l5terg -lter6kun -lter6loe -lt5er4lö -lt5er4nä -lter5oe -l4t3esk -lte5sta -lte5stra -lte5stre -lte5stro -lt5etho -lte4tik -lt3eur -l3thas -lt3heb -lt5heri -l2t3hi -l2t3ho -lt2hu -lt3hun -l2t3hy -lt3ide -lti6g5er6k -l4t3ind -lt3inf -l4t3ins -lt3i4ra -lt3i4ri -l4t3iso -l5tist -lti5va -l4t3o4be -l4t3obo -lt3oef -lt3oel -lt3oes -lt3o2f -l3top -l4topt -l4tord -l4t3org -l4t3o4ri -lto2w -l2töl -lt3ös -ltra3l -lt5räuc -lt3reb -lt3rec -lt5reic -lt5reif -l4trig -l4t3ris -l4t3roh -lt3ruc -lt3rum -ltse4l -lt4s5ele -lt5skal -lt4s5ort -lt3s4ph -lt4staf -lt4stie -lts4tre -lt4s5tüt -lt5szen -lt3t -ltu4el -ltu3en -lt3uh -l2tum -lt3ums -ltu4rab -ltu4r5an4 -ltu4ras -ltu4rei -ltu6r5ent -ltu4r5er -ltu4r3i -ltwa3r -ltz2a -1lu. -lu3an -2lub1 -lu2ba2 -lu2bo -lub3s2 -luch4s5a -luchs5t -luch4t5o -luch4t5r -lucht6s5o -lu4cku -lu4de. -lu2dr -2lueb -3luebd -luecke4 -luecker5 -2lueh -lu3ent -lu3enz -lue6scha -lue6schw -1luf -2l3u2fe -2luff -lu3f2r -luf2t -luf4t3a4 -luft3e -luft3r -lu4g3af -lu4gas -lu5ge. -lu4g3e4b -lu4gei -lugen5e -lug5erl -lug5erp -lu2g3i2 -lu2g3l -lu2go -lu2g3r -lug5spo -lug5sta -lug5ste -lug5str -lu2gu -2l1uh -l2u3hu -lu3id. -lu3is. -luka2 -3luke -l3ult -lumbi5e -2l3umd -lume2 -lumen3 -2lumf -2lumg -2l3umh -l3umj -2lumk -2luml -2lumm -lum4p3h -2lumr -l3ums -2l3umw -2lumz -1lun -2l3una -lu3nap -2l3unb -lund3r -2l3unf -l4ung. -5lunge. -4l3ungl -lungsgene6 -2l3u2ni -4lunkr -2l3unl -2lunr -2l3uns -2lunt -2lunv -2lunw -3lupe -lupf3 -lu4r3an -lu4r3at -lu4rau -lurch3 -lu4r3ei -2lurg -lu4ris -l3urk -2l3url -2lurn -luro2 -lu4rof -lu4ror -2lurs -2l3urt -lur3z2 -lu4ser -lu3si -lu3spr -lus4s3a -lus4s3c -lus6sein -lus4s5el -lus4s5er4 -lus4si -lus4s3o -lus4s3p -lus4st -luss5ti -lus4sur -lu2st -lust5ae -lust5ak -lust5ei -lus6t5erf -lus6t5erk -lus6terl -lus4t3o4 -lus4t3u -lu2s3u -lu4t3ae -lu4tal -lu4t3an -lu4t3au -lu2tä -lu4t3eb -lu4t3e4g -lu4t3ei -lu4t3el -lu4t5en4g -lu4t5ent -luter4 -lu4t5erb -lu4t5ere -lu4t5erg -lu6t5ersa -lu4t5erz -lut3ex -lu4thy -lut5ins -2luto -lu4t3ob -lu4toe -lu4t3o4f -lu4top -lu4t3or -lu2t3r -lut3s4a -lut5schl -lut3s4k -lu4t3ur -lut3z -2lübe -2lübu -lücke4 -lücker5 -lügel5e -2lüh -lü4h3e4l -lü4hem -lüh3l -lü2s -lü4scha -2lv -l2veo -lv2l -2lw -2lx -1ly -ly1a2 -ly3c -ly3en -lye2r -2l1yo -ly3on. -ly3onn -lyra3 -lyri3e -ly5sen -ly3sp -ly3th -ly4t3re -2lz -l4z3aer -l2z3ag -l2zak -lz3al -lza2m -l4z3ant -l4z3app -lza2r -lz3aro -l2za2s -l2zat -l4z3aus -l2z3är -l4z3eim -l4z3eis -lze2l -l4z3ele -l4z5enth -lzen6tro -l4zents -l4zentw -l4z3enz -lze2p -l4z3er4h -l4z3er4m -lze3ro -lz5ersa -lz5er4tr -l4z3er4z -lze2t -lz3eta -lzeugin6 -l2z3id -lz3iko -lzi2m -lz3imi -l4z3ind -lz3inf -lz3ing -l4z5inst -lz3int -lz3l -lzo2b -lz3oef -l2zo2f -l2z3or -l2z3oz -lz5rei -lzt2 -lz3tha -l4z3u4fe -lzug4s -lzvol4 -l4zwar -l2zwo -l2zwö -l2zwu -1ma -ma3ala -maa2n -ma3ari -ma3atl -2m3abb -2mabd -2mabf -2mabg -2mabh -2mabk -m3abla -4m3a4blä -2m3abm -2mabn -ma3bo -mab4ra -ma4bri -2mabs -2m3abt -2mabw -2mabz -ma4c3ar -ma5chan -ma3chi -ma3cho -m5achse -mach4ta -mach6terf -mach6t5erg -mach6t5erh -mach6terr -mach6t5ers -mach6t5ert -mach6t5erw -mach4t5r -ma3chu -ma2ci -2mack -mack4s -2m3act -mada4m -2m3add -ma4del -ma4dem -ma3d2j -2m3adm -2m3ad2r -mad2s -2madv -4m3aend -4m3aepf -ma3epo -2maeq -maerer4 -ma3erh -ma3erw -maer6z5en6 -maes3t -ma5estr -ma3eta -mae5us. -maeu4s5c -ma1f -2m3aff -maf2r -2m3aft -ma4ge. -ma4geb -ma4geg -ma4gek -mage4n3 -ma4gep -ma4ges. -ma4gev -ma4gew -2m3agg -magi5er. -magi5ers -m3a4git -ma3g4n -ma3gri -mah4ler -ma3ho -ma3hü -mai4ler -mai4n3e -mai4s3e -ma4kad -ma4ke. -ma4kes -ma3kla -ma3kli -mak2r -2m3akt -ma4kus -malad4a -ma5lade -mal5aer -ma3lag -mal3ak -mal3an -ma3las -ma4lär -malb2 -6mal6dehy -malen4s -ma4lent -mali5er. -ma4l5ins -ma4linv -malk2 -4mallt -ma4l3on -ma4lop -ma4l3o4r -mal5ska -malster6 -mal5s6terb -mal5s6tern -malu2 -ma4l3ut -mal4zäp -2m3amb -ma4mid -ma4moe -2m3a2mö -2m3amp -2m3amt -m3anae -5manag -4m3a4nal -man3an -ma4nar -4m3a4na3t -ma4nau -2m3anä -4manbie -man4ce. -man4cek -mand4 -man3di -man5erf -ma4n5er4n -ma4net -man3g4a -4mangab -4m5angeb -m3angs -4manhä -manin4t -5manip -4manke -4manla -m4anle -4ma4nod -man4o5m -4manpf -m4anpl -m5ansatz -4m3ansä -m5anschl -4m3ansi -4m3ansp -4m5ansti -man4tag -man4tan -man3um -manu3s -4manwen -4m3anza -manze4 -m3anzu -m3anzü -ma3o2b -ma2or -ma3pfl -ma4phr -4m3appa -4m3appl -ma3pr -4marag -mar4an -2m3arb -4m3area -mar3g4 -m3argu -ma3ri -3mark -mar4klo -mar4kr -m3armb -4m5arme. -mar4o -maro5d -ma3rö -2marr -mar6schei -mar6scher -mar6sch5l -mar6schm -mar6schn -mar6scho -mar6schr -mar6sch5w -4m3arse -mar4sp -mar4su -4mart. -4m3arti -ma3ru -ma3rü -masch4 -ma3sco -maser4n5 -ma2s2p -ma3spa -ma5spen -ma3spr -4m3as4so -mast5an -ma5stel -masten4 -mas6t5ent -mas4ti -mas4to -ma3s4tr -ma4t3ae -ma4t3an -ma4t3ar -ma4tat -ma2t3ä -mat3ei -m3a4tel -mate4n -ma4t5erd -mat5er5in -ma4t5erz -ma4tho -mati6kan -4m3atmo -ma4toa -ma5toma -ma4t5opf -ma4t5ort -mat3s4p -mats4t -mat3th -matt4ha -matt4hä5 -mat4the -mat5url -2matz -2mauc -2m3au2f3 -2m3aug -mau4les -mau4l3o -mau4lö -ma3um -m4aun -ma3una -ma3ung -ma3unt -m4aus. -mau4sa -4mausd -maus3p -maus3t -2m3auß -mau4ta -mau6t5erh -mauto4r -ma2xa -m2ay -ma3yo -1mä -mäh3r -2m3änd -m1äp -2mäq -märer4 -mär4kl -mär2z -märzen4 -mä3us. -mäu4s3c -2mb2 -mbe4er -mben5er -mbe4ren -mber5er5 -mbi4e3i -mbi3o4f -mble3s -mbo4ler -mb4re -mb3s -mby2t -2mc -m3ch2 -m1cr -2md -m2dab -md3aer -m2d3an -md3arb -md3art -m2d3a2s -md3ato -md3är -mde2a -m2d3ei -m4d3e4mi -mde2n -m4d3ent -mder2 -m4d3erf -m4d3erk -m4d3erl -m4d3err -m4d3ert -m4d3erw -mdi2a -mdi2e -mdien4 -m2dob -md3ras -m3dru -md2s3a -md2sä -mds2p -md3ums -1me -mea2n -me3at -meb2 -me4ben -me1c -4m3echo -3med -medie4 -me4dito -3mee -mee5ing -mee5ins -me5e4mis -mee2n -me3ene -meep2 -mee4rei -meer5eic -meer3u -2m3eff -3meh -meh4l5er -mehls4 -mehr3a4 -mehrer4 -meh6rert -2meic -2m3eif -2m3eig -mein4da -meinde5s -mei5nec -mei6nenz -mei6ner6k -mei6nerl -mein6hal -mein6sel -meins4p -m2eis -4m3eis. -m3eisb -me3iso -5meist -meist5a -me4l3au -3mel3d4 -melde5i -me3lec -me4l5ein -4m3e4lek -4m3elem -me4l3en -me4l3er -melet4 -4m3elf. -mel3id -4m3e4lim -meli4n -mel4k5ei -3melo -me4l3ob -me4lor -mel3p -mel4s5a4c -mel4san -mel4sph -mel4s5ta -6m5el6tern -mel4zei -mel6z5ent -mel4zi -4m5e4miss -2m3emp -m2en. -me4na4b -men4ade -men4ag -me4nan -men3au -me2nä -mende4c -4m3endl -mend4u -men3e4b -men3ed -men5end -menen5e -men5ent -men5erb -men5er5o -men5erö -men5ers -mene4s -me4n3e4v -men3f -4men4gag -me4nid -me4nil -me4n3im -menk2 -men4s5au -men4se. -men4seb -men6semb -mens5ert -men4ses -men4sex -mensex5p -men4s5i4d -mens6temp -mens5trun -men4sum -men6t5anz -mentar6ten -men6t5eig -men6t5enden -men6t5ers -men6t5erw -men4tet -men4thi -men4thu -men6t5ins -men6tres -men6t5rol -men3um -menz4w -me1o2 -2meou -me1p2 -me3ra -mera3s -merauto6 -mer3b -4m5erde. -4m5er4den -mer4dun -me4r3e4b -mer5eck -mere4f -me4r5eis -me4r5end -me4r5ens -me4r5ent -me4r5erd -me4r5erf -merer6kl -4m5erfol -merg4 -4m5ergän -4m5er4gus -meri4e -merin4d -4m5erklä -4m3erlö -merns4 -mer3of -mersau4 -mer4siv -mer3sm -mer3sn -5mer5te -merti4 -4merzä -mer4zer -4m5erzeu -mer4zwe -m4es. -mes2a -mes3ae -me2sä -mes3e4l -mes5er5o -mes3or -mes4pe -mes3pr -mes4s5ac -mes4sal -mes4s5an -mes4say -mes6sersa -mes4s5or -mes4ste -mes4sti -mes4stu -mest2 -me3sto -3me3ta -meta5st -mete4r3 -me3t2h -2metö -met5res -me4trig -metro5s -mett5en6de -met4t3i -meu1 -me3um -meute5r6e -2m3e2x3e -2mexp -1mé -2m1f2 -mfahr6ta -mfen2 -mfi4le. -2mg2 -mgeb2 -mglim4 -mgus2 -2m1h2 -mh4au -mhaut5o -1mi -mi1a -mia2b -mi3ak -mia2m -2m3i2at -mibi3e -mic3e -mi3ch -mi2ci -mi3cr -m2id. -mi4dea -mi4di. -mi3d2r -2mi2dy -mie3dr -mi4eno -mierer4 -mie4tau -mie4t3i -mie4t5rä -mie4tru -mi1f -mi4k3ar -mi4k3el -miken4 -mi4k5ens -mik6erl -mi4k5er4z -mi4k3in -mi4k3ob -mi4k3of -mi4kon -mi4kre -mi4kum -mi3l4a -mil4i -4m3illu -mi3l4u -2m3imm -2m3imp -mi3na -mi4na4b -min5eck -mi4nef -mi4n5e4r5i -4minfo -m2ing -min4gan -min4gl -ming3s4 -4m3init -min4kr -mi4n3of -mi3nop -mino3s -6minterp -mi3nu -min4z3o -mi2o -mi3o2b -mioni3 -mi3os -mi1p -mi3ra -2mirg -mi3sa -mis4au4 -mi6schef -mi6scherb -mi6sch5er6z -mi4schi -mise3r -mi3s2n -mi4som -mi3s4pa -mi3s4ph -mi3spo -mis3sa -mis4sac -mis4ser -mis4s5in -mis4st -mis3te -mit3ak -mi4tar -mit3as -mit3au -mite2 -mit3ed -mit3em -mit3er -mit3es -mi2t3h -mi3tia -mit5iss -mito2 -mit3op -mit3or -mi2t3r -mit3s4 -mit5sa -mi2tu4 -mit3ur -2mitz -mit3za -mi2zw -2mj -mjäge4 -2m1k2 -2m1l -ml1c -m3le -mlein4g -mlerin6t -ml2v -2m1m -mm3aal -mma4bar -m4m3aeh -m4m3aeu -mm3a4kr -mm3all -m4manb -m4manf -m4m3ank -m4m3ans -mmanu3 -m4m3anz -mma2p -mm3apo -m5mar. -mm3art -mma3sp -m2m3äh -m2m3äu -mmd2 -m2m3e2b -mm5e4dit -m2m3ef -m4m3eid -mm3ein -mm5eise -mmela4 -mme4l5ad -mme4l5in -mmen4er -mme4no -mmens6pa -mmen6stru -mmen5ta -mmen6te. -mmen6ten -m4m5entg -m4mentl -m4m5ents -m4mentw -mmenzu6c -mme4r3a4 -mme4rec -mmer6fol -mmerk4 -mmer6sio -mme2s -mmes3a -mme3sc -mmes3e -mmes6sens -mmes3t -m2m3eu -m2mex -mmi3e4l -mmi3k2 -m4m3inb -m4m3inf -m4m3inh -mm3inn -m4m3ins -m4m3int -mmisch4 -mmi3s4t -mmi3tu -mmi3tw -mmmi2 -mm3ope -mm3org -m2m3ou -mm1p2 -mms2k -mms4p -mms4tr -mm3te -mm2th -mm5ther -mmuel4 -m2mum -m3mun -mmut3s4 -mmül2 -mmüll3 -2m1n2 -mne4bl -m4nesi4 -mnes3t -mni3er -1mo -m4obb -2m3o2be -2mobj -mo3che -mode5st -mode5sz -mo2dr -moeb2 -2m3oe2f -moe2h -mo3ehe -2moek -mo3ela -mo3e2m -mo3ero -mo4fen -2m3off -mo4g3al -moh2a -mok3al -mo2k2l -mok3la -mo3kle -3molc -molch5a -mol3d4 -mo3lo -2molt -mom4e -mo4n3ae -mon3al -mo4n3an -mon5ato -mo2nä -mon4das -mon4d5el -mon6d5erk -mon4din -mon4dre -mo4n3er -moni4en -moni5er. -mon4s3t -mon3s4u -mont4a -2mony -mo2o -mo4per -2m3opf -2mopt -mo3rae -mo3ral -mo3rat -mor4d3a -mor6d5ere -mor6d5erl -mor6d5erm -mor4dr -mo4rei -mor5enge -morgens6 -mor5int -moro2 -mo4rob -mo4rop -mor3t4 -mosa6ische -mo4ska -mo3s2p -mo2st -mo3s4ti -mo2s3u -mo3t4h -mo2u -mous2 -2m1ox -moy1 -1mö -möb2 -mö2c -2m1ö2f -2mök -2m1öl -2mp -mpa3ne -mp2ar -mp3arb -mpat4 -mpe4la -mpe4ler -mpe4lin -mpe4ne -m4p5ener -mpens4 -mpf3au -mpf3ef -mp4f3el -mp4f5enz -mp6f5er6fo -mp6f5erfu -mp4f5erz -mpf5lae -mpf3lä -mpf3lo -mpf5lüc -mpf3ra -mp3haf -mph3au -mphe2 -m4phoe -mph5org -m2phö -m3pi -mpi2a -mpin4 -mp2l -mp3le. -m4p5lem. -m4p3len -m4p3les -m3po -mpo3l4i -mporta4 -mpor6tag -mpor6ter6 -mpp2 -m1pr -mp3sh -mp3sta -mp3s4tr -2mq -2m1r2 -m3ra -mrich7te -m3ru -2m1s -m2sak -ms3all -m4s3alt -ms3amt -ms5andr -m4s3ang -m4sanm -m4sanr -m4s3ant -m4s3anz -m2s3a2s -m4s3auf -msau4g -msau4n -ms3b -m4schan -msch4l -m3schu -m2sco -m2s3ef -ms5einsa -m3s4ek -mse5l4a -mse4le -msel5ei -mse2n -msende4 -m4s3ene -m4sentl -ms3erf -ms3erk -ms3ert -m5serv -mse2t -ms3eti -mse2x2 -mshi2 -m4s3imp -msi2n -ms3ini -ms3int -m2skl -m2s3o2d -mso2r -ms3orc -m4s3ori -m2s3ou -m4sped -m2spl -ms4pon -m4spot -m4spro -ms2pu -ms3s2 -mss4a -m3stät -ms5täti -m3stei -m4steil -m4stem -ms2ti -m4stit -ms2to -m5s4tras -mstro4 -ms2tu -m2sty -ms3ums -m2s3ur -m3sy -m2szi -2mt -mta2m -mt4asc -mt3änd -mt3c -mte2a -m2t3e2b -m2t3e2d -m2t3ef -mt3ein -m4t3eis -m4t3eiw -mt3elt -mte2m -m4t3emi -m4ten4ga -m4t5engl -mte4no -m4t5en4se -m4t5entf -m4t5entg -m4tentl -m4t5en4tr -m4t5ents -m4tentw -mte4ri -mter4n -m4t3eta -m2t3eu -m2t3e2v -mt1h -m2t3i2d -m2t3im -mt3inb -mt3ind -mt3inf -mt3inh -mt3ins -m2t3i2r -mti6schw -mt3isr -mt3ita -mtmen4s -m2tob -mt3oes -mt3ord -mt3org -m2töl -mt3ös -m4trah -mt5rang -mt5rauc -mt3rec -m4t3res -m4t3ris -mt3rob -mt3rot -mt2sa -mts2c -mt3se -mt4s3e4l -mt4sen -mt5s4kel -mts5kr -mt5spru -mts4tro -mtt2 -mtu3en -mt3um -mtz2 -1mu -mu1a -2muc -mu4ckel -mucker4 -mu6ck5erk -mu6ck5ers -mu6ck5erz -muck5sp -muck5st -mu4cku -2m3ueb -2muec -mue4her -muehl5a -mu3el. -muell5a -mu3els -muen4s -mu3ez -mu2f -2mug -2m1uh -mu3la -mul4lau -mul4l3e -2muls -mult2 -2m3umf -2muml -2mumr -mum4s3p -m3umst -2mumt -mundan4 -mun6dant -mun4dar -mun6d5erf -mund3r -muner4 -mu4nerk -4m5ungeb -4muniv -m2unm -2munw -2munz -mur3au -3mure -2m3url -murn2 -mu3r2u -mu4r3u4f -m2us -mu2s3a -mus5erf -mu4sin -mu2s3o -mus4s5e4h -muss4p -mu2st -4must. -mus3ta -mu2su -muße3 -mu4t3au -mu4t3er -2m3u2to -mut4str -2müb -1müd -1müh -mü4her -mühl3a -1mül -1mün -1müt -2mv -2mw2 -mwel2 -mwelt3 -mwelter5 -mwetterer5 -1my -my1a -my3la -my3on. -my3s2p -my4s3t -2mz2 -1na. -1naa -n1ab -3naba -na4b3ar -3nabä -4nabb -4nabd -3nabec -na4bei -nabens4 -nab5erk -nab5er4r -2nabf -nab4fr -2nabg -n3abh -n2abi -na4bi4t -na2bl -na4b3or -na4bos -3nabö -na4bra -na4bre -na4bri -nab4rue -n4ab4rü -4nabs -4nabt -na4bus -2nabz -3nac. -3nachb -na4che -nachen4 -na5chen. -nach5ent5 -nach5erb -nach5ers -3nachf -3nachh -nach3i -3nachl -3nachm -3nachr -nach3s -5nachsc -n5achse -5nach5s4p -3nacht -n4acht. -nachter6f -nach6terk -nach6t5ersc -nach4to -nach6traum -nach6t5ren -3nachw -4nadap -n3add -nade4l -nadel5a -nadel5e -na4der -n5ader. -nadet4 -n2adi -nadi5ers. -n3adl -4n3adm -4n3a2dr -n3ads -3n2aec -3naee -3naehe -3naehm -4n3aehn -3naeht -3naei -2n3ae2m -2n3aen -n2aep -2naeq -3naer. -na3erg -nae5us. -3naex -2n1af -3na3fl -na3fra -1nag -nage3i -nagein5 -nagel5e -nage6lei -na4gem -na4gent -2n3agg -na4gio -na4git -na3g2r -1n2ah -na4h5erk -3nahm -n3ahn -nah3oe -na2hö -4n3ahr. -na2hu -1n2ai -na3i2d -nai2e -na3im -2n3air -nai4re -n1ak -na4kad -3nakä -2na2kn -3nako -na4kro -4nakt -na4kus -n2al. -nala2 -nal3ab -nal3ad -nal3ae -nal3ak -nal3am -nal5ang -nal5are -nal3as -nal3at -na2lä -4nalbb -4n3albk -nalb4r -nal3da -na4leb -na4l3ed -na4l3ei -na4lel -na4l5end -nalen6se -na4lent -nal5ents -na4l3ep -na4l5erb -na6l5erei -na4l5erf -na4l5er4g -na4l5er4h -na4l5erm -na4l5er4s -na4l5erw -na4l5er4z -nales4 -nal3ex3 -nalf2 -nal3g -n2ali -na4l3i4d -nalk2 -nal6l5en6g -nal4lor -4nallt -n3almt -na4lob -na4loe -nal5oef -4nalog -na4lop -na4l3or -nal3ö -n4als. -nal5s4ka -nal5skr -n3alt. -n3altd -nal5to -n4alty -na2lu -nal3uk -2naly -n3a4mal -n3a4mat -3namä -3name. -na4mei -3n4amen -namen6s5p -4n3a4mer -n2ami -n3a4mid -na4min -2n3amm -2na3m2n -3namo -n3amp -namt4s -3namy -4nana -na4nae -na4nal -na4na3t -n3a2nä -2nanb -n3and2 -n4and. -nanden4 -n4ands -na4nem -nan3er -nan3eu -4nanf -2n3ang2 -2nanh -2nani -4n3ank -4nanl -4n3anm -3nann -4n3anna -2nanr -4n3ans2 -nanta4 -nan4tag -4nantr -3nanu -2nanw -nanzen4 -nan6zene -nan6zeng -nan6z5er6f -nan6zer6g -nan6z5er6m -nan6z5ers -nan4zin -nan4zw -1na1o -1naö -na4per -3napf. -na3pfa -4napfel -3napfs -3napl -na4pos -na2pr -n1a2q -n1ar -3n4ar. -3nara -na4rab -na4r3ak -na4ran -3narä -n4arbo -2narc -3n2ard -nar5ein -nare3s -n2arh -3n2ari -n2ark -n4arle -4narm. -4narme -na4rom -na3rö -n2arp -nar4rh -n4ars. -narse3 -nar4s5ta -nar4stä -2nart -n4arta -n2aru -na3rum -n2arv -n4arze -3n2as. -3nasa -na4san -na4sar -nas3au -3nasä -na4schm -na4schw -3nase -3nasf -n2asi -na2sp -nas4ph -4n3assi -na3sta -naster4 -nas6t5erf -na2su -na2sy -nasy5la -1nat -natal5s4 -na4tat -na4t3au -nat3ei -na2th -n3atla -2natm -n3atmu -n2ato -4natom -nats5ac -nat6scheng -nats5en -4n3atta -n5atten -n1au -n2aue -nau3eb -3nauem -3nauen -3naues -3naui -3n2aul -na3um. -4nausb -4nausd -4nauss -naussen6 -n4aut. -n4aute -1nav -2nava -nave2 -navel3 -navi5er. -navi5ers -1naw -3nax. -na2xi -3naxp -n3axt -3nazu -1nä -2näb -3n2äc -2näd -2n1äf -3näg -n4ähe -nä2hi -3n4ähm -2n3ähn -n2ähr -näh3ri -nä2hu -3näi -2n1ä2m -2n1än -n2äp -4näpfel -2näq -närm4s -n4ärr -när4s3t -n2äsc -3näss -2näst -2näu -3nä3um -nä3us. -2nb2 -n3ba -nbahner6sa -nbais4 -nbau3f -nbau3s -n3bä -n3be -nbe3n4a -nbe3ra -nber4e -nber4ga -nber6g5ei -nber4gr -nbes2 -nbi2e -n3bl -nboge4 -nbogen5 -nb4or -nbruchs5tr -n3bs -n3bu -nb4un -nby2t -2n1c -n2cab -n2cei -n2cem -n3ce4n -n2cev -ncha3l -nchar6me -n3che -nchial5 -nchi5en -n3chl -n3cu -2nd -n4dab. -n2dac -n2dad -nda3ef -nd3aet -nd2ag -n2dak -nd3akt -n4d3all -nd5ange -n4danl -nd3ans -n4danw -n4d3arb -ndar4da -ndar4t5r -n4daut -n2dax -nd3äng -n2d3ät -nd1c -nd5dre -nde4al. -nde3e -n4d3ei. -nd3eil -nd5ein. -nd5einh -n4d5einn -n4deis -nde4lat -nde5lec -nde4len -nde4ler -ndel5l -ndel6s5am -ndel6s5en -ndel6s5or -ndel5ster -nde4m5ac -nde4mau -n6d5en6gel. -nden6geln -n6dengli -nde4n5in -ndenk4r -nden5s4k -ndens6tem -n4dentl -n4d5ents -nde3o2 -nd5epoc -nde6r5eis -nde6r5eiz -nde6reng -nde6rent -nder6läs -nde4ron -n4d5eros -nder6tragen -nde4sam -nde4san -nde4sel -nde4ser -nde4set -nde6stät -nde5strae -nde5stran -nde5stri -ndi5en. -ndi4ene -ndi4ens -ndi5ensy -nd3imm -nd5inan -nd3iso -nd5kuc -ndni2 -ndo4n5au -ndoo2 -n4dopt -n4d3orb -n4d3o4ri -n4d3out -n4d3rat -n4dreih -nd3rob -n4d3rod -n4d3roe -n4drose -nd5rost -nd3rot -n2d3rö -n5druc -n4drui -nd3run -nds5amt -nds5chin -nd4s5ene -nds5erb -nd4skl -nd4spae -nd4spä -nd4szi -ndwa5re -nd5wil -1ne -nea2b -nea2n -ne2ap -ne3ar. -nebel5e -nebe6lei -nebels4 -2n3ebn -neb2r -2nec -3neca -3nece -ne3chr -n3echs -ne3ck4a -3necl -3neco -nedi4e -ne4dit -ne4e3i4g -neel4 -neen2 -nee3ra -ne3erk -nee3ro -ne3er4z -nee3t -ne3f4a -nef2l -neflei4 -2n3egg -neg2l -n3e2go -neg2r -4n3e4he. -4neheg -nehen4 -4n3e4hep -nehe4re -3nehm -neh4min -ne3ho -2n3ehr -neh4r5er -2n1ei -3neia -n3eic -nei6derf -neid5erfu -3neigt -5neigu -ne3imp -neins4 -ne5in6sel -n4einsu -nei6senho -neit4h -n4ek. -2nekd -2n3ek3s -nek3t4 -2nekz -n3e4las -4n3e4lek -4n3elem -3neli -ne3lid -ne4lim -ne4lit -3nelk -n2ell -nell5an -nel4l5au -nel4l5ei -nel6l5erf -nel6l5erk -nel4l5in -3n2eln -3nels -3nelt -n2em. -4n3e4mai -2n3emb -nem4e -n2emi -4ne4mig -n4emo -4n3emp -2n3ems -4n3e4mul -n4en. -n2ena -ne5nack -ne4nan -ne2nä -n2enb -n2enc -4n3endb -4n3endd -4n3endf -n3endg -4n3endh -nen4dig -4n3endk -n3endl -4n3endp -4n3endt -4n3endw -n2ene -ne4n3e4b -nen3ec -ne4n3ed -ne4n3el -nen5end -ne4n5en5e -nen5ens -nen5ent -nen5erb -ne4n5erk -ne4n5ers -ne4neru -ne4nerw -ne4n3eu -n2en3f2 -4n3engb -4n5enge. -nen6gen. -nen6gend -nen6gens -4n3engs -4n3engt -4n5engun -n2enh -ne2ni -nen3in -n2enj -n2enk2 -n2enm -nen4nar -nen5nec -ne2n3o2 -n2enr -n2ens -6nensemb -nen3sk -nen3sl -nen3sn -nens4p -nens6tern -4n3entb -4n3entd -4n3entf -4n3entg -4n3entl -4n3entn -4n3entp -nen6t5ric -4n3ents -4n3entt -4nentw -4nentz -ne4n3u -n2env -n5envir -n2enw -n2enz -4n5enzia -nenz4w -n3enzy -neob2 -ne4pen -ne3pfl -ne2pi -2nepo -n3e4pos -nep3t2 -n4er. -ne3ra. -ner3a4b -ner3af -ner3a4g -ner3ak -ne3ral -neral5a -ner3a4m -ner3an -ne4r3ap -ner3ar -nera4s -n4erat -4n5erbe. -4n3erbg -n5erbie -ner4big -4n3erbs -n3erbt -4nerdb -ner4dig -n2ere -ne4r3e4b -ne4r5eck -ner3ef -ne4r5eic -6n5ereign -ne4rein -ne4rek -ner5ema -ne4r5ere -ne4rerf -ne4r5erh -ner5ers -4n5erfol -n4erfüh -ner3g4 -n6erges -n5erheb -5n4erhin -n5erhöh -n2eri -neri4e -ner5inner -nerin6st -nerin4t -n2erj -nerk2 -4n5erklä -6n5erlaub -4n3erlö -ner6löses -4n5ermit -4n3ermü -n2ern -4n3ernä -4n5erneu -4n3erni -4n3ernt -4n3erod -6nerosio -ner4pre -nersau4 -ner4sk -4n5ersts -ne4rup -n2erv -nerve4 -4nerwae -4nerwä -n4erwo -4nerze. -4n5erzes -4nerzeu -n4erzim -n2es. -ne4sal -nesch4 -4n3e4sel -ne4s3ev -nesi5er. -ne4s3of -ne4spa -nes4pe -nes6sige -nes6sigs -ne3sta -nesti4e -nest5rae -nest5ran -nest5rau -nest5rä -nest5ri -4nesyn -ne4tab -ne4tad -n5e4tage -ne4t3an -4n3e4tap -4n3e4tat -ne4t3ei -ne4t3e4l -nete4n -net5erh -net5ers -ne2th -ne4t3in -ne4t3ob -net3sp -n4ett -net3tr -4netui -ne4tus -3netz -net4z3i -ne2u -neub2 -neu3c -ne3ueb -neu4ere -neuer4f -neuer4k -neuer4ö -neuer4r -neuer4s -neuer4w -n4euf -neu3g2 -4n3eule -ne3umf -ne3ums -2n3eup -neur2 -neur3a -4n3eury -neu5sc -neu3sp -neu3t4 -4neuzt -ne4van -ne4vid -ne4volu -n2ew -new2s3 -2n1ex -nex3em -1né -2n1f -nfa5chu -nfah4l -nf5akti -nfall6s5o -nfalt4 -n4fanb -nfangs5 -n4faut -n3fä -n3fe. -n4f3eim -nfen2 -n3fen. -n4f3end -n3fer -nf5erze -n2fex -n3fi -nfi4le. -nfin4s -n2fj -nf2l -nf5laen -nf3län -n3f2o -nfo3st -nf2r -nf3s2 -nf4tan -nf2t3r -nft4sen -nft4s3p -nft4st -nf1u -n2fun -2ng -n2g3a2c -ng3adr -n4g3ael -ng5aerm -nga3h -ng3aka -n4g3alm -ng3a4me -ng3amp -nga2n -ng5anda -ng5angl -ng5ansc -n4g3ant -n4g3anz -n5garn -ngas5ta -ng3ato -ng5attr -ng3auf -n2g3a2v -n2gäh -n2g3äl -ng3ärm -ng3d2 -n4g3eif -n4g3ein -n4g5eise -n4gelem -ngel6s5tu -nge4nat -ngen5ec -n6generg -ngen6erw -n6gensem -ngen5sk -n4gentl -nge4ral -ng5ereig -nge6rend -nge6rers -n4g5erho -ngerin6st -n6g5erwei -nger6zae -nger4zä -n3ges -n4g3ess -ng5e4tat -ngg2 -ngg3s -ng3hu -n2g3i2d -n3glä -ng4lea -ng4led -ng4le3e -ng3ler -ng4lev -n4glig -n4glog -ng5lot. -n4gluk -ng3m -n2gn2 -ng3nar -n3gnä -ng3ne -ng3ni -ng2ob -n2gof -ng3o4pe -ng3opf -ng3p -ng5raen -ng5raeu -n4g3rai -ng5ram. -ng5rand -n4g3rän -n4g3räu -ngres6s5o -n5grif -ng3ris -ng3rob -ng3roc -ng5rost -ng2s -ngs3e4h -ngse4m -ng4sf -ngs5tec -ngs5türm -ng3sy -2n1h2 -nhab4 -nhabe6r5e -n5halb -nh4au -n3he -nhe4re -nhe3x -nhor4s -1n2i -ni3a2b -ni3akl -ni3akr -ni5aleng -ni5alim -ni2ar -ni3as -ni3ato -nibb2 -ni3be -nib2l -nib2r -nibu2 -ni3cho -ni3chr -nich3s -nich4ta -nich7te. -nichter5 -nicht5sp -ni3cr -ni3da -n4i3de. -4nidea -4nidee -ni5den. -4nideo -nidi4e -n3i4dio -ni3d2r -2n3i2dy -nie3b -3nied -ni2e3i -ni3el. -ni3elh -ni3ell -ni3elr -ni3elw -ni3elz -nie2n -nien3e4 -niens4 -nien6z5er -niere4 -nie4r5ei -nie6rerg -nie5sti -nie4t3o -niets4 -ni3fl -ni3f2r -niga2 -ni4gab -ni4g3am -ni4g3an -4n3i4gel -nig5erz -4niget -ni4g3li -nig2n -ni4gre -nig4sal -nig4san -nig4spr -4ni4k3el -ni4k5er4f -2nikh -ni4k5ing -ni4klo -ni4k3of -ni4kor -niko3s -ni4k3ra -ni4k3rä -nikt2 -nil3l -nille3 -ni3l2o -3nimmt -2n3imp -ni3mu -ni3nac -ni3nae -ni3nap -ni3nä -2n3ind -2n3inf -3n4ing. -3nings -2n3inh -4n3i4n3it -2n3inj -2n3ink2 -4n3inno -ni4nor -2n3ins -n4ins. -2n3int -ni3n2u -2n3inv -ni3o2k -ni3ora -ni3os -ni4ron -2n3irr -ni4sai -ni4scha -ni5s4che -ni2s3e -ni3se. -nise4l -ni3seu -nisi2 -nis3id -ni4s3ik -nis3in -ni4s3oe -4n3isol -ni4som -nis3or -4nisot -ni2sö -3niss2 -nis3sk -nis3sz -nis5tha -ni2s3u -3nit. -nita4le -nit4an -4nitia -ni4top -3nitr -3nit3s2 -nit4t3a -nit6teig -nit6tele -nit6ter6g -nit6t5er6k -nit4t5ra -nitt5ri -ni3v -2nj -n3jo -2n1k -n4k3a4bl -n4k3abo -nk3a4br -n4k3adr -n4k3aeh -nk3a4ge -n4k3alg -nk3alp -nk3ama -nk3ang -n4kanh -n4kanl -n4kanna -n4kanr -n4k3ans -nk3apf -nk5art. -n4karth -n4k3atm -n4k3ato -n2käh -nke2c -n4k3ei. -nke4lei -n4kelem -nken5sk -nke6r5eis -nker6fah -nk5ergu -n4k5er4zi -nke2t -nk3eti -n4ketu -nki2d -n4k3ide -nkier4s -nkin4g3 -nk3inh -n4k3ins -n4k5lade -n4k3laf -n4k5lamp -n4klen -n4kleu -nk4link -nk4neb -n4knit -nk5ober -n4koel -n4kopt -nk5o4rie -nko2w -n2köl -nkraf4 -n4krel -n4kren -n4krez -nk3rob -n4kroh -n4k3rol -n5kron -n4k3ros -n4k3rot -nk3rüc -nk4s3ac -nk4s5alt -nk4s3el -nk2so -nk5spat -nk3s4pe -nks4por -nk3spr -nk3sta -nk3s4ti -nk3str -nk3stu -nk3s2z -nk4t3ak -nk4t3an -nk4t3au -nk4t5eig -nk4t5ent -nkte5ra -nk4t5erf -nk4t5erg -nk4t5ern -nk4t5ers -nk4t5ert -nkte5sk -nk4t3in -nk4t5ort -nk4t5rae -nk4trä -nkt5rol -n4kumf -n4k3ums -nku2n -nku3na -nk3uni -nk3urh -n4k3urk -nk3urt -nku2t -nk3ute -2n1l2 -n3la -nlage5i -nlage4n5 -nlan4dr -nlan4gl -nl4au -n3le -nle4gas -n3li -nli4ne. -nli4nem -nlite3 -n3lo -nlus4t5e -2n1m2 -n3ma -nma6l5erl -nm4en -nmen5sche -nmi6schw -nmi2t -n3mo -n3mu -2n1n -nnae4s -nna5esc -nna5le -n4n3alg -n4n3all -n4n3amb -n2n3an -nn4an. -n4n3app -nn3a4sc -nna3s4t -nn3atm -nn3ato -n2nau -nn3d2 -nndar4 -nn3eff -n3n4eh -nn4ei. -nne3lu -nnend4 -nneng4 -nne3pf -nne4r3a -nner6fol -n4n5er4fü -n6ner6geb -nner6hoe -n4n5er4hö -nner6laub -nner6mit -nner3o -nner3ö -nner6war -nner4zi -nne4s3e -nnesi5e -n3n2ex -nng2 -nngas3 -n2n3i2d -nni4sa -nnk2 -nno5be -nn3obl -n2n3of -n3nom -n2n3op -nno2r -n4n3ori -n3nos -nno4t3h -nn4s3am -nn4s3as -nn4sau -nns4po -nn4stec -nn4stoc -nn4s5tol -nnt4a -nn2th -n2n3uf -n4n3unf -nn3ur -nnvoll6 -n2nya -1no -3no. -no3at -2nob. -2n3obd -n4o5bel -nobe4la -nobe6lin -2n3obh -2nobj -no4bla -2n3obm -2n3obs -2nobt -n4oby -no1c -no2de -no3der -n3ods -2n3oe2f -2n3oek -n4oet -no2fe -2n3off -nogene6r -no3hö -n3ohr -n2ok -no4kel -2n3okk -n2ol -no4lam -nol3c -no4leu -no4liv -no4lym -4no4mal -n4omas -no3m4au -no4mel -n4o3mi -non2a -4nonke -2n3ont -2nony -no2o -no3oh -no3os -2n3o2pe -2nopf -nopf3a -no4pie -3nopp -2n3ops -2nopt -3no3p4te -n4or. -nor2a -no4rad -no3ral -4noran -no5rar -n3orbi -2norc -nor4d3a -nor4del -nor6d5en6d -nor6deng -n5ordens -nor6d5er6w -nor4des -nord3o -nor4d3r -nords4 -nor4du -nor2e -no3res -2n3org -n5o4rien -2nork -3n4o4rm -nor4m3o -2n3orn -n4oro -n4orö -2n3ort -no3ru -n2os -3n4os. -nose5l4 -no3sh -no3s2k -noste4 -nos4tei -noster5e -4n3ostl -nost5ra -4n3ostv -n2ot -no4tam -no4t3an -no4tap -no3tar -no4tau -not3ei -no4t3em -note4n -no4ter -not5erb -noter6b5r -no4tex -no3the -no4t3ho -no4t3in -not5ope -not5opf -notor4 -no4t5org -no2tr -3not3s2 -no4t3um -3notv -2n3out -no3va -2n1o2x -n2oz -2nöd -2n1ö2f -2n1ök -2n1öl -2n1p2 -npa4ge -npar4ka -npa4s -npe3l -npi2p -n3pn -n3pr -npro3 -n3ps -n3pu -2nq -2n1r2 -n3ra -nra4dei -nra4du -n3rä -n3re -nrebe4z -nre3sz -n3ri -nroe4s3 -nro2h -nrö2s3 -n3ru -nrue3b -nruecker6 -nr4un -n3rü -nrü3b -nrücker6 -2n1s -ns4aa -n3sabo -ns4ack -n2s3a2d -ns5aemt -n3saeu -n4s3a4gi -ns4ai -n2sak -nsa4kr -n4s3alg -n4s3all -n4s3alm -ns4alp -ns5alta -n4s5alte -n4sanb -n4s3ane -ns3ang -n4sanga -n4sange -n4sanh -ns5anka -n4sanm -n4s3ann -n4sanp -n4sanr -n4s3ant -n4s3anz -n4sapo -nsa4re -ns4ark -ns4atm -nsau4e -n4s3auf -nsau4n -nsau4r -n4saut -n4säugl -n5sche. -n6schefi -nsch5erf -n5sches -nsch5eul -n4schl. -nsch6mau -ns5chor. -nsch4ue -nsch5werd -n4scor -ns2cr -ns5eben -nse4ein -nseha4 -nse4hab -nse6hent -nseher4 -nseh5ere -nseh5erz -nse4hin -nse4hue -nsei4ne -n4s5einf -ns5einh -n4s5einl -n4seinn -ns5einsa -ns5einst -n4s5eint -n5seit -ns5e4kel -nse4la -n4selem -nse4len -nse4ler -nse4lim -nse4l5in -nse4lit -n3s4ell -n4sem. -n4s3epo -n3s4eq -n4s5erar -nse5ras -ns5erbi -nser5eig -n4s5erfa -n4s5erfo -ns5ergi -ns5ergu -ns5ergü -n4s3erh -n3seri -n4s5erke -n4s5erkl -n4s5erle -n4s5erne -nser3o -n4s5eroe -n4s3erö -ns5ersa -nse3r4u -n4s3erw -n4s3erz -n4s3eth -n4sety -ns3eur -nsex2 -n4se4xi -nsex4t -n3s4ha4k -ns4har -nshi2 -ns4hir -ns4ic -n4s3ima -n4s3imp -n4s3ind -n4s3ini -nsinn4s5 -n4s3int -n4siso -nsi4t3e -nsi4tr -n2sk -n5skala -n5s4kali -n3s4kel -ns3kr -n5skrip -n3s4ky. -ns4kys -n3slip -n5smara -nsmaragdei6 -n2so2b -n2s3o2d -ns2om -n2sop -nsor3a -n4s5ort. -n4s5orts -ns4pac -ns4pari -n4spark -ns6part. -n6sparti -ns4pas -ns4päs -ns2pe -n3spek -n4speri -n2sph -ns4pie -n2spl -n4s5poe -n6sporti -n5sprac -n4sprei -n4s5prie -nsrue4 -nsrü2 -ns3s2 -nst5abr -n6s5taeti -ns6tagent -ns5talb -n4stale -nsta4n -n4st5ane -n4s5tat. -n6s5taten -n4stats -n4staus -n4staut -n5stäb -n4s5täti -nst5eifer -n6steil. -n6steile -nst5einhe -nst5einsa -nstein6ste -ns4temm -ns4tent -ns4tep -nst5erbie -nster5ei -nst5ergeb -n6sterin -nst5erke -ns6terne -ns6terns -nster5o -ns4teu -n5steue -ns4tew -n3s4tic -nst3id -ns4tie -n4stilg -ns4tin -ns2to -n4stole -nst5ord -n4store -nst5org -ns2tr -n4s5trac -n4strik -ns5tros -ns2tu -ns5tung -n2sty -nsu4m3o4 -ns5umsa -n4s3urt -n2s3ut -2nt -n4t5a4ben -nta5chl -nt4ack -nt3aem -nt5aend -n4t5aerz -nta3f -nta4gn -ntak4t5a -ntal3a -ntalent6 -ntal5ins -ntalk4 -n4t5allt -nt5alter -nta3ne -n5t4anfr -n4tanna -n4tantr -nt6anwer -ntar5ak -ntar3c -ntar6chi -nt5art. -n5tarta -n5tartei -nt5arten -nt5arter -n5tartex -n5tarth -nt5arti -nt5artu -nta4ru -ntasi4 -n4tath -nt3äm -nt3änd -n4t3ärz -nte5ame -n4teben -nteg4 -n3tehe -nt3ein -n4t3eis -nt4el -nt5elek -nte4let -nte4lin -nte5ma -nte4mot -nte4nem -ntene4r5 -n4tenpä -ntens4 -nten6te. -n4t5ents -n4tentw -n4tepi -n3ter -nter3a4 -nt5erbe. -nt5erbt -nterdru6 -nter5eic -nte6r5eis -nte4rel -nteren6de -nter6loe -nter4lö -nter5n4e -nter5obe -nter5oe -nter3z4 -n4t5erz. -nter6ze. -nte3sa -n4t3ess -nte5stei -ntest5r -n6t5estri -nteu6eri -nt4hals -nthan5a -n4t3heb -n4t3hel -n3ther -nt5herd -nt5herst -nth2o -n4t3hof -n4t3hot -n3thr -n4thue -n4thum -n3ti -nti3an -nti3c -nti3f -nti6k5erh -nti4lin -nt4im -ntim3p -n4t3ind -ntin4e4 -nti5nen -ntiner6s -n4t3inf -nting4 -n4t3inh -ntini5er. -ntini5ers -n4t5inse -nt4is -nti5sta -ntlan4 -n3t4lem -ntmen4s -ntni2 -ntnis3 -n3tof -nton4st -nto5pfa -n4topt -n4t3org -n4t5o4rie -ntos2 -nto3st -nt2r -nt5raed -nt5raend -nt4ral -nt5rand -nt5rang -nt5ränd -nt5räng -n4t5räts -nt3rea -n5treco -n5t4ree -nt5reic -nt5reif -n4t5reis -n5t4rem -nt4ré -n4t5rieg -n4t5ries -nt5ring -n4t3roh -nt1s -nt2sk -nts2t -nt3sti -nt6stief -ntt4a -n3t2ue -ntu3en -n4tumsc -ntu4r3a -ntu4re. -ntu4rei -ntu4rer -n3t2ü -ntz2 -1nu1a -nu3a2r3 -nubi3 -1nu1c -1nud -nudel5e -3nue. -3nuea -2nueb -3nuec -3nue3e -3nuek -nu3el. -nu3els -3nuem -3nuen -nu3en. -nue3na -nu3end -3nueo -3nues -3nuet -3nuev -3nuew -3nuez -nu2fe -1nug -2n1uh -3nuhi -1nui -nu3in. -n2uk -n3u2kr -nul4l3a -nulle4 -nul4l5eb -nul4lei -n2um. -2n3umb -n2ume -4numf -4numg -2numl -3n2umm -4n3umma -4numr -2nums2 -2n3umv -2n3umw -2n3umz -nu2n -2n3una -2n3und -2n3une2 -4n3unfa -4nunfä -nunft3 -3n2ung -4n3ungl -nungs3 -nungscha6o -2n3uni -n3unk -2nunr -nuns2 -2nunt -nunte4 -3nunti -2nunw -3n2unz -4n3unzu -1nuo -2nup -2nur -nu2ra -nu2ri -nur2z3 -1n2us -nu2sa -nus3au -nu2sä -nu2s3i -nus5pe -nus6serl -nus3t -1nut -nu2ta -nuti2 -4n3u4top -3nutt -3nutz -nut4zei -nutzer5e -1nuu -1nu2x1 -1nuz -1nü. -1nüa -2nüb -3nüba -1nüc -1nü1e -1nük -nü3la -1nüm -1nün2 -nü3na -1nüo -nür3c -nürs2 -nür5ste -1nüs -1nüt -1nüv -1nüw -1nüz -2nv2 -n3ve -n3vi -n3vo -nvoran4 -2nw -n3wa -n3we -nweg3s -nwehr3 -nwin6d5er5 -n1x -1ny. -n1ya -n2ya. -1nyf -1nyh -1nyl -n1yo -1nyr -1ny1s2 -1nyw -2nz -nza2b -n4z3aer -n2z3a2g -n2za2k -nz3and -n4z3a4n3e -n5zani -n4z3ant -n4z3anz -nz3app -n4z3a4ri -n2zas -n2zat -n4z3aus -n2zän -n2z3är -n4z3eim -nze4la -nze4lei -n4z3emb -nze5nad -nzene4 -n6zenerg -nzen5ev -n6zensem -nz5ente. -n4z5entg -n4zentl -n4z5ents -n4zentw -n4zentz -nzer3a -nzer5eig -n4z5erlo -n4z5erlö -nzer5oe -n4z3erö -nzer6tau -nzer6trae -n6z5er6trä -nze3sk -nze5sta -nze5s4te -nze5sti -nze5str -nze3u4t -nzin3a -nz5info -n4z3inj -n4z3inn -n4z5inte -nz3l -nz4ln -nzo2b -n4z3oef -n2zof -nz3oli -n2zor -nzue4c -nzu5eck -nzu5eng -n4zwet -n5z4will -n2zwo -n2zwö -n2zwu -1ñ -ño1 -o1a -oa2be -o2abi -oa2br -o2ac -oa3chi -o2ad -oa2dr -oad3ra -oad3t -oa2g -oa3in -oak3l -oa2ku -oa2l -o2ala -oa3lab -oa3las -oa2m -o2a3mi -oampe4 -oa2n -oana3 -oan3e -o2aq -oa2r -oar4dr -o3a2sc -3oase -oa2si -o3ass -o2at -oa2tr -o3au -oauf3 -oa2v -oa2x -oa2z -o1ä -ob3ach -ob2al -oba4lin -ob2am -2ob2ar -2ob2as -2obau -ob3auf -2ob2ä -obb2l -2obe. -2obea -4obec -obe3e -2obef -ob5eins -4obek -obe6leis -obe3li -2oben -obend4 -obera4 -ober5ad -ober5al -o4beram -obe4r5ap -o4berb -obe4reb -obe6r5eis -obe4rel -obe6rend -o4berf -3o4berg -obe6rin6g -oberin6t -obe4ris -o4berk -2obet -2obev -2obez -2obi -obi2e -obi4l5in -1obj -ob2l -ob3lau -ob3leb -ob3lei -ob3li -ob4lit -3o4boe. -2oboh -ob3ort -2obot -ob2ra -2obru -2obrü -ob4s3ac -ob3sc -ob3s2h -ob3sku -ob4ster -3obsts -2obu -o4bueb -o4bunt -obu2s -2obü -o2büb -2oby -oby2t -ocas2 -ocau2 -oc1c -3occl -o1ce -och3a4b -o5chani -ocha4r -och3au -ochdru4 -oche4b -o4ch3e4c -och3eh -och3ei -oche4l -och5ele -o4ch5er4g -ocher6ke -o4ch5er4l -och3id -ochi3e -och3l -och3m -och3n -ochoe4f -ocho4f -och3ö4f -och3re -3ochse -och3sh -ochs4p -och5sta -och5stem -och5stu -ochtergene6 -ochu4f -o3chun -och3u4t -och3w -o1ci -o3ckad -ocke4l -ock5ersa -o6ck5er6sc -o4ck5er4w -ock5le -o2cko -ock3s4p -ock5sta -ock3sz -o4ckum -o1cl -o1cr -ocre2 -od2a -odab2 -od3ak -od2d3r -o3dec -ode3e -odegene6 -ode3i2 -odein4 -o5dein. -o5deins -ode4l5a4g -ode6lauf -odel6l5ae -odel6lau -odel4l5ä -ode4lo -ode2n -oden3e -oden3i -odenk4 -o3der. -o3dern -o3ders -odes5ae -ode4sä -odes4po -o6d5esser -o3dex -2odia -2odie -2odif -2odir -2odo -o3dom -3o2don -o2d3op -o4dora -odo4si -2odö -2odr -ods2 -2ody -ody2m3 -oe3ba -oebels4 -oe4ble -oe2br -oeb2s3 -oe1c -oech6ste -oe2du -oe1e -o3effi -oe2f3l -oegen4s -oe2gl -oe2gr -2oeh -oehe4n3 -oeh3ri -2o1ei -oe4is -oe5isch. -3oe2ko3 -oe2kr -oe2l -3oel. -oel3an -oel3au -oelb5ste -oel5ein -4o3elek -oel5ent -oe3ler -oeles4 -oel4fei -oelfen4 -oel6f5end -oel3in -oel4kle -oel4kli -oel3le -3oelm -oelo2 -3oelr -3oels. -oel3sz -3oelu -3oelv -3oelw -2oem -o3em. -oe4mie -2oen -oen3ec -oen5erg -o3entf -o3entl -oentu3 -oe3nu -oe3ota -2oep -oepf3l -o3e2pi -oe4r3a2 -oer4b3l -oe4r3ec -oe4r3ei -oe4rel -oe4r3e4m -oe4r5ene -oerer6fa -oerer6fol -oerer6gebn -oerer4l -oe6r5ermu -oe5r4erz -4oerf -o3erfi -o3ergä -oe4r3il -oerner6v -oer3oe -o3erö -oers4k -oert6er -o2es -oe2sa -oe4scha -oe6schef -oe6sch5ei -oe6schene -oe6sch5eri -oe4schl -oe4schm -oe4schn -oe4scho -oe4schr -oe4schw -oes2p -oes6s5che -oes4st -oest3a -oes3te -oes3tr -4oesu -oeße4n3 -o3et. -oe4tag -oet3ei -oete4n3 -oet2h -oe3t4he -oe4tho -oe4thy -oe2t3r -o1eu -oe2vo -o1e2x -o1ë -2of2a -ofa2c -of3a2d -of3a2g -of3är -o2f3e2b -o2f3ec -o4fena -o4feng -o4fenk -o4fent -o4fenw -o4fenz -o4f3erb -o4f3err -o4f5er4sc -o3fes -of3eti -of3eun -of2fa -off3al -off3an -off5eie -offe4l -offel5s -of4f3in -3offiz -of2fl -of2fr -off3ru -off3rü -off3sh -off3si -offs4o -off3t2 -of4f3uh -of4fur -ofgas3 -2ofi -of3ins -ofi3s4 -ofi5sc -2o1fl -of2la -of2lo -of2lö -2ofo -o2fop -of3orc -2o1f2r -of3rä -of4s3am -of4sei -of4s3en -of4sin -of4sof -of4spa -of4spe -of4staf -of4sto -of2s3u -of3th -of4t3ri -oft3s -2ofu -o2f3uh -o2fum -o2f3ur -2og -o2gab -o2g3ac -og3ang -og3arb -og3ari -og3aus -og3d -og3ein -ogel3i4 -ogen5ec -ogener6n -ogen4id -ogens4p -o4g3erf -ogeri4 -o4g3eth -ogfi2 -ogge4n -ogging5 -og2hu -ogin3 -o4g3ini -o4g3ins -ogle3e -ogleer4 -og2lo -og2n2 -og4nas -o2g3nu -ogo3e -o3gog -og3o4ri -o2g3ou -og3rau -ogs2c -og3spr -og2st -og3ste -og3sti -ogu2 -o1ha -oh3alk -o2h3ar -o1hä -o1he -o2h3e2d -o4h3ein -o4h3eis -ohen5s -o4h3er4t -ohe3x2 -o1hi -ohkos4 -ohkost5 -ohl3a -ohla4d -oh3lec -ohl5eint -ohl5eis -oh3lep -ohler4 -oh4l5erg -oh4l5erh -oh4l5erw -oh3les -ohl3o4r -ohl5sa -ohlste4 -oh2lu -oh3mi -oh3mu -ohn3ac -ohn3ap -oh4n3e4b -oh3nee -oh3nel -oh6nense -ohn5ersa -oh4n3e4t -oh4nin -3ohnm -oh2n3o -ohn3s4k -o1ho -oh3oel -oho4l3a -oho4l3o -o2h3o2p -oh3öl -ohr3a -ohre2 -oh4r3el -oh4rem -oh4r5er4g -ohr5erh -ohr5erk -oh4r5ero -oh6rer6sa -oh6rersä -oh3rie -ohr3im -oh4r3in -ohrs2 -ohr3sk -oh1s -ohs2p -oht2 -o1hu -o2h3um -oh3w -o1hy -o1i2d -oid3an4 -o2ik -o1i2m -oimm2 -o1in1 -oi4nen -oi2n3i -o1i2o -oi2ra -o5isch. -o4ische -o3ism -o3isr -oiss4 -ois5sa -oi4ta -oi2tu -ojek6tori -o1k -oka3b2 -okabe4 -o2kac -oka3i -okale4 -oka4lei -oka6lere -oka4lin -okal5th -oka3pf -okas3 -oker4be -oke3re -okka3s4 -ok2la -ok2li -ok2lo -ok2lu -ok2o -oko4pt -ok2r -ok2sa -ok2s3o2 -oks3p -okt4o -okto5b -oktobe4 -ok5tor -o4kula -1okw -2o1la -ol3a4br -ol3abu -o4l3ach -olade4n -ola3ei -o4l3aet -ola3fl -ol2al -ol3a4mi -ol3ana -olars4 -ol3a4ve -o1lä -o2l3ät -olb2 -olbe4n3 -ol3che -olch3r -ol5da. -ol4dae -ol2dä -ol4d3ed -old5ersa -ol4d5erz -olde4s -ol4d5ese -ol4deu -ol4dim -ol4dop -oldo4r -ol4d3re -ol4d3ri -ol4d3ro -ol4drü -old3sa -old4sam -old4sk -old3st -old6stro -ol3eie -ol3emb -ol3emp -ole2n -olen3e -ole3ra -o4l3erk -o4l3ert -o4l3erz -o4l3ess -oles4te5 -ole3um -ol3ext -ol3exz -o1lé -ol4fak -ol4fau -ol4f3ei -ol4fem -olfer4l -ol4fle -olft4 -ol2gl -ol4gre -o3li. -oli3a -olie4n3 -oli4kan -ol3imp -ol3inj -oli4tor -oli5tur -oli3ze -ol2kl -ol4kre -ol4kro -olk2s -olks3c -olks3t -oll5ach -ol4l3ak -ol4lanh -oll5anz -oll5ass -ol4l3au -oll3e4c -oll5ein -ol4l3el -ollende6f -ol4l5er4d -oll5erfo -ol4l5erh -ol4l5er4m -oll5ertr -oll5erwe -ol4l5erz -ol4l5ess -ol4l5ins -ollla6der -ol4l3op -oll5s4a -olls4p -ol4lum -2olo -oloni4e -olo5pe -olo3ra -ol3ort -ol2ov -ol3s2k -oltai4 -olta5is -ol4t3am -o1lu -olu4ta -olu4th -o1lü -olve4 -ol2y -4olys -ol2za -olz3a4c -olz3ar -ol4z5eig -ol4z5en4g -ol4zep -olzer4 -ol4z5ern -ol4z5ers -ol4z5ert -ol4zik -ol4zim -ol4z3in -ol2z3o -ol2zö -ol2zw -ol2zy -2om. -o4m3a4bl -o4m3a4br -o4mada -o4m3a4ge -oma3la -o4m3alg -omali4e -om3all -o2m3a2m -4oman -oma4n5er -om4anh -omani4e -om5anke -om4anr -o6mantei -o4m3arm -omar4s -o4m3art -omar4te -o4m3a4sy -4omat -o4m3atl -o6m5atom. -o6m5atome -o2m3au -ombi4e -ombu3c -2ome -o2meb -om3ebe -o2m3ef -om3ein -o4meis -om5eise -o4mele -ome3na -om5ener -o2mep -o5mer. -om3er4h -omer4ke -omer4sp -om5er4tr -o4meru -om3erz -o4mesh -o2m3eu -o2m3ex -omi4c3 -omi2e -omie3s -o4m3inf -o4m3inj -o4m3inn -omi3n4o -o4m3ins -o4m3int -o4m3inv -o4miso -omi3tr -2omm -omme4la -1omn -o2m3oa -om3ope -o4m3orb -o4mord -o4m3org -om3o4ri -ompani4 -om3pf -2oms2 -omt2 -o2m3um -o4m3unf -o4munt -o2m3ur -o3mus -o1mü -2o1na -o2nab -ona4be -on2ac -onaer4s5 -ona5ge -o2nak -onaler4 -ona4l5in -on3amb -on5amei -on3amt -on3ann -on3a4no -on3apo -on3app -on4are -o4narm -on4arr -on4ars -on3art -ona2s -on3asp -on5atom -onat4s -onats5c -2onä -onär4s3 -on4d3an -onda4s -ond5ast -on4d3ei -onder5ei -onde6r5ers -on4d3ex -ondi4e -ond5inn -on4dob -on4d5ril -on4d5rin -on4d3ro -ond5ruec -on4d5rüc -ond4sa -ond4sei -ond3st -ond4ue -2one -o2neb -on3e4ck -o2n3ef -on3ein -one2l -on3ela -on3ele -on3ema -3onemp -one2n -o4n5ends -on4eng -onens4 -on3ep -o4n3erd -o4n3ere -o4n3erf -oner4fa -on3erg -o4n3erh -o4n5erke -o4n3er3o4 -on3erö -on5ersa -o4n3erz -o4n3est3 -o4netu -on3f2 -onf4l -ong2 -on3ge -on3gha -on3gla -on3gle -on3go -ongru5e -ong3s2 -on3gu -2oni -oni5ake -onialer6 -oni5a6lerw -on3ido -oni4enk -oni4enm -onie3s -oni4g3a -oni4ger -oni4gl -onig3s4 -o4ni4kan -o4nikba -o4nikbe -o4nikbr -o4nikei -o4nikf -o4nikk -o4ni4kl -o4nikm -o4nikp -o4ni4kr -o4nikse -onis4o -2onj -on3k2 -3onke -onli2 -onlo4c -onloch5 -on3na -on4na4b -onn4an -on3n4e -onne3s -ono1 -on3o4be -on3ode -on3ofe -o4n3oke -on3orc -onos2 -onot4h -ons3a -onsch4 -on4s3eb -onsen5e -onser5e -onse4t -onsi4d -ons5ing -ons5kl -on4spar -onst4a -on6stapel. -onste4r5 -ont5arm -on4t5auf -ont5end. -ont5erfa -on4t5er4h -ont5erke -on4t5erl -on4tho -onto3e -on4t5rat -on3tu -o1nu -o2nue -2onuk -on3ums -on3url -2onut -1ony -on3z -onz4e -onze5in4 -onze5la -onze5r4a -onzer6t5ak -2oo -oo2bo -oo1c -oo2dr -o1oe2 -oo3en -oo3ge -oo2gl -ooki2 -oo2k3l -oo2kn -ool3c -oomgene6 -oomo2 -oo4mop -o1op -o1or -oo4r3a -oor3d4 -oor5de -oo4r3ei -ooren4 -oo4r5ent -oo4rer -oo2s3a -oo2sk -o3osm -oos2p -oo2s3u -oo2t3a -oot3ei -oo2th -oot3he -oo2t3r -oot4s3a -oot4s5en -oo4tur -o1ö2 -2op. -op3adr -op3aeh -opa3i -op3akt -opa4let -op3ant -opar4a -opa6rade -opa4rit -op3arm -op3äh -4o3ped -o2p3ef -op3eig -o3pek -4o3pel -o3pen -ope4ne -openty4 -4opep -3o4pera -op3erh -3opern -o5pers -op3ey -op2fa -opf3ac -opf3af -op3fah -opf3al -opf3ar -opf3au -op2fä -op4fem -opfe4n -opf5ent -opf5eroe -op4f5erö -op4fin -4opfl -opf3lo -op3flü -op2fo -opf3r -opf5ste -oph2 -op3him -op3hit -o4phol -oph3t -o1pi -opi3a -opia4t -op3id -opi5er. -opi5ers. -opie4r5u -op3i2k -opin2 -oping3 -op3lag -op3lic -op3lis -op2ne -opo4la -op4pan -oppe6l5in -oppin4 -op2p3l -3oppo -2o1pr -op3rec -op3roe -op3rö -3opsi -op3sta -op3sto -op3stu -op3sz -1opt2 -2opt4e -op3th -2opu -2opy -o3ra. -o3raa -ora2b -or3abb -or3abr -or3abt -or3ace -or3ach -o3rad -o4r3add -or3adr -4orae -or3aeh -or3aet -o3raeu -o3raf -o3rag -ora4ge. -4o3rak. -or5akad -3orake -o3rakr -4o3raks -or3akt -or3a4ku -o5ralba -oral6ben -oral6bum -o3rali -o4r3alm -o3rals -oral4ta -oral4tä -ora3ma -or3a4mi -oran6fra -oran4ku -oran4kü -oran4me -ora4no -or3app -or3aps -ora4r3a -ora4rer -2o3ras -o3rat. -o3rate -o3rati -or3atm -o3rats -o4r3att -4orau -o3raub -o3rauc -or3auf -o3raum -o3ra3un -or3aus -oraus6gewaehl -oraus6gewähl -oraus6sche -oraus6wa -or3auß -o3rav -o3raw -o3rax -o3ray -o3raz -o1rä -or3äh -or3änd -or3ät -or4bana -orb5art -or4b5inn -3orbit -orb2l -orb5lad -or1c -4orca -or2ce -4orda -orda4m -or4darb -or4das -or4dau -or4d3e4b -or4d3ei -ord5els -ord5engl -4ordes -or4d3eu -or4d3it -3ordn -or2do -or4dös -ord3st -ord3t2 -or4duf -or4dum -2ore -orea3s4 -ore4ast -o2reb -or3e4be -o4r3e4ck -or3e4di -or3eff -o3reg -or3eig -or3ein -or3e4la -or3emp -or3emu -o4renbo -oren4er -ore4n3i -o4renki -oren6nen. -o6rennet -o4renno -ore4no -o4renpl -o6renpor -orens4 -o6rensen -or3erd -o4r3erf -ore3ri -o4r3erk -ore3ro -or3erp -or3erw -or3erz -or3et4h -o2r3eu -o2rew -orfa4c -or4fend -or4feng -orf3s2 -or3g4a -orge3i -orgein4 -4orget -or3ghi -4orgia -orgi5en -orgi5er -or2gn -orhu2 -o1ri -ori4ena -ori6enbe -ori4enl -ori6enme -ori4enn -ori4enr -5o6rienti -ori4env -ori6enwe -o5rier -ori5er. -or3ima -ori4mi -ori2n -o4r3ind -orin3i -or3ins -o4riso -4o3rit -ork2a -ork5erz -ork2l -ork3sh -orkt4 -ork5ta -2orm -or4mad -or4mam -or4m5ans -orm5asp -or4m3eb -or6m5erfa -or6merge -orm5inh -orm5ins -orm5int -or4m3op -orm5ord -orm5org -orm5ori -or4mum -ormu4n -or4m5uni -or4munt -ormvol4 -orna4c -orn5ack -or4n3al -or4nan -or4nar -or4nau -orn5erf -orno3s -2o1ro -or3o4be -or3oef -or3oel -o2r3ok -or3oly -oro5n4a -or3o4pe -or3opf -o2r3o2r -o2r3ox -o1rö -or3öl -orp2f -orrai4 -or3r4e -or5ren -orr2h -or3s2a -orsau4 -or3sä -orsch4m -orse3s -or3sh -ors2k -orspor4 -ors4tin -orst5ob -ors2z -ort5aer -or4tan -ort5an. -or4t3a4r -or4t5aus -or4tär -or4t3eb -ort5eiw -or4t5eli -orte4n -or4tent -or4tenz -ort5erb -or4t5er5e -ort5erfi -or4t5erk -or4t5erl -orter6lo -orter6lö -ort5ersa -ort5er6sc -or4t5erw -or4t5erz -orter6ze -or4t3e4v -or4thol -2orti -orti5en -or4t3in -or4t5obs -or4t5off -or4t5ori -or4t5ort -or2tö -or4t5res -ort5rin -ort5rol -ort5spe -orts4ti -or4t3um -4ortü -or3uhr -orum4s -or3un -oru2r -o3rus -or3u2t -o3rüc -ory1 -o2rya -orzel5 -os2a -osa3ar -o3sab -osa3er -osa3i -o4s3ami -os3art -o3s4a3s2 -3o2s3ca -2osch -osch5ar -osch5aug -o3sche -osch5lei -os2co -os2e -o4s3eie -ose3in4 -o3sen -o4s3er4k -o3s2hi -o3s2hu -o1sk -os4kal -o2ski -os4kle -os2ko -os3kol -o2sky -o3s4mog -o2s3oe -os1p -os4pac -o4spae -os4pas -o4spä -os4per -o4spf -o3sphä -os2po -os4pra -o3s2pu -oss4abb -os4s3a4c -os4s3ae -os4s5ala -os4sall -os4san -oss5and -os2sä -os4s5en4z -os4s3ep -os4s5er4b -os4s5er4f -os4s5er4r -os4s5er4z -oss5esse -os4sik -os4sim -os4s3io -os4sof -os4son -os4s3or -oss3o4v -os4spar -os4spe -os4spo -os4s3ta -os4sto -os4str -osstu5er. -os4stur -o4st. -ost5abl -ost3ac -ost5aer -o3stah -ost3ak -ost5ang -ost5ans -ost5apo -os5tarr -ost5auf -ost3a4v -4ostä -ost5einb -ost5eing -ost5einl -ost5einr -ost5einw -ost5eis -os5telz -o4ste4n -ost5engl -os6t5er6weit -ost6es. -o4s3tet -ost5eur -os2th -ost5her -os4t3i4d -os5tina -ost5ink -os4tins -ost3ir -os4t3o4b -os3toc -os4tom -os2tr -ost5rac -ost5räu -ost5rie -ost5rot -o3s4tuc -os4tug -os4tums -os4tune -ost5urk -os3tüm -osu2 -osyri3 -o3s2ze -os4zin -2oß -o2ß3el -o4ß3en4z -oßer4 -o4ß3erb -o4ß3ere -o4ß3erf -o4ß3erh -o4ß3err -o4ß5ersc -oß3is -o4t3abi -ota2c -o2t3ad -o4t3aer -o4t3aeu -o4tagg -ot3a4go -o2t3ah -ot3amb -ot3ano -ot3app -otar3a -o4t3arb -o4t3arm -o4tarz -otas3t -ot5astr -o4tatem -o5tati -ot3att -ot3auf -ot5auge -ot3aus -ot3äm -o2t3är -o2teb -o4t3eib -o4t3eic -otei4n -ote5in. -ote5ina -ote5ind -ote5ine -ote5ini -ote5ink -ote5inp -ote5inu -ote5inv -ote5inw -ote5inz -o4t3eis -ote4l3a -ote4lei -ote4ler -ote4l5in -otel3s4 -ote2m -o4t3emi -o5ten. -ote4na -o4t5ends -oten3s4 -o4t5entb -o4tentl -o3ter -otergene6 -ot3erl -oter4le -o4t3erw -o4t3es4s -ot4est -otes5tant -otfall5 -ot4h3a4c -ot4har -o3t4has -o2thi -ot3hin -ot3hir -o4t3hof -ot3hol -o2til -ot3ill -o2t3i2m -otima4 -oto3c -oto3e2 -ot3off -otop3f -oto4r5in -oto3sz -o2tö -otra5c -o3tran -ot3rec -ot3ret -ot2ri -ot3ric -ot3rin -ot3rut -ot4s3at -ot4s3el -otso2 -ot4s3ol -ot3s4pe -ot4stei -ots4tra -ot4stri -ot4stru -ot4strü -ott5akt -ot4taut -ot4t3eb -ott5er6kl -ott5er6lo -otter6lö -ott5erwä -otte4s -ot3to -ott5rei -otts4t -o3tund -ot3url -ot4zae -ot2zä -otze4d -ot4z5er4k -ot4z5er4r -oub2 -ouch3e4 -ou3chi -ou3et -ouf3f4 -ou3f2l -oug2 -ou3g4a -ou2ge -o1uh -ou3is. -ou4le. -ou4lel -ou4les -ou2li -oul3s -o1um -ou2n -o4und -oun4d5ei -o5undso -o3unf -oun2g -oure2 -ou4r3et -ourie4 -ourme4 -ourmet5 -our4ne. -ou2sc -ous4i -ousi4e -ou2st -out3el -3outf -outli4 -3outp -out3s4 -outu4 -o1ü -3o2val -ove3s2 -oviso5 -2ovo -2ow -o3wec -ow3ef -o4wer. -ow3eru -o2wh -owse2 -ox2al -2oxe -ox3ele -ox4erz -o3xi. -3o2xid -ox3l -ox2od -o2x1u -ox3v -1oxy -2oxy. -o1yo -oy3s2 -o3z2a -3o2zea -oz4en -ozen4ta -ozes4s5c -ozes4s5t -o3zi -ozialer6 -ozon3a -órd2 -ö3ba -öbe4la -öbe4li -öbe4lo -öbels4 -ö2b3le -ö2br -öb2s3 -ö1c -öch3l -ö2chr -öch6ste -öchst5ei -öchst5r -öchs4tu -ödi1 -öd2st -1ödu -ö1e -öf2fl -öf1l -ögen4s3 -ö2gl -ö2gr -ö1he -öhe2n3 -öhle2 -öh4nef -öhre2 -öh3ri -ö1hu -ö1i -öi2g -ö4ische -ö1ke -1ö2ko3 -ökos2 -ö2kr -ö2l -3öl. -öl1a2 -ölas2 -ölb2r -öl3ei -öl3em -öl3ent -ö3ler -öles4 -öl3ess -öl4f3ei -ölfen4 -öl4f5end -öl4fli -ölf3s -1öli -öl3im -öl3in -öl4kle -öl4kli -öl3le -ölle4n3 -1ölm -öl4nar -ölo2 -1ölp -2ölpe -öl4ple -1öl1s -öls2z -öl3tu -1ölu -2ölz -ölz2w -ö2mie -öm2s -ön3ec -önizi5e -ön2st -ön3sta -öntu3 -ö1nu -ö3o2ta -öoti3e -2öp -öpf3l -ör1a2 -ör2b3l -ör1c -ördens4 -ö2r3ec -ö2r3ei -ö2r3e2l -ö2r3e2m -öre2n -ö4r3ene -ö4r3ent -öre4rer -ö4rer4fa -örer6fol -ör6erfor -örer6gebn -örerin6t -ö4r3er4l -ö4r5ermü -ör3ess -ör4fli -ö1ri -ö2r3il -ö2r3im -örne2 -örner4v -ör1o2 -örs2k -ö1ru2 -ö4r3une -örungs5 -ö2sa -ö4sch3a -ö4schä -ö4schef -ö4sch5ei -ö6schene -ö6schent -ösche4r -ö6sch5er6f -ö6sch5eri -ö6sch5er6l -ö4schi -ö4sch3l -ö4sch3m -ö4schn -ö4sch3o -ö4sch3r -ö4schum -ö4sch3w -ös2e -öse3e -ö4s3ein -öse5str -ös4s5che -össe4n3 -ös2st -öst3a2 -ös3te -ös2th -öße2n3 -öß2ti -2öt -ö2t1a -ö2t3ei -ötel4n3 -öte2n3 -öt3ho -ö2t1o2 -ö2t1r -öts2 -öt2sc -öt2tr -öwe2 -öze3 -özes4 -1pa -3paa -pab2ä -pab4lu -pab4ru -pab4rü -2pabw -pach6t5er6 -pa3e2b -pae6cker -pae4d5er -2paeh -pa3ell -2paen -pa3erf -p3aerm -2paeß -2paet -pae6terb -pae6t5er6k -paet3h -pae4ti -pae4tr -paet3t -pa3fl -pa3fr -pag2 -2pa3gh -pa3ho -pa3ke -paki3 -pa3kl -pak4to -3pa3la -pa4lar -pala3t4 -3palä -3pale -pa3l4i -pal4mae -pal6m5ast -pal4mä -pal4m3o -2palt -pal4tal -pal4teb -pal4tei -pal4to -pal4t3r -pa3mi -pam3s -pa4nar -pa4n3at -pa4nau -pan3d4a -pan5de -pan5enz -pa4n3eu -p2anf -3panik -pa4nisl -pank2 -2panl -pan4nar -pan4n5e4b -pan6nene -3pa2no -pans2 -pan3sl -3pan3t4h -3panto -4pantr -3pap -papieren6 -papie6r5end -pap4pr -paps2 -papst3 -para3b -3parad -pa4rae -pa4r3af -par5akt -3param -pa4rant -3paras -pa2rä -2parb -4pare. -pa3ree -4paref -par5ein -4paren. -4parer -parer6geb -pa3reu -2parg -pargel6d -pa4r3id -par5ins -par4kae -par4k5am -par4k5au -par4kop -par4kr -park5ra -par4kue -2parm -par3ne -pa4rod -pa4roe -pa4rof -3parol -pa4ron -pa4rö -2parp2 -2parr -4parsa -4parst -4parsu -4parta -par3t4e -partei5 -parter4 -3party -2paru -pa4rue -2parv -2parw -par3z2 -pa4scha -pas5chaf -3pasi -pa3spo -pa3spr -5passa -pas6sein -passer4 -pas6serg -pass5ersa -pass5ersä -pas6s5ert -pas4sp -pas3t -pa5s4ter -pa3s4tr -2paß -pat2c -pat4e2 -4patel -pa5ten -3pati -pat3sa -4patz. -2p3auf -pa3uni -4p3ausl -4p3ausr -4p3ausz -3pazif -1pä -pä2c -päck5er -pä4d3er -2päh -2päl -2pän -2p3ärm -2päß -2pät -pä4ta -pät3e4h -pä4t3ei -pä4t5ent -pä4tep -pä4t5erb -pä4t5er4k -pät3h -pä2t3i -pä2to -pä2t3r -pät3s -pät3t -2pb -2p1c -p3ch -2pd2 -pda2 -pdi2e -1pe -pe2a -pea2r -pea2s -2p1e2b -pech3e -pech3s -4pecht -2peck -ped4a -pe5dan -pe4dri -3peel -peene3 -2p3eff -2pefr -pe4gal -pege4l3 -pe2gn -pei1 -2peic -2peie -2peig -pei4l3a -pe3im -4peis -peise5i -2pek -p2el -pe4l3ab -pe4l3ac -pe4l3ae -pe4lai -pe4l3au -pe4l3ax -pe2l3ä -pelb2 -pel3d -pe4l3e4h -pe4l5ei4c -pe4l5ein -pe4l5ei4s -4p3elem -pel5end -pel3er -peler5e -peler6sa -pe4l3et -pe4leu -pel3ex -pel3i4d -peli4n -pel4inh -pe4l5ink -pel5inn -pel5int -pe4lit -pelk2 -pe4l3ob -pe4l3oe4 -pe4l3or -pe2l3ö2 -pels2 -pel3sk -pel5ster -pe4lut -pel4zap -pena4b -pena4r -pe2nä -pend4a -pende4c -pende6l5a -pend4o -pe4n3en -penen5e -pen3f -pen5inn -peni4t -pen3k2 -penn4i -pe2n3o -pens4e -3pensi -pen3sl -pen3sm -pen5s4o5 -pen5s4p -3pensu -pen3ta -4pentw -penu2 -penz2 -3pep -pept4 -pe3ra -4perad -pe4r3ae -pe4r3ak -pe4r3as -pe4rau -pe2r3ä -pe4r3e4b -pe4r3ec -per5eig -pe4re4l -peren5e -pere3s -3perga -perik4 -pe5r4io -pe3rit -per6l5a6sc -per6l5aug -3perle -pern5en -per3o4b -per3oe -pe3ros -per4r5an -perr5eig -4persio -perwa4r -per4zeu -pes2a -pese4n3 -pes3s2 -pest5op -peten6z5a -pet2r -2pe2x -2pey -1pé -2pér -2pf. -p2fab -pf3abe -p2fad -pf3aer -pf3aeu -p2faf -pf3ai -p2f3a2k -pf3ame -pf3ans -pf3ant -p2fa2r -pf3are -p2f3as -p2fau -p2fäh -p2f3är -p2f3äu -pfdru4 -2pfe. -p2f3e2b -p2fef -p2fei -pf3eig -pfeil5s4 -pf3eim -pf3ein -pfe4l3i -pfels4 -pfe2m -pf3emi -p3fens -p2f3e2p -pfe4r3a -pfe6ran -p6f5ereig -pfer6gebn -p6ferleb -pf5erpro -pfer6prob -pfer6zeu -p3fes -pfes5ta -pfe5str -p2f3e2t -p2f3e2v -p2fex -pff2 -2pfh -p2f3i2d -pf3inf -pf3inn -p4f3ins -p4f3int -2pfk -pf2l -pf3lad -pf3lag -pf3lam -3pfleg -pf3lei -pf3les -pf3lie -pf3lin -pf3lis -pf3los -pf3lös -pf3luc -4pfluem -pf3lus -4pflüm -pf3ly -2pfm -p2f3o2b -p2foe2 -pf3oed -p2f3of -p2fom -p2fo2r -pf3orc -pf3ori -pf3orn -p2fot -pf3ott -p2fö2 -pf3öd -pf3ran -pf3rän -pf3ris -pf3rob -pf3roe -pf3ros -pf3rö -2pfs2 -pfsau4 -pf3s4e -pf3sk -pf3sl -pfspor4 -pf3str -pf3stu -pf3sz -2pft -pft4r -p2fuh -pf3uhr -p2f3um -3pfusc -2pg2 -p3gl -pgra4d -1ph -2ph. -phack3 -4p3haeh -pha3el -4phaer -pha3fl -2p3hah -pha3k -phal4t5a -phal6t5es -phaltest5 -p3hand -pha3s -pha4tr -2phau -2p3häh -3phän -2phb -2phd -phebe4 -phebel5 -2p3hei -phen3d4 -phe4n3e -phens4 -2phf -2phg -phik3a -phi4kan -phi4la -phir3 -phi4st -phit3e -2phk -ph2l -2phm -2phn -p2ho. -ph3oed -phori4e -ph2r -2phs -pht2 -4ph3the -phu2s3 -phu3t -2p1hü -3phys -2phz -1pia1 -piab2 -pia5ka -pi4a5li -pi4a3n -piap2 -pi2ar -pias2 -4pi3ce -pich3o -pich3s -p2id2 -pie3b2 -pie4gl -pi2e3i -pie4l3a4 -pie6leis -pieler6ke -pieler6kl -3piep -3pier -pie5rae -pie4reb -pie4rei -pie4rem -pieren6z -pies4st -1pig -1pil -pil4zer -pil4zi -pil4zw -pi3nal -pi3nas -pi3n4at -2pind -piner4 -pi4n5erf -2p3inf -3ping -pin4ga -pingen4 -ping5er -ping3s -3pinse -pin3sk -pins4p -pi3n4um -2pioi -pi5o4n5al -pi3os -1pip -pi4pe. -pi4pel -pi3ra -5pirate -pi3ri -3piro -pir3u -1p2is -2pi3s2k -2piso -pita6l5ent -pite2 -pitel3 -pites3 -2pi3t2h -pit2s -2pitz -pit4za -pitz4e -pi4z3in -3pizz -2pj -2p1k2 -pkur3a -1pl2 -2pl. -p3lab -2p3lad -pl4an -p3lanz -pla2q -p2lat -p2lau -pla2y -p3lea -ple3c -p2leg -p2lem -ple3n2 -4p3lere -p2leu -p2li1 -2p3lig -pli3n4a -p2lo -2p3lok -plo4min -plo3r -p2lö -2pluf -2p3lun -plu3r -plu2s3 -plus5s -2p1m2 -p3ma -p3mö -2p1n -pnoe3 -1po -2pobj -po1c -3pock -pocke4 -3pod -po5e4ben -po3ems -po3erb -po3erh -po3ets -2p3off -2poil -po2in -pok2 -3pol -po6lacke -po4l3an -po4l3au -pol4d3a -pol5ein -pol5eis -po4lep -polo3p4 -polo3s -po4lum -pol3z2 -pom4ph -p2on -2pond -poni2 -pont2 -3poo -po3o2b -poo4len -popa2 -po4p3ak -po4p3ar -po4phi -po2pi -po4p3li -pop3s2 -2popt -po3ral -po3rat -po3rau -por2ä -po3rär -pori4e -4porn. -pors2 -por4tae -por4t5ak -por4t5ei -por4tem -porter6l -por4t3h -por4t5is -5porto. -5portos -5portra -por6t5raeu -por6t5rat -por6t5rau -5porträ -por6t5räu -port5ren -por6t5ric -por4tro -port5sz -3po3ru -po3s2p -po2st -po4sta -pos4t5a4g -postasi6 -post5au -post5ein -pos4tem -pos6t5er6h -post3h -pos5til -pos4t5or -post5ra -post5re -post5ri -3pota -po4t3ar -3potä -3pote -poti4e -pot3sp -pot4t3a4 -pot4t3r -po4t3um -po3un -povi2 -po2w1 -po3wa -powe2 -1pö -3pöb -pö2bl -pö2c -pöl2s -2p1p -p2p3a2b -p2p3ac -pp3alt -p4p3ank -pp3ans -pp3app -pp3arb -p2p3at -ppe3e -pp3ein -pp3eis -pp3eit -p3pek -ppe4l3a4 -ppe4len -ppe4ler -ppeli5ne -ppe4lo -ppe2n3 -ppens4 -p4p5erze -pp3fa -ppfen4 -pp3fr -p2pfu -p2p1h -pp1l -p4plac -p2plä -p4plet -p5plik -pp3lis -pp3lo -p2p3oe2 -p2p3oh -p2p3o2p -pporter6 -p2p1ö2 -ppp2 -p2p3ra -p2p3rä -pp3rec -p4p3reg -pp3ren -p2p3ri -pp3roe -pp3rol -pp3rot -pp3rö -pp3run -pp3rüc -p2ps -pps2a -pps2p -pp3sta -pp3sto -pp3str -pp3stu -pp3sy -ppt4a -ppt4e -pp3to -p3puc -p2p3um -p2r2 -3praef -5praeg -3prael -3praem -3praep -3praes -3praev -3praez -pra3f -3prak -3pral -pra3r -3prax -3präf -3präg -3präl -3präm -3präp -prä3q -3präs -3prät -3präv -3präz -2p3re. -pre6cher6l -3pred -2pre2e -pree3i -3prei -p3reih -prei4s5c -prei6s5ei -preis5s4 -prei6st -4preiz -3prem -pr4en -pren4ga -pren4gl -3pres -pres4st -p3resu -pri2e -2p3rig -pri2l3 -3prim -pri4met -4pring -prings4 -3prinz -3prio3 -3pris -pri2t -prit3a -prit5er4 -prit5t -4pritz -3priv -1pro -3prob -probe5i -probein6 -2proc -3prod -2proe -3prof -3prog -3proj -3prop -4pross -pro3st -3prot -pro4ta -pro3te -prote5i -4prott -pro3x -3proz -2prö -2p3rub -2pruc -4pruec -3pruef -3prueg -4prueh -4prung -prun4gl -prung5sc -prun4kr -2p3rut -2prüc -3prüf -3prüg -2prüh -2prün -2ps. -p2s3ad -2psai -2psan -ps3ang -p2s3au -2psb -2psc -psch4l -2pse -ps3ein -p2seu -2psf -2psg -2psh -ps2hi2 -2psi -psi2e -psie3n4 -psie3r -ps3ind -psis3 -2psk -2psm -2pso -p2s3oe -2p1sp -ps2pe -2pss2 -pss4a -ps5sau -2pst -ps3taf -pst3au -p3stea -p3stel -ps3tes -ps2t2h -ps3tha -ps2to -p2s2tu -2psv -2psw -1p2sy -2psys -ps2ze -2pt -pt3ak -pt3alb -pt3am -p4t3ana -p2t3ar -p2t3a2t -pt3au -pt3ax -pt1ä -ptbau6mes -p3te. -p2t3e2b -pt3ec -pt3e2d -pt3ef -pt3ei -pt3ele -pte2m -pt3emi -pt3emp -4pten -p3tend -pt3eng -pten4ge -p3tens -pt3ent -pt3ep -pt3erb -pter5ei -pt5ernt -pte3ro -pt3erw -pt3erz -p3tes -p3tet. -pt3eta -pt3e4ti -pt3eun -pt3e2v -ptgene4 -p2t1h -pt3ide -pti2n3 -pt3ind -pt3inh -pt3ini -pt3int -pt3ir -pto3e -pto4mo -pto4n3a -pto4p3 -ptor3a -pto2w -pt3rol -pt3ru -pt1s2 -pt4soe -pt3uh -pt3ums -p3tung -pt3urh -pt3urk -pt3url -pt3urs -pt3urt -1pty -ptz2 -1pu -pua1 -3pub -2puc -pu2dr -2pue -pueb2 -3puef -pue4lei -pue4l3o -puf4fa -puf4fr -2p1uh -2puk -pu4ker -pu2kl -pu2k3o -4pule. -pu4l3in -pul4s5ac -pul4sp -pul4s3t -pult3a -3pulv -2pulw -2pumg -pum4pa -pumpe4 -pump3f -pum4p3h -pum4p3l -pum4p3r -2pund -pun2e -3punk -punk4tr -pun2s -2punt -pu2r3a -pure2 -pu4r3ei -pur5erk -pur5erw -purg2 -pu3ri -pu2r3o2 -3purpu -pu2se -pus2h -pu3she -pus3t -puter5s -3puts -put6schm -put6schn -3putz -1püf -2pül -pü4lei -pül3l -pü2l3ö -2pv -2pw -pwa2r -1p2y1 -py3s2 -py3t -2pz -1q -2qs -qu2 -3qual -qu4ar -que4rei -que4ri -quer3n -que4te. -que4tek -2quew -2quip -3quo -ra2ab -r3aal -ra3arb -ra4b3ar -rab4ble -4rabd -ra4b3ei -rabe4l -ra4bep -rab4er -rab5erd -rabe4s5e -2rabf -4rabg -4rabh -ra4b5itu -2rabk -4rabla -rab3lo -2rabn -ra2br -4rabre -r4abro -rab4rü -4rabs -2rabt -4rabw -3raby2 -4rabz -4r3acet -ra6cher5ei -ra6ch5erf -ra6ch5erh -ra6cherk -rach5in -4rachs -rach5sk -rach6t5er6h -rachts4 -racht5sp -rach3u -3r2acr -r2ada -ra4dac -ra4d3a4m -ra4d3an -4radap -3radar -rad5ein -4r5a4del. -rad5ende -rad5enz -rad5erf -rad5erk -ra4dern -rad5ers -rad5erz -rade3s -3radf -3radh -r4adi -3radio -3radiu -3radmu -3radp -ra2dr -rad3ri -rad3s -rad4sor -3rad3t4 -3radw -ra2e -raech4s -r2aed -2raeg -2raeh -ra3elb -ra3elf -ra3elk -ra3elr -ra3els -4raem -4raenk -r3aepf -2raer -r4aer. -ra3erk -r3aerm -3raers -rae4sc -raes5sc -rae3te -raete5i -3raetl -4raeue -4raeul -4r3aex -rafa5els -raf5ahn -rafe2 -ra4f3er -raf3la -raf3lü -r4afo -raf6t5erz -raft3s4 -rag2a -4ragg -2ragm -ra2g3n -3ragou -rag4stä -3rah. -rahle4n -3r4ahm -r3ahn -ra3ho -rah4ter -rah4t3r -rai4c -rail4l -ra3ims -ra3ind -1raj -4rakad -3rakan -3rakat -2rakk -ra3k2l -ra4kre -4rakti -rak4tin -3rakue -3rakü -2rakz -r4al. -rala2 -ral3ab -ral3ae -ral3ag -ral3ak -ra5lamp -ral5ant -ral3as -ra5laue -ra2lä -ral3b2 -5ralbea -r5alben -ral5bl -5ralbuc -ra4lel -ral5ents -ra4l5erb -ra4l5erl -ra4l5erm -ra4l5ern -ra4l5erz -2ralg -ra4l3id -ralie4n -rali5er. -rali5ers -ra4l5ind -ralin6de -ra4l5inf -ralin6ge -ral5insp -ra6l5inst -ralin6str -ralin4t -ra4lin4v -ra4lir -ralk2 -r3alk. -2rall -ral3la -r5alleg -r6allin -4rallt -r3allu -r3allü -3rally -4r3alm. -ra2lo -ral3os -3ralö -r3alp. -4ralpe -3ralq -ral3s4k -ral5s6tern -rals5tuer -ral6s5turm -ral6s5tür -ral4t5ak -r3altä -ral4tem -r4al3th -2ra2lu -ral3un -3ralü -3ralz -r3a4mei -3rami -ram4mab -ram4man -ram4mas -ram4med -ram4mei -ram6mens -rammer5 -ram6m5ere -ram6m5erk -ram6m5erw -ram4mor -ram4m3u -ram4p5le -3ramsc -4r3amt -ramt4s -ran5ade -ra4nae -r3a4nal -ra4nan -ra4n3a4r -ra4nau -r3a2nä -4ranb -ran4ce. -r4anda. -ran6d5ala -r4andas -ran6dau. -r4anden -ran4dep -ran6d5ere -ran6d5erh -rand5erke -ran6d5erm -ran6d5err -ran6d5ers -ran4dob -ran4dre -rand3s -rands4t -4ranei -r3anek -ra4nenz -r4aner -ran5erk -ra4n5erz -ra4n3eu -3ranfi -ran4gan -4rangeb -ran6gerh -rang5sk -rang5s4p -rangs4t -rang5ste -rang5str -ran4i -rani3e -r3a4nil -ra3nin -ran4kr -2ranl -2r3anm -r4anmi -r3anna -ran3ne -ra4nod -ra4n3oe -4r3a4nom -2ranp -2rans -r4ans. -ran5skr -ran4spa -r5anthr -ranti4e -ran3um -4r5anzah -ran4zar -ran4z5er -ran4zin -4ranzu -4ranzü -ran4zw -3rap. -ra4par -4rapf -ra4pher -ra4pok -ra4pos -4rappa -rap4ple -rap4pr -4r3a4pri -2r1a2q -r1ar -r2ara -ra4rab -ra4r3ak -ra4r3an -4rarb -2rarc -r2ard -4rarea -3r4arei -raren5e -rar5erh -rar3e4v -r2arf -ra4rid -rari3e -ra4r3in -ra4rist -4rarm -rar4man -ra4rom -raro4t -rarot5i -r2arp -r2aru -r2arv -4rarz -rar3zw -r2as. -ra4sae -ra2sä -ra4schl -ra4s3eb -ra4s3el -4r3as4ph -4rass. -ra2st -ras5taf -rast5ei -ras4t3o -ra5stra -3rasur -4r3asyl -2raß -r4ata -ra4tae -ra4t3ar -ra2tä -ra4t3ei -4ratemg -ra6t5en6te -4rathl -ra3ti -ra4tid -rati4e -rat5inh -r3atmo -ra4toe -r3atom -rat4or -6ratorge -4ra4tro -ra4tru -ratsche6f -rat4se -rat4stä -4ratta -4r3atti -4r3attr -ratu4r5a -2rau. -r4aub -3raub. -rau4ban -rau4bel -rau4bri -r4auc -rauch5a -rau5chig -ra3ueb -raue4n -rau4ere -raue4r5i -2rauf -4raufb -4r3auff -4raufg -4rauf3l -raufs4 -4raug2 -2rauk -r4aum -5raumab -rau4m5a4g -rau4man -rau5me. -rau6ment -rau5mes -rau4m3i -r4aun -rau4nal -rauner4 -rau6nerz -r4aup -4raur -4raus. -4rausb -3rausc -rau6scher -rau6schn -rau6sch5r -4rausd -4r3ausg -4raush -4rausl -4rausr -raus5se -4rausv -4r3ausw -r4aut. -r4aute -rau6t5ent -4rauto -rau4tra -rau4tro -rau4tru -raut5s -2rax -ra2xo -r3axt -ray3o -3ra2za -ra4zub -2räb -räch4s3 -1r2äd -2räf -rä3fr -2räg -rä4g3ei -2räh -2räm -3rän. -3räni -3räns -2räp -2räq -2r1är -r2är. -rä3ra -rärm4s -rä3ro -rä2sa -rä2sc -räs3sc -rä3te -räte3i -2rätz -rä2u -4räue -2räul -räu6scher -4räuss -2räuß -2räx -2rb -rb3abb -rb3abe -rba4del -rba4lin -rba2m -r4banf -r4b3ang -r4b3ant -r4barz -rbb2 -rbby2 -rbe5erf -rbe5eri -rbei3d4 -rb5einh -rbein6te -rbe6lach -r6belaeh -rbe4lau -r4beläh -rbe4ler -rben4ac -rbengene6 -rbe5rae -rber4ei -rber6en -rberin6t -rber5k -rb5erke -rbe5rum -rbe3sl -r4beti -r2b3im -r4b3inf -r4blan -r6b5lasser -r4b5last -rb2le -r4b3le. -r4b3leg -rb5lein -r4b3ler -r4bleu -rb4lie -rb4lin -rb4log -rb2lö -rb2o -r4bonk -rboten6e -rb2ö -rb2r -rb3ras -rb3rea -r6brechts -rb4sam -rb4s3an -rb4set -rb4stau -rbs4teu -rb4sum -rbun4da -r2büb -rby2t -2rc -r1ce -rce2n -r3che. -rch3ei -rchein6s -rche4l -r3che4n -rchi3d -r3chil -rch3l -rch5mi -rch3n -rch3ra -rch3re -rch3s2 -rch5sp -rch3t4a -rch6t5er6f -rch6t5erg -rch6t5er6r -rch6t5erw -r2ch3u -rch3w -r1ci -r2ck -r3cki -rck3s -r1cl -r1cr -2rd -r2dab -rd2ac -rda5chu -r2da2d -rdaer4 -r2daf -r2d3ak -r2d3al -rd3ans -rd5anti -rd5anza -r2d3a2q -rd3ara -r4d3arg -rd3ark -r4d3art -r4darz -rd3a4si -rd3asp -rdär2 -rde4al -r4d3ech -rdein6fl -r4d3eis -r4d3elb -r4d3elf -r4d3emp -r3den -rden3d -rd5endu -rden4es -r6d5engen -rdenk4r -rden4s5a -rd5enth -r4d5ents -rde5ono -rde4r5ei -rde6rend -rde5r4er -rde6rer6g -rder6folg -rderin6du -rderin6s -r4derit -r4d5ernt -rder5oe -rder3ö -rdes4k -rde5stel -rd5estn -r5dete -rd3eti -rdeu4ri -r2d3e2x3 -rdi2e -rdie5ne -rdien4s -rdiensta6 -rdienst5ad -rdin4a -rd3inf -r4d3ins -r4d3ira -rdo4be -rdoe2 -r2d3o2f -rdo2l -rd3oly -r3dom -rd3oss -rdo4st3 -rdö2 -rd3öst -rd3rat -rd3rau -r4drea -rd6reig -rd4reis -rd3ris -rd3rob -rd3roc -rd3rol -rd3rom -r4drose -rd3rot -rd2rö -rdrucker6 -rd3rum -rd3run -rd4s5aen -rd4s3än -rds2p -rds4to -rds2z -rdt4e -rdte3l -rdt2s -r4dueb -rdu2f -rd3ufe -r2d3ug -r2d3uk -r4d3ums -rduns3 -r2düb -1re -re4ade -rea6l5erw -re2am -rea4men -rea2s -re3as3e -re3at. -re3ats -2reä -re4ban -re4bau -3rebel -reb4si -reb4sti -rech5ar -5recherc -re6cherfa -re6ch5erzi -4rechs -rech5tei -rech6t5erh -5rechtl -5rechts -re3chu -4reckg -4recki -4reckz -4recot -4recô -3recy -4redd -4r3edel -rede3r -2redi -r3e4dik -3redip -3redu -re3el -re3eng -re3er -re4es -3refe -2reff -r3effi -3refl -3refro -rege4l5a -rege4l5ä -regelei6g -2r3egg -re3gi -re2gn -2reh -re4h3ac -re4h3ar -re4h3ei -re4hel -re4h5ene -re4h5ent -re4hep -re4herf -re4h5er4l -rehgene6 -reh3l -reh5le -reh3n -reho2 -re4h3or -3rehö -reh3ra -reh3ro -re4hue -re4h3ur -re2hü -r4ei. -3reiä -r2eib -rei4bar -reich6si -rei3de -4reidec -4r3eidg -r4eie -4reier. -4reiern -4reiers -r2eif -4reifel -2r2eig -reig4a -5reigel -r5eigene -5reigeno -r5eigensc -5reiger -5reiges -5reigew -5reigru -r4eih -rei4kab -reil4a -4reils -4r3eilt -4r5eimer -rei4mor -re3im4p -rei5nalt -4reinan -4reinb -rei5necke. -4reinf -4reing -4reink -4r3einn -4reino -4reinp -4reinr -re5in6sel -r5einsp -re3in4v -4reinw -4reinz -rei4sal -6reisant -4reisar -6r5eisenb -rei6s5ent -rei6s5erk -rei6sers -reises4 -4reisf -4reish -4reisk -4reisl -rei4so -reis5or -4reisr -rei5s6temp -4reisu -4reisv -4reisw -reit3s4 -rei4zan -rei6zell -rei6zene -reizer4 -rei6z5err -rei4zwi -4rekk -rekter6z -rek4tin -rek4top -rek4tum -rekturin6 -2rekz -2r3elb -rel4e -relea4 -4re4lef -re5lei -4r3e4lek -4r3elem -re4lend -r4elev -2r3elf -re4lim -4relit -4r3elix -rel4lar -rel4l5ei -4r3elsa -4r3elst -2r3elt -relu4d -r2em -r4em. -re4m3a4b -re4mau -2remb -rem4da -re4m3ei -re5men -re6m5erei -re4m5er4f -re4m5erl -4remig -r4empe -4r3empf -4rempo -rem4sel -rem4str -re4mul -r4en. -r4ena -rena4be -re3nad -re4nan -ren5aue -4r5endab -ren4d5an -rende4b -rende4k -ren6d5erk -ren6d5erm -4rendg -3rendi -re4n3e4b -ren3ec -ren5ende -re4nep -4r5energ -ren5erk -re4n5erl -re4n5erm -4renexe -3ren3f2 -4ren4gad -4rengag -ren4goe -4rengp -4rengs -4renhis -re4nid -re4n3im -ren5inn -4renju -renk4lo -renk4r -3renl -3renm -ren4nar -ren4n5e4b -renn5e4c -ren6nene -6rennen6n -4renng -ren4n5in -renns4 -3reno -re4nos -renrue4 -renrü4 -rensch4 -4r5ensem -ren6serg -ren6sing -ren6s5pan -ren6spat -6renstip -ren6s5tri -4r3entd -6rentera -4r3entf -5rentfo -4rentgi -4r5enthä -ren4thi -4r5enthu -4r3entl -rentmo4 -ren6tric -ren4trü -4r3ents -5rentss -4rentw -4rentz -4renue -4renumg -2renü -r2enz -ren4z5ar -ren6zerg -ren6zer6l -ren6zerm -renz5ertr -ren6zer6w -ren4z5in -ren4zut -ren4zw -4r3enzy -re3ou -3repe -4r3e4pen -re3pf -2repi -re4pis -4r3epoc -2repp -3repra -3reprä -4repren -3repu -r4er. -rer3a4b -re3rad -4r5er4bie -6rerbreg -4r3erbs -4rerdb -4r3er4dr -rere4d -re4r5eid -4r5ereig -4r3e4rek -re4r5ent -re4r3ep -re4r5ers -re4r5erw -re5res -r3erfa -4rerfah -r3erfo -4rerfol -r6erform -rer4fue -4r3er4fü -4r3ergä -6rergebn -rer4gom -rer4gri -4r3erhö -r2eri -re4r3id -re4rim -rerin4f -rerin4s -rerin6ter -rerk2 -rer4kan -5r4erki -5r4erko -5r4erkre -5r6erlaen -rer4lan -5r6erland -4r5erlau -5r4erlän -5r4erli -4r5erloe -4r3erlö -4r5ermae -4r3ermä -4r5ermit -rer6mued -rer4müd -4r3ernä -rer5ne -4r3er4ni -4r3erns -4r3ernt -rero4b -4r3erod -4r3eroe -rer3o4v -2r3er2ö -rer5reg -5rers. -rer6sätz -rer4seh -rer4sto -4r3er4su -rer4tei -re2ru -rer4wac -4r3erwä -6r5erwerb -4rerzae -4rerzä -rer5ze. -5r4erzei -rer5zen -rer5zes -5r4erzu -5r4erzü -5r4erzy -r2es -r4es. -re4s3an -re4s3au -rese4h -3reser -reser5e -3rese4t -re4s3in -re4s3of -3resol -3reson -6ressen -res6s5erf -res6s5er6m -res6s5erp -res6s5er6w -ressgene6 -res4spa -3rest. -re5start -res4tas -5reste. -rest5ein -re5stel -5resten4 -res6tent -res4tex -re4stö -rest5rau -rest5ric -3rests -res4tu -4r3etab -re4t3ak -4r3e4tap -4retat -retex4 -re2th -4rethi -re4t5rol -rett3a -ret4tan -ret4th -rett3s4 -reub2 -reu4eri -reu3g2 -2reul -re3ums -re3ung -4r3eunu -2reup -2r3eur -reu4z5ei -2reü -2reva -re4val -revil4 -rewa4r -re2wi -rewin4 -2rewo -2r1e2x1 -2rey -1ré -2r1f -rfa5chu -r2fap -r5fast -rf3aue -rf4äh -r4fense -rfen6ste -r4f3erd -r4f5er4la -rfer4le -rf4es -rff2l -rff2r -rfi4le. -rf3inf -rfin4s -r2fj -rf2l -r4fland -r4f3lic -r2f3o2b -r2f3o2f -rfolg4s -rf2r -r4f3ras -r4f5rauc -rf2sa -rf4säu -rfs3id -rf4sor -rf2su -rf2u -2rg -rg2ab -r2g3a2d -r4g3aeh -r4g3aem -r2g3ah -r2g3a2k -r4g3alb -r4g3alp -rga4me. -rg5amei -rga4mes -r5gamo -r4g3amt -rga4ner -rga4nin -r2g3ap -rg5art. -r4g3ask -r5gasse. -rga4st -rgast5a -rgast5ei -rg3auf -rg3aus -r2gäh -rgd2 -rge4ant -r4g3ech -r4g3eid -r4g5eise -rge4l5ac -r4g5elef -rgel5ein -rgel5in -rgene6rals -rgen6gel -rgens4p -r4g5entr -rgen4zw -rge4ral -rge6rang -rge4rau -rgerin6t -r4g5e4tap -rgg2 -rgi5er. -rgi4sel -r4gl. -rg2le -rg3le. -r4gled -rg3leh -rg3ler -r4gleu -r4glig -rg4loec -rg4log -rgmi2 -rgn2 -rg3nar -r2g3ni -r2gno -rg3nom -r2g3ny -r2g3oa -r2go2b -rgoe2 -r4g3oed -r3gog -rg3ora -rgö2 -r2g3öd -rg2r -rg5raen -rg3rai -r4g3ral -rg5rand -r4g3rän -r4g3reg -r4g3res -r4g3ret -rg3rüs -rgs4al -rgs2c -rg4sel -rgs4en -rgs2i -rg3s4pe -rgs4pi -rg3spr -rg5stei -rgs4tol -rgs4tra -rgs4tub -rgti2 -r1h2 -2rh. -r2ha. -2rhaa -2rhae -2rhah -2rhak -2rhal -rhal4b3 -2rhan -rhang5s -2rhar -2rhau -2rhä -r2he. -2rheb -2rhef -4rheit -2rher -rhe4r3e -2rhi -r2hod -4rhoel -2rhof -rhof3s -rho2i3 -2rhol -2rhot -2rhöl -2rhs -2rhue -rh4un -2rhü -3rhyt -ri5alei -ri5a6l5erw -ri5alim -ri3am. -ri3ams -ri3ang -ria3no -ria3p -ri4ara -ria3s4t -rib4bl -rib2l -ri3ce -rich3a -ri5chlo -rich5ska -rich4sp -richtgene6 -5richtl -richt6s5e -richt6s5o -ri4cki -ri4con -ri4dar -ri4dau -4r3i4dea -4r3idee -ri4d3e4l -ri4dent -4ridol -2ri2dy -r2ie -rie4bac -rie5blo -rie4b5ru -rie4b5rü -rie4fem -4riefm -rie4fr -rie5g4r -rieg4s -ri2e3i2 -riein3 -ri3el. -rie5la -ri3ell -ri3els -rie5n6ade -riene4 -riener5i -ri5enro -rie5rad -rier5ei. -riere4n -rie5res -ri3ern -ri4ernu -rie3sa -rie5sta -ri3eur -ri2fa -rif3an -ri2fä -ri4f3ei -ri4f3er -rif6f5en6d -rif4f5er -rif4fr -ri4fin -ri2f3o -rif3s2 -rif4t5er -1r2ig -riga3e -4r3i4gel -ri4g5ene -ri2gl -rig3la -rig3lä -ri3g2o -ri2g3r -ri1i2 -rika3s4 -ri2kä -ri4kin -ri2k3l -ri2kn -ri4kone -ri4kor -ri2k3r -ri4kun -rila4b -rilf2 -rille4n5 -2rima -ri4mag -ri4m3au -ri4me. -2rimm -2rimp -rim2s3 -r2in. -ri3na -4r3inan -4rinbe -3rinde -rin4dia -4r3indu -ri3n2e -rine3i -rinein4 -rine3s4 -2r3inf -rin5fr -5rinfre -r2ing -rin4g3a -rin4gei -6ringeni -rin4gle -rin4gor -rin4gr -ring5sc -ring5s6en -ring5sti -ring5str -2rinh -5ri5ni. -4rinit -2r3inj -rin4kal -rin6k5ent -rin4kl -rin4kn -rin4ko -rin4kri -2rinl -6r5innenm -4rinner -r5innere -r3inno -4r3innr -4rinnta -rino3 -2r3in2q -2rins -r4ins. -rin4sc -r4insen -rin4sig -rin4sol -5r4inspi -r5insti -3rinsy -rin4t5an -4rinte -rin6tent -rin6ter6g -r4intr -rin4t5re -2r3inv -rio2d -2r1ir -r2is -ri6schei -ri4schl -ri4sch5o -rischoe6 -ri4sch5ö4 -ri4schw -risen3 -3risik -3risk -ri3s4ko -4risl -rismu4 -4risol -4risom -ri2sp -ri4spi -ris3pr -ris4ser -ris4sp -ris4ste -ri4st -rista4 -rist5ab -ris6t5erf -ris6t5ers -ris3to -ris4t3r -r2it -4r3i4tal -rit5ant -2ritr -rit4ra -ritre3 -rit3s2 -ritt3a -rit4tan -rit4tau -rit4t3h -rit4to -rit4tr -ritt4s5o -3ritua -r3i4tum -rix1 -rizi4s -1rí -2rj -2r1k -rka4b5l -rk3adr -r2k3a2g -rk3all -r4kanl -rk3are -r4k3att -rkauf6st -rk3edi -r4kelem -rke3lo -rke2n -rkens4t -r3ker -rker4he -rker4le -rker4we -rk3ide -r2k3im -rk3inh -r4k3i4n3i -rk2le -rk2lu -r4knut -r4k3o4be -rko3ef -r4kord -r4k3o4ri -r2k3ou -rk2ö -rk2r -rk3ren -r4k3rom -r4krou -rk4s3al -rks3au -rk4s5cho -rk4s5ein -rk4sel -rks5ern -rk4sin -rks5inn -rk2so2 -rks3or -rks4pen -rk3sta -rkstati6 -rk4stoc -rk4stor -rk3str -rk5s4tue -rk4sum -rk4t3ak -rk4t3an -rk4t5eng -rk4t5ent -rk4t5erf -rk4t5erg -rk4t5erk -rk4t5erl -rk4t5ero -rk6t5ersc -rk4t5erw -rk4t5erz -rk4t5eta -rk4t3in -rk4t3o4b -rkt5ope -rk4torg -rk4t5o4ri -rk4t5rad -rk4t5rat -rkt5rei -rk4t3ri -rkt4sor -rk4t3um -rku2n -rk3una -rk3uni -rku4ra -rku4sa -rkus3t -2r1l -rl2a -rlage4n5 -rlags5t -r3lan -r5land -rlan4d5i -r2l3ar -rlas4t5r -r4l3au. -rl1c -rle2a -r3lec -r5lei. -r3lex -r3li -rli4ne. -rli4nee -rli4nem -rli4nes -rli2s -rloes5s -rlog5in -r3lor -rlos3t -rlös3s -rl3skr -rl3ste -rl4sto -r3l2u -rlust5a -rlus4t5r -rlz2 -2r1m -rm3ade -rma3le -rma4leb -rm3ami -r4m3amo -rma5n4e -r4m3ank -r4manl -r4manp -r4mantr -r4m3aph -r4m3a4po3 -r4marc -r4marz -r4matta -rm2ä -rm3d2 -rmd4e -r4m3e4ck -rme5erh -rme5erz -r4m3ei. -r4m5einb -r4m5einf -rme5inha -rme5inse -rme5in6to -r4m5einw -rme4ner -rmens4 -r4m5entf -r4m5ents -r4mentw -r4mentz -r4m3erb -rmere4 -rmer6fah -r4m5er4fo -r4m5erfu -r4m5erfü -rmer6geb -r4m3erh -rmer4ha -rm5erkr -r4m3erl -r4m5er4na -r4m5er4nä -r4m3erp -r4m5erse -r6m5erwei -r4m5er4ze -rm4es -rme3s4a -rme5sta -rme5str -r2mex -rmid2 -r4m3i4de -rmilch5 -rmi6nanz -rmi4nar -rmi6neng -rmin5it -rmin6terp -r2m3i2r -rmi6schl -rmi6schw -r4m3iso -rmiti4 -rmm4a -rm5man -r4m3obs -rmo3el -r4m3ofe -rmo4nau -rmon5s4 -rmo6reng -rmo4ri -rmo3st -rm1p2 -rms2k -rms2p -rms4tr -rm3ums -rm3umt -r4m3una -r4m3unf -2rn -r1na -r3na. -r2nab -rna4ben -rn3abi -rnab4l -rnab4st -r3nac -r4nacc -r4n3aes -r4naho -r2nak -r4n3ala -r4n3amt -rna2n -rn3an. -rn3ana -rn4and -rn3ani -r4n3ano -r4n3anz -rn3app -rna2r -r4nare -r4narg -r4n3ari -r4narm -rn4arr -rn5arra -r4n3asp -r4n3ast -r4n3atl -rnat4s3 -r4nauf -r4naug -r4naus -r2n3äs -rnd2 -rn3da -rndi4e -rne4bl -r4n3eff -r3n4eid -r4n5elfe -r4n3e4ma -rne2n -r5nen. -r4n5ends -r4n5ener -rneng4 -r2n3e2p -r4n5erbi -r4n5er4fa -r4n5erfo -r4n3erg -r4n5erke -r4n5er4ku -r4n5er4le -r4n3ert -r4n3er4w -r4n3erz -rne4t3a -rne4ter -rne4to -rne4t3r -rne3u4f -rn3f2 -rng2 -r2n3i2d -r2n3in -rn4i3na -r3n4ing -rnk2 -rnk4a -rnn2 -r2n3oc -r2n3oe -rnoe4d -r3noet -rn3oly -r4n3ome -rn3opf -rn3orc -rn5ost. -rn5osti -r4n3ott -rn1ö -rnö2d -r3nöt -rn3sä -rnsch4 -rnse4h5a -rn3s2k -rn3s2l -rns2p -rns2z -rn3te -rnte3e -rn3tr -r1nu -rn3ums -rn3ur -rnus3 -r1nü -r1ny -rn3z2 -1ro. -ro3adr -r5oberf -ro4berl -2robj -r4obo -ro4b3o4r -2robs -ro1c -3rock. -ro4ckel -5rockes -3rock3s -ro4cku -rodi4e -rod2r -roebel5 -roe4bl -roe2f -r3oefe -r4oeh -ro4eis -4r3oek -4roel. -ro3ela -r3oelf -r3oeli -roel4la -2roem -3roemi -ro3en. -ro3end -ro3enf -ro3enk -ro3enz -ro3erf -ro3erh -ro3erz -ro3eta -3roetu -ro3ev -ro3fa -ro4fen -4roff -3rogg -ro4h3in -r4oh3l4 -roh5nau -ro4hoe -3rohr -roh4rer -ro3ir -ro3iso -rok2l -r3okt -ro3le -ro4liv -rol4lac -rol4l5a4g -rol4lan -rol4las -rolle4 -rol4l5eb -rol4lei -rol6lerg -rol6ler6w -rollor4 -rolls4 -roll5ste -r4olo -rol3s -ro4lym -5roman. -5romane -rom4anf -4romb -ro4mei -romen5e -rom5ent -ro4mer -rom5erk -rom5ers -2romn -r4on. -ron5ant -ro3nar -ron4au -rond4a -ro4n5erb -roni4e -ro4nikg -ro4niki -ro4niku -rons4 -ron4t5an -ron4tar -ron4tel -ron6tend -ron6terf -ron6terk -ron4th -ron4t3r -ron4t3u -2rony -ro3o2f -r3oog -3room -4ropf -ro3pn -ro3p4r -rops5t -4ropt -ror3ab -ro3rae -ror3al -ror3an -ro3rau -r4o3re -4r3orga -r4oro -ro3rol -ro3rom -ro4rop -rort4st -r4oru -ro3r4ue -ror2ü -rosai4 -r4ose -ro3se. -ros5elt -3rosh -ro3s4i -3rosin -ro3sp -ros4pe -ros4s5ei -ross5enke -rosseri6 -ros6sess -rossmut6 -ros4so -ros4spa -ros4st -ros4t5er -3rosto -r3oszi -ro4ß5en4k -ro2ßi -ro2ßu -ro4t3ak -ro4t3al -ro4t5ars -rot5art -ro2tä -ro4t5en4d -ro4terl -rot5ers -ro4thu -ro4toe -rot3sa -rots4te -rot6terk -rot6terl -ro4t3um -3roule -roulet4 -3rout -r3ouv -3rowd -r4ox. -ro3xa -r4oxk -röbel3 -rö2bl -rö2du -2r1ö2f -1röh -2r1ök -r3ölf -röl4l3a -3römi -r1ör -3rösl -3rötu -2r1p -r3p2a -rpe4r3a -rpe4r3e -rpe5re. -rpe4r5in -rpe2s3 -rp2fa -rp2fä -rp2fo -rp2l -r2p3li -rp2sa -rp2s3i -rps3t -rp3t -2rq -2r1r -rra3b4r -rr3abt -rr2ac -rr3a4ck -rr2ad -rrade4 -rra4deb -rra4den -rra4der -rr3aem -r4r3akt -r3ral -rrat4st -rr5aufg -rr5aufl -rr3äm -rr1c -r3reh -rr5einr -rreise6k -rrena4 -rre4nob -rren3s4 -rren6z5er -rrere4 -r4r3er4h -r4r3er4l -r4r3esk -rre4ve -r4rezi -rrg2 -rr4hen -rr4hos -r3ri -rrid4 -rri5er. -rrie5r4e -rri5erm -rrik4 -rri4tan -rrk2 -rr4nau -rr3obs -rro3el -rroe4s3 -rron3 -rro4re -r4r3osz -rrö2s3 -rr2th -r3ru -rr2ue -rrue3b -rruecker6 -r4rur -r3r2ü -rrü3b -rrücker6 -rry1 -rrz2 -2r1s -r3sabo -r3sac -rsa5chu -r2s3a2d -r4s3aem -r2sak -rsa4lin -r4s3amt -rs3ana -r4sanb -r4sanf -r4sanga -r4sanp -r4s3ant -r4s3anz -r2sap -rs4ark -r5s4au. -rsau6erl -rsau4g -rsau4m -rsau4n -rsau5se -rs2är -rscha4o -r3sche -r4sch5e4b -rsch5einh -rscher5ei -r3schi -r3schl -r3scho -rsch5ort -r3schr -rsch4ue -r4scor -r4s3ebe -rse2e -r3seg -r4s5e4mis -r3sen -rse5n4al -rse4ne -rsenk4r -rsens4 -rser5ei -r4s3erh -r4s3er3o -r4s3erö -r4s5ersc -r4s3erw -r4s3erz -r3set -rsex4t -rs4ha4k -rs4har -r3s4ha4v -rshi2 -r3shir -r3s4hor -rsi3el -rs4il -rs4kal -rs4kel -rs4kie -rs4kis -rs4kle -r3skul -r2sky -r3slip -r3s2lu -r3s4mog -r3snob -r2so2b -rs2om -r4s5ort. -r4s5orts -rs3ost -rs2p -rs5para -r4s3phi -rs3s2 -rst5abl -r3stad -rst5ala -r4stale -r4stanp -r4st5ans -r4stant -rst3as -rs4tat -r4staus -rstauto6 -rs5täti -rs4tei -r4s5teil -rst5eindr -rst5einga -rst5emi -rs4temp -rs4terb -rst5erba -rst5er4f -rs4t5er4h -r4steri -rst5er4l -rs4tern -r4st5ero -rst5er4ö -rst5er4r -rst5er6st -rst5er4t -rst5er4w -rs4t5erz -rs3tes -rs4t5ess -rs4t5eta -r3steu -r3s4tev -rs4tew -rs2th -rst5her -r3s4tie -r4stin -rst5ing -r4stit -rs2to -r4s3tot -rs2tr -r6st5rat. -rst5rät -r4strea -r4s5tren -rst5ret -rst5rin -r4strun -rs2tu -r6stuebe -rst5ums -rs5tung -r4stübe -r2sty -rsui2 -r4sumf -rs3umw -rs4win -r2sya -rs4zin -2rt -r4t5a4ben -r4t3abo -rta4ck -rt3aeh -r5taet -rt3aff -rt3aga -rt5a6gent -rt3aka -rt3akr -rt5aktes. -r4talle -rt5allt -rt3alp -rt3ama -rt3ana -rt3ang -r4tange -r4t5anku -rt3ann -rt3ans -r4tanw -rt5anza -rt3arb -r4t3arg -rt5a4rie -r4t3arm -rt3art -rt3asp -r5tat. -rt3atl -r4t3atm -rta4to -r4t5auft -r4taut -rt3äh -rt3ärm -rt3ärz -rtät2 -r3te. -r4teben -r3ted -rte3eh -rt3eff -r3teh -rtei3f -rt3ein -r5t4e5in4d -rtei3s4 -rte4lae -r4t3el4b -rte4l5ei -r4telem -rte4l5en -rte4ler -rte4lit -rtel6lau -rtels4t -rtel5ste -r4t3emb -rtem4e -rte4nem -rte4ner -rte4net -rtenge6bi -rten3s4 -r4t5entf -rt5entl -r4t5ents -r4tentw -rteo2 -rte5rae -r6t5ereig -r4t5erfo -rt5ergu -rt5ergü -r4t5erla -rter6mit -r4t5erna -r4t5ernä -r4t5ernt -rter6öff -rter6sparn -r7t6erstel -rter4wa -r3tes -rte3s4k -rte5stat -r4t5etab -r4t3e4ti -r4teuro -rtex4tr -r3t4hag -r4t3heb -r4t3hel -rt4herr -rt3heu -r2t3hi -r4thony -rt4hum -r2t3i2d -rti2e -rti4gel -rti4g3r -rti4kl -rt5illu -r4t3i4ma -r4t3ind -r4t3inf -r3tit -r3to -r4t3oef -rto4nam -rt3ope -rto5pfl -r4topt -r4t3orc -r4t3org -rto4rie -rto3s -rt2ö -r4t3ral -rt5rams -rt5rand -rt5rati -rt3rea -rt3reb -rt5reic -rt5reif -r4t5reis -rt5reit -rtreu4 -rt5ring -r4t3ris -r4t3riv -r4t3rob -r4troh -rt5roma -r4t3ron -r4trou -rtrue4c -rtrü4c -rt5sand -rtsch4 -rts5ing -rt4sis -rtski4l -rt4s3or -rt5s4pas -rt5s4por -rts4tru -rts4tue -rtt2 -rtu5end -rt3uh -rt3umb -rt3und -r4tunf -rt3ups -rtu4rei -r4t3url -r4t3urs -rtus3e -rtu2t -r4t3ute -rtwa5re -rt2y1 -rtz2 -ru1a -ru3a2r3 -rube4 -rubens6t -ruch3a -ru4ch5er5 -ruchter6 -ruch6top -rucht5s -ruck3a -ru6ck5erho -ru6ckerhö -ruck5er6kr -ru6ckerzeu -ru4cku -rude4a -rude4l5i -ru2dr -rueb3r -5rueckf -5rueckg -5rueckz -5ruempe -ru5enz. -ru5enzs -1r2uf -3ruf. -ru2f3a -ruf5erei -ruf5erk -ru2fo -ruf4se -ruf4ter -ru4fum -ru4gae -ru2gä -ru2gr -rug4sa -ruhe3i -3ruhm -3ruin -ru3in. -ru3ins -ru3ist -2r3ulk -2r3ulm -2rum -ruma2 -ru4maa -ru4m5aer -ru4mal -ru4mar -ru4mär -r3umd -rum3ei -4rumf -ru2mi -rum4mei -r4umpe -rum4ple -r4ums. -r3umt -r3umw -2r3una -2r3unb -4run4d3a -r4unde -run4d5eb -run4d5ei -run6demo -runden5e -run4dep -run6d5erf -run6d5erk -run6d5erl -run6d5ern -run6dero -run6d5ers -run6d5ert -run6d5erw -run6d5erz -run4do -rund3r -rund3s4 -run4due -4rundv -4rundz -runei4 -4r3unem -run4en -2runf -runft3 -4run4ga -4r5ungeh -run4gei -run4gel -run6gerg -4rungl -run4gla -run4gr -rungsauto6 -2r3u2ni -run4k5er4 -2r3unl -2runm -2runn -2runr -4runse -4runsi -2runt -runte4 -2runw -ru2p3a -2rupd -ru2p3h -rupt4o -2r1ur -ru2ra -ru2r3e -r2us -ru4sam -rus4s5er -3russi -3russl -rus4spa -rus4ste -rus6t5erf -rust3o -ru4ß3er -ru4t3ab -ru4tak -ru4t3an -ru4t3ap -ru4tau -rute2 -ru4tei -ru4t3el -rut5erf -2ru4to -rut3o4f -ru2t3r -rut6scha -2ruz -ru2zw -2rüb -rü4bc -rü4bk -rü4bs -rü4ckel -3rückg -rücks4 -rück5sa -rü4fet -rü2fo -rü4h3e4l -rüher4 -rüh3la -3rümpe -rün3f -rün3z2 -rüse2 -2rv -rve3a -rven3e -r4ventz -rve3s -rv2o -2rw -rwe4gel -r5weis -r2wo. -rwolf3 -2r1x -1ry -ry2ac -ry2c -ryl3e -ry3sth -2rz -rza2b -rz4aeh -r2z3af -r2zak -r2z3al -rz3am -rza2n -rz3and -rz3ano -r4z3ant -rz3anz -rz3app -r2z3ar -r2zas -r2zat -rz3aud -rzba4c -rz3ega -r3zel -r4zelem -rze4li -rzell4a -rzen3e -rzen6gen -rzen6ten -r4zentg -r4z5entl -r4z5ents -r4zentw -r4zentz -rz3epe -r4z3er4d -r4z5er5ei -r4z5erfo -r4z5erfr -rz5erfu -rz5erfü -r4z3erg -rzer4la -r6z5erreg -rz5er4sa -r4z5ersc -r5zert -r4z3erw -r4z3er4z -r3zes -r4z3ess -rzeu4g5i -rzeugin6n -r2z3id -r4z3ind -r4z3inf -rz3inh -r4z3inj -r4z3int -rz3inv -r5zirk -rzkop4 -rz5oele -rz2of -r2z3ot -rzten4g -rzt5ric -rzu4gl -rzum2 -r4zwar -r3zwec -r4zweis -r4zwet -r4z5wir -rz4wis -r2zwo -1sa -3s2aa -saat3s -2s1ab -sab2ä -5s4abbat -3sabel -sa4ben -sab5erk -3sabet -s3abh -sa4b3i4t -sa2bl -sa4bo. -sa4b3or -sa2br -sa3b4ru -sab4rü -s3abs -2sacc -sa5chal -3s4ache -sach3o4 -sachs4 -sach3t -3sachv -s2ack -sa4cki -sa3cr -2s3ada -sa4der -2s3adm -2s3a2dr -s2aeb -3saec -sa3e2d -3saeg -4s3aehn -4s3aelt -4s5aemte -4s3aend -4s5aengs -2s3aep -2saeq -s3aerm -3s2aet -4saeugi -3saeul -4s5aeuss -4s3aeuß -sa4fe. -2s3aff -sa3f2r -s2aft -saf4to -saf4t3r -saft3s4 -3sage. -sage3i -5sagen. -4s5a4gent -2s3agg -sa4gio -2s3a2gr -sah3l -sah3n4a -s2ahs -3s2ai -sai2b -sa3ik -sai4ka -sail2 -sai2r -s1ak -sa2ka -3sa3kar -3s4aki -2sakk -3sa3kl -3sako -2s3akt -sa4kus -2sakz -3s2al. -sa3la -sa4l3a4g -sa4lal -sa4lan -sa4lar -s4alat -sal4bo -3sald -3sale -sa4leb -sa4l5erb -sa4l5erf -sa4l5erk -3s2ali -sa4l3id -sali5er. -sal5ins -sal4le. -4s3alli -s3allt -sa4lor -3s2alp -sal4s3o -sal4tau -s3altä -s4alto -3s2alz -sal4zer -sal4zw -3sam. -sa4mae -sa4map -4s3a4mat -sa2mä -4s3ambu -3s2ame -4s3a4mei -samen5s -4s5a4meri -sa4m5ess -3samf -sa4min -3samk -s2amm -5samml -2s3amn -sa4m3oe -samp4 -4s3amph -3sams -sam3sc -sam4ti -sam4to -sam4tre -sam4tri -samt5s4k -samts4t -3samu -sa4nae -4s3anal -sa4nan -s4ana3t -sa2nä -4s3anbr -san2c -5s4and. -san4dab -san4dan -san4d5ar -san6d5ers -4s5andeu -san4dob -san4dot -san4dre -san4d5ri -3s4and5s4 -sa4n3er -4sanfa -4sanfä -3sang. -3sanie -4s5anker -sank4l -4s3anku -4s3ankü -2sanl -s3anm -s4anmu -2s3a2no -2s3ans2 -s4ans. -san5sp -4santr -4santw -2sanw -s1ap -sa2pe -2sapf -sapi3 -sa4pos -2sap3p -s2apr -2s1a2q -s1ar -3sar. -s2ara -2s3arb -2sarc -3s2ard -4sarea -3sarg. -s4arge -sar4gl -s4args -4sargu -4sa4rit -2sarl -2sarm -2sarr -s2ars -2sart -sarts4 -s2a2r3u2 -s2arv -s2arw -2sarz -2s3a2s2p -4s3asso -sa3s4tu -2s3a2sy -satan4 -sa4t5ant -sat3ei -4s3a4tem -2s3atl -2s3atm -sa4tol -sa2tr -sats2 -sa3t4su -4s3atta -4s3attr -3s4atz -sat4zei -sat4zel -sat4z5en -sat4zer5 -satz5ere -5satzk -s2au. -3s4auc -3s2aue -sau4ere -sau6erste -4s3aufb -sau5fra -4s3aufs -s3aufz -s5auges -sau4gla -sau4gle -sau4glo -3sau4g3r -sau2i -2s3auk -3s4aum -3s4aun -sau3n4a3 -s2aur -4s3aura -sauri5 -saurie4 -4s3ausb -4sausd -s5ausei -4s3ausf -4s3ausg -4s3ausl -4s3ausm -4s3ausn -s3ausp -4s3ausr -4s3auss -saussen6d -s5austau -4s5austr -s3ausu -s3ausü -4s3ausw -4s3ausz -2s3auß -sauto6rin -2s3a2xi -s4axo -sa2y1 -1sä -s2äb -3s2äck -3s2äg -säge3i -s1äh -2sähn -2s3ält -2s1äm -3säma -3säml -2s3änd -4s3ängs -2s1äp -2säq -s1är -s2är. -2sära -s2ärg -2särm -2särz -s2ät -s4äuge -4säugi -3säul -3s4äur -4s3äuss -2säuß -2sb2 -s3ba -sba4ne -sb4au -sbau6m5en -sbe3n4a -sbe3ra -sber4e -sbest5op -sbi2e -s3bl -sb4li -s3bo -s3br -sbue5be -sbü3be -1sc -2sc. -2scab -2scac -2scaf -2scal -2scam -s2can -2scar -2scas -2scat -s1ce -2scei -2s4ch. -4schabf -scha5cher. -s4chad -s4cha4e -4schaep -4schaeq -3schaf -s4chal -4schame -sch5ana -4schanc -4schang -4schanl -4schans -4schant -4schanw -4schao -4schap -5schar. -4schara -4scharc -sch5arme -4scharo -s4chauf -6schaufk -6schaufn -6schausf -6schauss -s2chä -4schäp -4schäq -2schb -2schc -2schd -s4che. -4schech -4schef. -4schefs -4sch5ei. -4scheim -6scheinm -schei4s -s4chel -4schemi -4schemp -6scheng. -6schenge. -6schentk -s4cher -scherin6s -scherin6t -sch5er6kl -scher6kran -6schernt -scher6trag -5scherü -4schess -4schex -2schf -2schg -2schh -s2chi -4sch3i4d -3schie -5schil -4schimi -4schinf -4schins -4schint -4schiru -2schj -2schk -4schlad -6schlaed -4schläd -4schle. -4schleb -4schleh -6schlein -4schlek -4schloc -4schlog -sch5lor -4schlöc -4schluf -6schmaes -6schmaeß -4schmas -4schmaß -4schmäs -4schmäß -4schmed -4schmes -4schmex -4schmod -4schmoh -4schmüh -4schmy -4schn. -4schnas -4schneb -4schneg -sch4nic -4schniv -4schnum -sc4ho -4schobj -scho4e -4schoer -scho4f -sch5ofe -4schoh -4schorc -sch5ord -sch5org -sch5orte -sch5ortu -4schox -4schök -4schöl -3schön -4schör -2schp -2schq -4schrad -4schrag -4schrap -4schras -4schräu -4schre. -4schrea -4schref -4schreg -4schreh -6schreif -4schrep -4schres -4schrin -4schris -4schrol -4schron -6schrote -4schrou -4schrus -4schrut -2schs -sch3s4k -schs4p -sch5sta -sch5s4ti -sch5str -sch5stu -2scht -sch3t4a -s4chu -4schuhu -4schumg -4schunf -4schunt -3schur -sch2ü -2schv -s4chw -4schwaa -4schwah -4schwas -4schwäl -4schwec -4schweg -4schwet -4schwid -2schz -2scj -2s1cl -2scoa -2scoc -2scod -2scom -2scon -4scoup -4scous -scre2 -s2cri -2scs -2scu -2scy -2sd2 -s2daq -sdaue4 -s3de -sde2a -s3di -sdi2a -sdi2e -sdien4s -s3do -s3dr -s3ds -s3du -sdue3b -sdü3b -1se -sea2n -se3at. -seauto6r -se4ben -seb2l -4s3echo -sechst5r -4s3echt -2s3eck -sed2 -sede3 -sedes4 -sedi4e -se4dik -se4dit -3see -se4e3i4g -see3na -se3end -see5n4e -4seeng -se3ent -se3enz -see3p2 -see3ra -se3erb -se3erd -se3erf -seer4i -se3erk -se3ern -see3ro -se3err -se3ers -se3ert -se3erz -see3sk -sefen4 -2s3eff -sef2l -4s3e4gal -segelei6 -3se2gl -se3gla -se3glä -se3glo -se2gn -s3e2go -seg2r -seh5abe -se4h3a4g -se4h3ak -se4h5ang -se4hap -se4heng -se6hense -sehen4t -seh5ente -se4hep -se4h5erf -se4h5er4k -se4h5erl -seh5ing -seh5int -seh3l -seh3na -seh3ra -seh3ri -seh3ro -seh3s -seh3t -se4h3ur -se4hüb -2s3ei. -2s3eic -4s3eid. -4s5eidec -4s5eides -4s3eids -4s5eifer -2s3eig -s4eih -s2eil -sei4l3a -sei4lei -4s3eime -se3imp -5s4ein. -4s3einb -4s3ein4e -seine5i -seinen4 -4s5einfl -se5in4fo -4s5einfu -4s5einfü -4s3eing -sein4ha -4s5einhe -4s3eini -4s3eink -4s3einm -s3einn -se5inne -4s3einr -s5einsat -s5einsät -4s5einsc -s5einspa -sein6stit -sein6str -s5einstu -se5in6ten -sein6ter -4s5eintr -4s3einw -4s3einz -2s3eis -3s4eism -3s2eit -2sekz -s2el. -se4l3ac -sela4n -se4l5ano -se4lanz -se4l3at -se4l3au -s2elb -4selbis -selbst5e -selbst5r -sel3d4 -sel3e4c -se4lef -4s3e4leg -se4l5eig -6selektr -4seleme -selen5e -se4l5ent -sel5erd -se4l5ere -seler5ei -se6l5erfi -se4l5erk -se4l5erl -sel5erm -4s3elf. -4s3elfm -sel5ind -sel5inf -sel5inn -se6l5inst -4s3elix -selk2 -3s2eln -se4l3ob -3s2els -sel5skl -sel5sko -sel5stem -sel5sti -sel3sz -seltex4 -se2lü -selz2 -4s3e4mig -2s3emp -s4en. -sena4b -se4na4g -se4nah -sena4r -sen3au -se5n4au. -se2nä -5sendel -senderin6f -sen5eck -se5neg -senei4l -se4nel -sen5ende -senen5e -se4nep -se6n5erei -se4nerf -4senerg -se4n5erk -se4n5ers -se4nerw -se4ness -se4n3e4v -s2enf -sen5fun -seng4le -4sengp -senhaus6 -3s2eni -se4n3im -se4n3in -6s5en6keli -sen6kero -sen4k5ni -senks4 -sen3ob -se4noe -se4n5ott -se4noz -s4ensa -sensch4 -sen4s5e4h -sen6s5ers -sen4s5i4d -sen4s5in -sen5s6lan -3s4enso -senst4 -sens6temp -4s3entd -sen6tenr -4s3entf -4s3entg -4sentla -4s3entn -sen6t5ric -4s3ents -4sentw -4sentz -se4n3un -sen4zun -sen4zym -seo2b -se3of -se3oh -seo2r -se4pen -se3p2f -s3epis -se4pit -s2er. -se3ra. -ser5adl -ser3af -ser5age -se3rai -ser3a4k -ser3al -ser3am -se5rand -ser5anz -5serat -ser5äus -serb2 -s5erbe. -se4r3e4b -ser5eck -serei4f -6s5ereign -se4rein -serei5s6t -ser5eli -sere4m -ser5emi -3s4eren -ser5end -se4r5en5e -se4r5enk -se4r5ens -ser5enth -se4r5erb -serer5e -se4r5erh -ser5ersc -se4r5erw -4s3ergä -6sergebn -ser5ha -4s3er4hi -4s3erhö -se4r3id -3serie -ser5inner -serk2 -6s5erkran -4s5erläs -4s5er4le. -4s5er4len -4s5ermah -ser3na -4s5ernte -ser3o4b -ser3oc -se3rod -4s5eroef -se5roel -4s3eros -4s3erot -4s3eröf -se3röl -s4ers. -sersau4 -4s5erseh -ser6sehn -sers4k -ser4um. -se5rumm -3s4erv -se2ry -3ses. -sesch4 -4s3e4sel -se5skan -4s5eskap -se3so -ses2p -4s3essa -s4essel -sest2 -se3sta -se5stec -se5stei -se5stel -se5stemp -se3sti -se3sto -se3stu -3s2et. -4s3e4tap -4s3e4thi -4s3e4tho -4s3e4tik -set3sp -se3tun -3setz -set4zei -seu2f -se3ufe -se3uh -2s3eul -se3um2 -seums3 -se3uni -2s3eup -seu2t -se3ute -se3uto -4s3e4van -s1ex -3s4ex. -4sex4am -se4xen -2s3exk -s2exo -2sex2p -sext4an -sex5ti -4sextr -sext6ra -3s4exu -2sex2z -1sé -2s1f2 -s3fa -sfal6l5er -s3fe -sfen2 -sfi4le. -sfin4s -s3fl -sf4la -s3fo -s3fö -2sg2 -s3ga -sgangs5 -sga2t -s3gä -s3ge -sgeha4 -sge6habe -sge3lu -sge3n4a -s3gi -s3gm -s3go -s3gö -s3gr -2s1h -3s2ha. -s3hae -s4ha4ke. -shal4li -shalt4s5 -3s4hamp -s4has. -s3hä -3shi. -sh4ia -3s2hib -3s2hik -3s4hip -3s4hirt -sh1l -sh1n -sh2o -s3hoc -s3hoe -s4hoe. -s3hof -3s4hoo -3s2ho2p3 -sho4re -s4horts -3s4ho2w3 -s3hö -sh2r -sh4re -sh2u -sh2ü -1si -si5ach. -si3am. -si3as -sia4s3e -2siat -sib2 -3sic -sichts5te -sicker5e -4s3i4dea -4s3idee -4s3ideo -si3der -2si2dy -s2ie -sie4b3a -siebens6 -sie4b3r -sie4bu -si2ed -sie4gl -sie4g3r -sie5r4e -sies2 -si1f2 -s2ig -si2g3a2 -si2gä -si4g3ei -s3i4gel -si2gl -sig2n -sig4nu -si2g3r -sigs4e -sigs4p -sig5sto -si4k3ab -si4kad -si4k3ae -si4k3a4g -si4k3ak -si4kalb -si4kana -si4kanb -si4k5ans -si6kanth -si4kanw -si4k3ar -sik5asp -sik5ass -si2k3ä -si4k3el -si4k5ens -si4k5ent -sikerbe6 -si6k5erei -sik5erfo -si6k5erke -si4k5er4z -si2k3i2 -sikin3 -si2k3l -sik5le -si2k3n -siko3n -siko3s -si2kö -si2k3r -sik3s -sikt2 -si2ku -si3kum -si2kü -s2il -3silb -silber5e -sil4br -sil3c -3sili -2s3ill -si4mag -2s3imb -2s3imm -4s3impf -4simpu -sim4st -3s4imu -si3n2a -si5nau -4s3indu -sines4 -4s3infe -4s5infor -4s3infr -sin4gae -sin4gan -sin4gar -sin5g6le. -sin4glo -sin4g3r -sings4 -sing5sa -sing5sc -sing5so -2s3inh -sini5er. -4s3i4n3it -s2ink -3sinn. -s5innenm -4s5inner4 -sin6n5erf -3sinnl -4s3inno -3sinns -2s3inq -2s3ins -3s4ins. -s4into -2s3inv -2s3inz -si4onm -si3os -3sip -s2ir -2s3irr -si4sam -3sisc -si4scha -si4schl -si4schn -si4schu -si4schw -si2s3e2 -sisen3 -sis3i -si4sin -si4sis -sismu4 -s3i2so -sis3or -siss2 -3s4i2st -sis3te -si2su -3s2it -si4tal -si4tau -si4tra -si4t3re -sit3s -sit4zel -sit6z5erm -siv3a -si4val -si4van -sive3 -si4v5er4f -si4v5er4t -si4vin -siv3o2 -si4vor -si2vr -1sí -2sj -s3jo -s1k2 -2sk. -2skab -2skae -ska3la -4skalk -2skam -5skanda -2skap -2s3kar -2s3kas -ska4s3p -4skata -ska4te. -4skateg -s4kater -ska4tes -ska4to -ska4t3r -2skau -2skä -2skb -2skei -ske4l3i -2ske2n -sken3e -3s2kep -ske3ra -s4ketc -s3kh -3s2ki. -s2kia -3s2kif -s2kig -3s2kik -2skin -s2kip -4skirc -3s4kire -3s4kis. -3skiv -3skiz -2skk -s3klam -4sklas -s4klav -5sklave -2sklu -2skn -4skoe -sko3ef -2skoh -2skol -3s4koli -4s3kom -4s3kon -3skop. -s4kopa -sko4par -s4kope -s4ko4pl -s4ko4pr -2skow -2skö -2skr -s3kra -skraf4 -s3kre -3s4krib -s4krip -s3kro -2sk3s -sk3t -skto2 -2sku -s3kue -3skulp -s3kun -s3kur -2s3kü -2skv -2s1l2 -s3lab -s3lae -s3lag -3slal -sla4ma -sland5a -sland6s5o -slap3 -4s3lar -3s4lash -sla3sp -s3l4au -sla4ve -s3lä -sl3b -s3le -sle4gas -slers4 -s3lic -s2lip -sl4ob -slo5be -s3loc -3s4loga -3s2low -s3lö -sl4um -2s1m2 -s3ma -smab2 -sma3la -s4mall -sm4an -sm4ar -smar4k5n -sma3sp -smen4t5i -s3mi -smi2e -smi2t3 -smitte4 -3s2mok -s3mu -smusiker6f -smut4ti -s3mü -2s1n -sn2a -snab2 -s4nack -snae4s3 -snä2s3 -s3n2e -s3ni -sni5er. -sni5ers -sn4ob -snus2 -1so -2s3oas -s1ob -s2oba -so4bel -4so4ber -2sobj -so2b3l -2sobm -4so4boe -so2br -3s2o1c -s4oda -sodi2 -so2do -so3e2b -2s3oe2d -soe2f -2s3oek -2s3oes -so3et -so4fen -s4off. -4s3offi -3s2oft -sog2l -s2oh -2s3o2he -3s4ohl -sohle4 -sohlen5 -4s3ohng -2s3ohr -2s3okt -3sol. -so3la -3solan -so4l3au -sol3c -3sold -sol4d5er -3sole -so4l3ei -4s3o4lig -sol4l3a -sol4l5er -3solm -so3lo -solo3p -solo3s -3sols -so3lu -2s3o2ly -2s3omb -some4n3 -3somm -s2on -son6anz -sonan6z5ei -so4nau -3sond -sone2 -so4n3eh -so4ner -son4ga -3sonn -son5ori -sons4a -son4s3o -so1o -2s3o2pe -2s3opf -3s4opr -sop3s2 -2sopt -4s3orat -2s3ord -sore2 -so4rei -so4r3el -so4rem -so4rer -4s3orga -sorgi3 -4s3o4rie -so2r3o2 -sor4t5ae -sor4tau -sor4t3ä -4s5ortes -4s3orth -5sorti -sort4st -sose4 -2s3osm -so3s2p -3s2oss -so3sta -s3osth -2s3osz -3so3ß -soun4d3 -3sour -2s3out -2s1ox -s2oz -2s3o2ze -sö2c -2s1ö2d -s1ö2f -s3öffn -1söh -2s1ök -2s1öl -2s1ös -sp2 -2sp. -4spaa -3s4pace -4spack -3spaen -4spaer -3spaet -s4paga -4spage -spa4ge. -4spago -2spak -4spala -4spalä -4spale -s4pali -4spalm -5spalt -spal4t5a -spa2m -s2p4an -5spannu -4spano -5spant. -4spanth -4s3panz -2spap -sp4ar -4spara -spa4rap -s4pard -3sparr -5s6parten -4spartn -4sparty -4spassi -3spaß -3s4pat. -5spatel -4spati -4spatr -3s4pats -2spau -s2paz -5spazie -2späd -3spän -2spär -3spät -2spc -2spe. -s2pea -3s4pedi -s2pee -2speg -3speic -4spein -3speis -s4pek -4spensi -spen6ster -4s3perf -3sperg -s3peri -4sperle -s3pern -s4perr -sper6r5ei -4s3pers -4speru -4sperü -3s4pese -2s3pet -3spex -3s2pez -2s3pf -5sphaer -s4phal -s3phan -4s3phas -s2phä -4sphän -3sphär -3s4phin -2sphy -1spi -2spia -3s2pi4e -4spiep -4spier -spier4r -2spig -spi2k -2s3pil -4sping -3s4pinn -2spip -s2pir -4spiro -2spis -2s3pla -2s3plä -4sple -sp3le. -3s2pli -2s3plu -2s3pn -2spod -2spog -3s2poh -3spoi -2s3pok -4spol -3s2pom -3s4pons -2spoo -2spop -spor6tag -spor4tu -4sporz -2s3pos -4spote -4spoti -s4pots -s2pö -4spr. -3sprac -4spraeg -4sprael -4spraem -s4praen -4spraep -4spraes -4spraev -4spraez -4sprak -3s4pran -4sprax -3spray -s3präd -4spräg -4spräl -4spräm -s4prän -4spräp -4spräs -4spräv -4spräz -3s4prec -4spred -4spreis -5spreiz -3s4pren -4spres -s4pric -5spring -5sprint -4sprinz -4spriv -4sprob -4sprod -4sprof -4sprog -4sproj -4sprom -4sprop -5spross -4sprot -4sprov -4sproz -3spruc -5spruec -4spruef -3sprüc -4sprüf -3sprüh -3sprün -2s3ps -s4ps. -sp3t -4spub -2spuf -3spuk -4spulv -3spund -4spunk -2spup -3s2pur -spu4rer -4spurpu -2sput -1spü -s2pür -2s3py -2sq -s3qu -2s1r2 -s3ra -srat4s5t -s3rä -s3re -sre5cha -s3rh -sroe4s -sroes5c -sro3tu -srö2s -srös3c -s3ru -sruecker6 -s3rü -srücker6 -2s1s -s3s4aba -ssa5ble -ssa3bo -s4sach. -s2s3aj -ss5alar -s4s3amb -s5samm -s4s3amp -s5san. -s4sanf -s4s3ang -s4sanh -s4sanp -s4s3anz -ssa4ro -ssa2s -s4s3asi -s4s3ass -ssa3s4t -ss3att -ss3aud -s4s3au4f -ssau4n -ssau4r -ssau5ri -s3sät -s3s2äu -ss2ca -s2sce -ssch2 -s6scheid. -sscheiner6 -ss4chem -s2sco -sse3a -s4s5eben -sse4ck -sse3ec -s3seh -ss3eib -sse5in4d -sse5inha -ss5einl -sse5inte -s4sekä -s4selem -sse6l5end -sse4l5oe -sse4lö -s3sen -s4s5endl -sse4n5e4b -sse6nend -sse6nene -sse6n5ent -sse6n5er5i -sseng4 -ssengene6 -sse4nid -ssenk4 -ssen6kel. -ssen6keln -ssen6kels -sse4no -s6sen6sem -ssen6te. -sser5a4d -sser5as -sse6r5att -sse6r5ent -sse4rep -sser6fahru -sser6gebn -s6s5erker -ss5ernt -sser4öf -sses4sa -sses6sen -s4s5estr -ssex2 -s4sexa -ssgene6ri -s3s4hut -ssicht6s -s3sie -ssi3es -ssi4ken -ss3i4ko -s4s3imp -s4s3ind -ssing5s -s4s3int -s4s3isr -s2sk -s5skala -ss3li -s4s3oef -ss3oel -ss3ofe -ss3off -ssoi2 -s4sonk -sso5ral -s5sorde -s4s5orgi -s2söf -ss4pec -s2spl -s4spro -ssquet4 -ss3s2 -sssau4 -ss5spr -s4st. -sst2a -s3stab -s5stad -s3stae -s3stah -ss3tak -s3stan -ss5tank -s5s4tap -s3stau -ss5tauc -s3stä -ss5täti -s5stein -ss4teng -s3s4tep -ss4tern -ss3tet -s3steu -ss5teur -ss3tho -s3stie -ss4tin -ss4toer -s3stop -s3stöp -s5stör -s3str -ss4tras -ss4trat -s4strie -sstro6ma -s3stu -s3stü -s2sty -s4sumf -s4sumg -s4sumk -s4sumr -ss5umsc -s4s5umse -s4suns -s4sunw -s3sup -s4s3urk -s2sz -2st. -s2ta -s3ta. -3staa -3stab. -4stabb -4stabd -4stabel -5stabes -4stabfl -4stabh -3stabi -4stabit -4stabl -sta4bli -st3abo -4stabr -4stabt -4stabu -4stabz -st2ac -4stada -3stadi -4stadj -4stadm -4stadr -3stadt -3s4taeb -4staebt -3staed -4staeg -4staehn -3staem -6staenz -4staep -st5aerm -5staett -4staeus -4staeuß -4stafel -5stafet -st3afr -2stag -s4tagg -s4tagl -3s4tagr -stai2 -2stak -st3aka -3staks -s3tal. -4stala -sta5lak -st3alg -stalle4 -stal6leb -stall6ta -st3alm -st3alp -st3alr -st3alv -4stame -st3a4mi -3stamm -stam4ma -3stan. -4st3ana -4stanb -3stand -3stane -4stanf -5stange. -6stangeb -6stangek -st5angel -st5anges -4stangr -4stanl -4st3anm -4stann -st3ano -3stanp -st5ansa -st5ansä -4stanw -st3a2o -3stapl -stapo3 -stapos6 -4st3app -3s4tar. -6starbei -4stari -4starn -3s4tars -star4ta -4starti -4starz -2stas -3stase -stas4tr -4statb -5statis -st3atl -st3ato -4staubh -4stauf -st5aufb -st5aufk -st5aufs -st5auft -4s3taug -3staup -5s4taur -4stausc -stau6scha -4stausg -4stauss -staussen6 -4stauto -3stauu -2stav -2s3tax -1s2tä -s4täb -3städ -2s3täf -2stäg -4stähn -4stäle -4stält -s4tänd -4stänz -2stäp -st3ärm -st3ärz -4stäte -3stätt -4stäus -4stäuß -2stb -2stc -st3ch -2std -4s3te. -3ste4ak -4steam -4stean -5s4teche -ste4chi -4stechn -ste2d -st3edi -5stedt -2stee -s2teg -3steg. -3stege -ste4gra -ste4gre -3stegs -3steh -4stehr -4s3teic -4s4t3eid -3s4teif -st5eifr -s4teig -5steiga -stei4gr -5steigu -steil5a -4steilc -4steils -4steilu -6steindr -ste5i4sc -s4t5eise -s4t3eiw -2stek -s2tel -4stel. -4stela -4stelb -4stelef -4steleg -s5telei -st5elek -4stelem -4steleo -4stelep -ste4ler4 -st5elev -s3telf -stel6laen -stel6l5än -4stelm -4steln -stel5oe -ste4lö -4stels -4stem. -4stema -ste4m5ac -ste4mei -ste6m5ent -ste4mer -4stemi -ste4min -6stemper -4stempf -ste4mu -2sten -stende6ta -s4t5ends -stenei6l -stens4 -st5entd -s4t5entf -s4t5entg -s4t5entl -s4tents -s4tentw -sten6zerf -s4tepi -4stepr -4s5ter. -4stera -ster5al -4sterba -s6terben -st5erbrin -4sterd -4stere. -ste4rec -4sterei -ster5eif -ste4rel -4sterem -4s5teren -5stereo -ste4rep -4sterer -4steres -4sterf -st5erfin -4sterg -ster6gie -4sterh -s4terit -4ster5k -4sterl -4sterm -st6ern. -4sterna -4sterö -4sterp -4sterr -4sters -4stert -4steru -4sterw -4sterz -ster4za -ster4zä -s4t5er4zo -2stes -stese4 -stes5er -stes6se. -ste4st -stes5ta -s4testn -stes5tr -2stet -ste4tab -ste4tag -3s4teti -3s4tett -s2teu -3steue -4steul -st5eura -2stev -st3eva -st3e4vi -2stex -s4texa -s4t5extr -2stf -2stg -2sth -st3heb -st3hee -st3hel -st5herd -s5therm -s4theu -st3hex -s2t3hi -s2t3ho -s2thu -2stia -2stib -s2tic -3stich -6sticket -2stid -4stie. -4stief. -4stiefl -s4tieh -sti4el -stie6lei -4stien -6stiere. -3stieß -3s2tif -2stig -st5i4gel -2s3tik -sti4kam -sti4kan -sti4kar -sti4kas -sti6k5ere -sti4kl -sti4kr -s2til -3stil. -sti4l3a -s3tild -3stile -3stils -s2tim -3stimm -4stimp -sti3na -st3inb -s4t3ind -4s4t3inf -s4t3inh -s4tink -st5inka -5stinke -st5inko -st3ins -s4tinsz -4s4t3int -2stio -4stip. -s4tipe -4stipps -4stips -s2ti2r -st3iri -3stirn -4st3iro -4stis -sti6scha -st3iso -st3isr -st3ita -4stite -4stiti -5stitui -2stiv -stiva4 -stiz3a -2stj -2stk -st5kan -2stl -2stm -2stn -4sto. -4st3o4bl -4stocht -2stod -4stoech -4s4t3oef -4stoen -4stoepf -3stoer -s4toeru -s4toes -3stoeß -4stoet -3stof -6stoffiz -s2toh -4stoil -s4tois -3stoll -4ston. -sto4ne. -4stonn -4stons -4stoo -s4t3ope -4stopf. -4s5topo -3stopp -4stor. -s4torc -4stord -4stores -4storf -4storg -4stori -s4torn -4storp -4stors -4stort -4storw -s4tory -s2tos -3stoss -stost5r -st3osz -3stoß -4stota -4s3tote -s4tott -4stou -1s2tö -4stöch -3stöck -2stöf -2s3t2öl -2stön -4stöpf -2stöt -2stp -2stq -4strad -4s5traec -4straeg -6straeum -stra4f5a -s4trafe -s4traft -4strag -4strahi -5st6rahl -4strai -4strak -4stral -stra5la -6strangi -5strangs -s5trank -4strans -st5rast -3straß -4straum -4s3träc -4sträg -3strän -3sträs -3sträß -4sträum -4stre. -4strech -4stref -4streg -4s5treib -5streif -s4t5reis -4streno -4strep -4streso -s4tress -st5rest -4stret -6streuem -6streues -4streuh -5s4treuu -4strez -4strib -4strieb -strien4 -strie5s4 -4strig -4strim -4strink -4s5trio -5s4tripp -4stris -4striu -st2ro -4stroc -4stroep -3s4troh -4stroll -s4troma -5s4trome -5stromk -4stropf -4stros -s4t5rose -4strou -4ströp -s3trös -4strua -3s4trud -6struemm -4struh -3struk -4strup -4strümm -2st3s2 -sts4a -stsau4 -st4sb -stsch4 -sts4i -2st3t2 -4s5tuch -3stud -4s5tuech -5s4tueck -3stueh -stu4el -3s4tuen -4stuer. -4stuere -5stuetz -3stuf -st3uga -3stuh -s2t3uk -4stumf -4stumo -4stums -stum4st -4stun. -st3unb -3stund -4stune -4s4tunf -4stung -4stunn -4stuns -4stunt -4s3turb -stu5re -sturi3 -s4turk -st3url -stur6mei -4s5turn -4sturs -4st3urt -3s4turz -2s3tus -3s2tut -1s2tü -4stübu -4stüch -3stüh -4stür. -4stürc -4stüre -4stürg -4stürs -4stürw -3stürz -3stüt -4stütc -2stv -2stw -stwa5re -s2tya -3s2tyl -2stz2 -1su -su3al. -su3an -su2b3 -suba2 -4subi -subs2 -3suc -such3o -such6s5ende -such6spe -such6ters -sucht4s -3sud -2sueb -3suec -sue2d3 -sueden4 -3suef -su3ell -3suen -3suep -3sues -3sueß -su3ez -su2f -2s3ufe -su3fi -sugge4s -2s1uh -3suhl -su3is -su3it. -su2k -s2ul -su3la -su3li -3sulta -su4m3an -su4mar -su4mau -su4mei -su4m3el -sument4 -su6m5ents -su6mentw -su4m3et -4sumfa -4sumfe -su4m3id -su4m3in -4sumla -3s4umm -3s2ump -4s3ums4t -su2n -2s3una -sunder4 -sun6d5erh -sun6d5ess -sunds4 -2s3une -2s3unf -6s5ungena -4s3ungl -2s3uni -2s3unl -2sunm -4sunsi -2sunt -2sunv -s4unwa -4sunwe -2supd -3sup3p -su4ran -su4rau -su4r3ei -su4r3er -3s4urf -surfs4 -sur5ins -4s3urku -su2r3o2 -s5urspr -s3urw -sus3e -su3shi -sus3i -s1ut -su2te -su2to -1sü -2süb -sü4be -3sü2d1 -süden4 -3sün -s2üs -2sv2 -s3ve -svie2 -s3vo -svoran6s -2sw -s3we -swe6gers -sweg3s -sweh2 -s3wi -4s5wink -s4wist -1sy -s1ya2 -sy3ab -syl1 -3syli -sy2lo -sy2lu -sym3 -sy2n3 -3synd -2syo -3sys -sy3sto -sz2 -2s3za -2s3zä -2sz4ei -szeits4 -2szel -3s4zena -s4zend -s4ze3n4e -s4zeni -4szent -2szer -sze3ra -2szet -2szeu -3s2zew -2szie -4szins -s4zint -3s2zis -2szo -s3zs -sz3ta -2s3zu -sz4un -szu3ni -2s3zü -2szw -2szy -1ß -2ß1a2 -ß3ac -2ß1ä -2ßb2 -ßbal4li -ßbau4c -ßbrau4 -2ß1c -2ßd2 -ße2be -2ß1ec -2ß1ef -2ß3e2g -2ß1ei -2ß1ek -ße2l3a2 -4ß3e4lek -ßels2 -ßel3sk -2ß3emp -ß4en. -ße2na2 -ßene2 -4ß5energ -ßeng2 -ße2ni -ßenk2 -ße2n3o2 -ßen3s4k -ßens4l -ßens4p -ßens4t -ßen4te. -4ß3entf -4ß3entl -4ß3ents -4ßentw -4ßentz -2ß1e2p -ßer3a -ßerei5s4 -4ß5er4geb -ßerk2 -4ß3er4ke -ßer4le -ßermen4 -4ß3ersa -ßer4seh -ßers4k -2ß3er2z -2ß3es4s -4ß3estr -2ß1ex -2ß1f2 -2ßg2 -ß3gm -2ß1h -ßi2g3a2 -ßigs4e -2ß1i2k -2ß1il -2ß1im -2ß1in -2ß1i2o -2ßj -2ß1k2 -ß3kh -2ß1l -ßlers4 -ßli1 -2ß1m -ßmau2 -ßmut2 -2ß1n -2ß1o2 -ßoe2 -ßos2 -2ß1ö2 -2ß1p2 -2ßq -2ß1r -ßroe4s3 -ßrö2s3 -ß3ru -2ß1s2 -ßsau2 -ßsch2 -ßst2 -2ßt -ß2t3in -ßt2ö -ßtu5er. -ßtu5ers -2ß1ue -2ß1um -ßums2 -2ß3unf -2ßunt -2ßunv -2ß1ü -2ßv -ßvie2 -2ßw -2ßz2 -ß3zs -1t -3taan -ta3ar -4taat -t2aba -ta4b3an -2t3abb -2tabd -3tabel -2tabf -2tabg -2tabh -4t3a4b3i4t -2t3abk -4t3abla -4t3a4blä -5tablet -4t3ablö -2tabm -t3abme -2tabn -tab5ort -2ta2br -2tabs -2t3abt -3tabu -ta4bue -tabu5s -ta2bü -2tabw -taby2 -2tabz -2t1ac -t2a3ce -t4ache -ta3chi -ta3cho -tach4r -4tachs -ta3chu -3tacl -3tacu -2t3add -4t3a4der -tadi3o4 -t3adm -t3a4dol -2t3a2dr -ta3d2s -4tadt -tad4t3a4 -2tadv -ta2e -t3aebt -tae2d -ta3ede -ta3edr -3t2aef -t3aega -3taegi -3taegl -4taegy -2taeh -t3aehn -ta3ei -3taele -4t3aelt -2tae2m -t3aemt -t5aenderu -4taeng -ta3ent -3taenz -2t3aep -2taeq -tae6rers -tae4rob -t2aet -3taete -ta3eth -taet4s5t -t3aetz -4t3aeug -5taeusc -t5aeuss -4t3aeuß -2t3aex -3taf. -3tafa -3tafel -2taff -t3afg -ta3flu -ta3fra -3t2ag -t4ag. -ta2g3a2 -ta4g3ei -6tagent. -tage4si -t3agg -ta4git -ta5gla -ta4glo -4tagnat -tagse4 -tags5ta -2tahl -tah4ler -tah4li -tahls4t -tahl5ste -tah4l3u -3t2ai -ta3i2k -tai2l3 -tai4ne. -ta3ins -ta3ir. -ta2ka -2takk -ta3kla -2t3akq -ta4kro -tak4tel -tak6t5erf -tak6t5er6z -5taktik -tak4t5in -4t5aktiv -tak4t3o4 -tak4t3r -tak4tum -2takz -t2al. -tal3a4b -tal3ac -ta3lad -ta3lag -tal3a4s -t2alb -t3alb. -4t5alben -t3albk -3talbr -ta4leb -talem4 -tal5emb -ta4l5emi -3talen -ta4l5en4d -ta4l5eng -ta4l5en4s -talent6n -3taler -ta4l5er4f -ta4l5erg -tal5erha -tal5ersa -ta4l5ert -ta4let -ta4leu -ta4lev -tal4ga -t3algo -3talia -tali6ene -3talis -3talke -tal4l5ac -tal4lan -tal6l5auf -tal6l5ein -tal6l5erh -tal6lerm -tall3s4 -t5allta -4t3alm. -talmi4 -3talo -ta4lob -ta4lop -ta4l3o4r -ta3lö -talp2 -3t2als -tal4sae -4talter -talt4s -3t4am. -t4amar -t3a4mei -3tamen -4t3a4mer -5ta5mix -tam4mei -tam6mense -tam4m5er -tam4mi -tam4m3o -2t3a2mö -t3ampl -t3ampu -3t4ams -2t3amt -3tamu -ta2na -4t3anal -ta2nä -t4anbe -4tanbl -3tanbu -3tan2c -tan4dan -tan6d5erk -tan4dre -tan4d5ri -tand4s5a -tand6sen -tand4sk -tand4st -ta5neie -tan5erfa -2tanf -4tangab -tan4gar -5tango. -5tangos -t3ang4r -tan4gra -4tanhä -t4ani -t4anj -tan4kan -tan6k5er6k -tan4klo -3tanks -4t3ankü -2tanl -3t4anne -tano2 -ta4nod -4t3a4nom -t3anon -ta4nop -ta4nor -4t5ansam -4t5ansat -t3ansc -4tanspr -4tanstr -t4ant. -tan4tan -4tantei -5tantenf -5tantenp -t5antig -5tantis -4tanwae -4tanwal -4tanwä -t4anz. -tan4zar -tan6z5erh -tan6z5erm -tan6z5erz -4t3anzu -tan4z5un -4t3anzü -tan4zw -3ta4pe. -ta4pes -2tapf -ta2pl -ta4poka -t3app. -4tappa -t3appr -ta2ra -4t5arabi -3tar3al -4t3aram -ta5rant -tar3ap -ta3ras -tarb2 -2tarc -5tarchl -5tarchr -3tard -4t3area -tar5ein -ta4r3el -ta4r5en4d -ta4rer -tar5ere -tar5ers -tar4hei -3tari -tar5ins -tar4kn -4t3arm. -t3armb -4t5arme. -4t5armee -6t5armen. -6tarmeni -4t3arms -3tarn -t3a4rom -ta4rop -4tarot -ta3rö -2tart -tar4tau -tart5e4c -tar6t5eig -tar6t5ere -t5artik -tar4t5in -tar4t3o -tar6t5rau -tar4tre -tar6t5ric -tart3s -3tartü -3tarty -ta3rum -4tarzt -3tas. -3t2asc -tas4k3l -ta3spr -ta4s3s -4t3assi -4tasso -3t2ast -ta4ste -tast5ei -tas4tin -tas4to -2t3as2z -2t1aß -t4at. -ta4t3an -ta4t5arm -3tatb -t2ate -ta4te3c -tat5ein -ta4tem -ta4t5ent -ta4t3er -ta4t3hi -4t3atom -tat5ort -tat5ric -ta4tro -3tatsa -4tatta -tatt5an -4tatti -3tatur -tat3zw -tau4b3a -tau4bei -3taubh -tau4b3r -t4auc -tauchs4 -t3audi -tau3e -tauer4l -tau4fak -t5aufar -4t5aufba -t3aufd -taufe4 -5t6aufe. -4t3auff -4t3aufg -t3aufh -4t5aufko -4t3aufl -tau5f6lie -4t3aufm -4taufn -4t3aufp -4t3aufr -taufs4 -4t5aufsi -4t3aufw -4t3aufz -3taugl -t3augm -2tauk -t3aukt -t3aul -3taume -4t3ausb -3tausc -tau6schei -tau6schm -tau6schn -tau6schr -tau6sch5w -4tausd -4t5ausei -5tausen -4t3ausf -4t3ausg -4t3aush -t3ausk -4t3ausl -t3ausn -4t3aus3p -4t3ausr -4t3auss -4t5austa -4t3ausu -4t3ausü -4t3ausw -4t3ausz -3tav. -3tavs -taxi3s -2t3axt -3taz -t3äbt -tä1c -t2äf -3tägi -3tägl -2tägy -2täh -t3ähn -3täle -2t3ält -2tä2m -t3ämt -t5änderu -2täng -t3ängs -3tänz -2t1äp -2täq -tä4reng -tä4r5ers -tärm2 -tä2ru -t2ät -3täte -5tätigk -4tätt -2t3ätz -2täug -3täusc -t3äuss -2täuß -2täx -2tb2 -tbahner5 -tbauer4 -tbau3f -tbau6m5en -tber4e -t3bh -tblocken6 -tbote4 -tb4ra -t3bs -tby2t -2t1c -tchar4m -tche4n -tch4i -tch3s -tch3w -t2ck -tcor2 -tcre2 -tcu2 -2td2 -tdar4m3 -tde2a -tde3s4t -tdi2e -t3ds -tea2 -2teak3 -3te2am -tean3 -3teba -t3e4ben -t5e4ber. -t2ec -4techd -4techk -4techm -5techn -4t3echo -4tech3s -4techt -2teck -tecke4 -te4cki -tecks4 -2t3ecu -te4del -tedi4e -te4dit -ted2r -2tedt -tee3id -te3ele -te3end -te4er. -tee3r4a -te3erl -te3erz -te4es -teesen4 -tee3t -4tefeu -2teff -te5ge. -tegen4 -te5gen. -te4g5end -teger4n -2t3egg -teg2l -3te3gr -teg5rae -teg5ran -te4grä -2teh -3teha -te4h3ac -te4hap -3tehä -te4hef -te4hep -3tehi -te4him -teh3la -3teho -3tehö -t3ehr -teh3ra -3tei. -t5eifers -tei4gab -teigerin6s -tei4gla -tei4gle -4t3eign -teik2 -t2eil -tei4lau -teilen6s -tei6l5ent -tei6l5er6h -tei6l5ero -tei6lerst -5teiln -2tein -tein5al -tein3d -tei4n5e4b -tein5e4c -tei6nen6s -tei6n5erf -tei6nerk -tein6hab -tein6sel -tein6spo -tein6tol -4t3eis. -t3eisb -4t3eish -4t3eisk -4teiss -tei5su -2teiß -tei3t -teiz2 -2tekz -3tel. -3tela -te4l3ab -te4l3ac -tel5aer -te4lam -tela4n -tel5an5e -te4lant -te4lat -te4l3au -te4l3är -3telbe -3telbr -3telbu -3telc -3tel3d4 -te3le. -tel5ech -tel5e4ck -3telef -3teleg -te4l5ei4c -te6l5eier -te4l5eig -tel5eint -5telema -tel5en5e -tel5eng -te4l5ent -3teleo -3telep -te4l5erb -te4l5erd -te4l5erf -te4l5erg -te4l5erh -te4l5erk -te4l5ern -te4l5ers -3teles -te4l5eti -te4leu -3telex -4t3elf. -4telfd -4t3elft -3telg -3telh -tel3id -te4lim -tel5ind -tel5inf -tel5inn -tel5ita -3telk2 -tel6lanf -tel6lant -tel6lein -tel5ler -tellerei6g -3tellö -3telm -3teln -3telo -telo4g3 -te4l3or -3telp2 -3telq -3telr -3tels -tel3s4k -3telt2 -3telu -te4lut -3telv -3telw -3telzy -t2em -3tem. -te4mak -3temal -te4mant -te4marc -tem5art -te4m5att -te4mau -4temäc -4t3embl -tem3eb -tem5ein -tem5er5e -tem5erf -tem5erg -tem5erh -tem5ern -tem5ers -tem5erw -te4m3i4d -te4m3i4m -tem5ing -te4miss -4temms -4temmt -te4mop -te4m3o4r -6tempelt -5temper -4t3empf -4temph -tem4pli -tempo3 -tems2 -temt2 -te4mun -te4m3ur -3t4en. -te4na4b -tena4d -ten5aeh -te4na4g -te4nan -te4n3a4r -ten3au -te2nä -ten3äh -4t5endal -4t5endap -4t3endf -4t3endh -4t3endk -4t3endl -4t3endp -tend4r -4t3endt -te3ne. -te4n3e4b -ten3ec -te4n3ed -te4nef -te5n4ei. -tenei4d -te4n3el -te4n5end -te4n5en5e -te4n5ens -te4n5ent -te4nep -ten5erei -te4nerf -4t5energ -te4n5erk -te4n5erl -ten5erm -te4n5ern -te4n5ero -te4n5erö -te4n5erp -te4n5ers -te4n5ert -te4neru -ten5e4sc -te4ness -tene4t -ten5eta -ten5eti -te4neu -te4n3ev -ten3f2 -4t3eng. -4tengag -4tenge. -6tenglis -teng4r -te4nid -te4nil -te4n3in -tenk2 -ten3n4 -4tenog -te4nol -te4nop -te4nos -te4n5ott -te4n3o4v -3tenr -3t4ens. -tensch4 -ten4seb -ten6serg -ten6stip -ten4sur -tent5art -ten6ters -ten6t5er6t -ten6t5er6w -6tentheb -t5enthu -t5enthü -ten6ting -4t3entk -4t5entrü -4tentz -te2ny -ten6z5erw -tenz6wei -ten4zym -teo2b -te3o2f -te5o4rei -te3ou -t2ep -2tep. -te3p2f -4tepit -4t3epoc -4teppe -4teppu -t4er. -t4era -te3ra. -ter5abe -ter3a4c -tera4d -4terad. -ter5ade -ter5aff -ter3a4g -ter3ak -ter5alar -te5ralk -ter5all -te6ralm -ter5alp -te5rals -ter5alt -te5ramp -te5ran. -te5rane -ter5anz -te4r3ap -ter3a4s -te4rato -ter3a4v -te4r5änd -ter6bauu -4terbe. -6terben. -6terbend -ter6blin -4t3erbs -4terbt -terb4u -4terdäp -4t5erde. -4terdg -terd4r -ter3d4s -3tere. -te4r3e4b -te4r5eck -ter5eier -ter5eige -6t5ereign -ter5eil -te4rein -ter5ell -3tere4m -te4r5ema -te4r5emi -te4r5end -te4r5en5e -te4r5ens -te4r5ent -teren5th -te4r5en4z -4tereo -3terer -te4r5erb -te4r5erd -te4r5erf -te4r5erg -ter5erh -terer6ku -terer4n -terer5o -terer5ö -te4r5erp -te4r5err -te4r5ers -te4r5erw -3teres -te4r3e4t -ter6folgr -terg2 -6tergebn -tergene6s -4t5erhab -ter7hall -ter6haltb -6t5erhöhu -te4r3id -ter5iko -4teril -6t5erinner -terin4v -teri4on -te4riso -te4rit -ter5i4ta -terk2 -t5erklär -6terkranku -ter6löse -3termi -ter4nas -4ternc -terngene6 -4ternwa -tero4be -ter5o4de -ter5oel -ter5ofe -te3roh -te3rok -4terons -ter3ö -ter6probt -terr2 -ter3ra -ter4re. -ter6regt -tersau6er -ter4sep -6tersparn -ter5stag -ter5t4a -tert4s -teru2 -te4r5ufe -terungsex5 -ter5uns -4t3erzb -terz4e -4t5erze. -ter4zes -4t3erzg -t2es -t4es. -te4sa4k -te4s3au -tes5eli -te4s3ep -tes5erb -tes5er5e -tes5erl -te4sex -tes4par -tes4pen -te4sph -tes4por -tes6senz. -tes3si -tess4t -test5ak -te5stal -test5art -tes5tät -tes4teb -te5stec -te5steig -te5s4tel -test5elt -te5step -tes6t5er5e -tes6t5er6f -tes6ter6g -tes6t5erh -tes6terk -test5erkl -tes4tin -t3estn -tes5to5s4 -te4sur -te4tabl -4t3e4tap -4t3e4tat -3tete -4t3e4thi -4tetho -2tetl -tet2s -3t4euf -teu4f3a -2t3euk -2t3eul -3te3um -te3una -te3uni -t3eunu -2t3eup -3teur. -teuren4 -t3euro -teu4r5ob -3teus -te4val -te4van -tewa4s -tex1 -4texam -2t3e2xe -2t3e2xi -2t3exk -2t3exo -2texp -tex4ta -tex4th -tex6trau -2t3exz -tè2 -2t1f2 -tf4ae -tfahr6ta -tfal4l5i -tf4äh -tfen2 -tf4la -2tg2 -tga4s5er -tgas5te -tgas4tr -tga4su -tga2t -tgeb2 -tgel4ta -tgende4 -tge4n5en5 -tge4nid -tgens4 -tger4a -t3gl -tgol6der -2th. -2t1h2a -3t4ha. -t2had -t4haei -3thal. -thal5ba -3thale -3thalh -t4hali -t4han. -3thani -2t1hä -3thäi -2thb -2thc -3the. -3t2hea -2t3hef -2t3hei -t4hein4 -the5ins -3t4hek -the4kau -3t4hema -4themd -3t4heme -t3hemi -4t3hemm -the3na -t3heng -t3henn -3t4heo -5therap -4therd -t3herl -t4herm -4t3herr -t3herz -3t4hese -t3hess -t2heu -t3heu. -2thf -2thg -3thi. -3thic -thic3k -t3hiel -thi5er. -2t3hil -2t3him -th4in -thi3nu -4thinw -2thir -4t3hist -2thk -2th1l -2th3m -thmu2 -thmus3 -2th3n -thn4o -2t3hob -tho5chr -2t3hoe -2t3hoh -4t3hole -4t3holt -4tholz -4thops -4thopt -tho3ra -4t3horn -4t3hose -tho3st -4t3hote -2t3hou -t3hov -2t1hö -2thp -4th3rau -thr4i -th3rin -2ths -2tht2 -t1hu -2thub -t3huet -2thuh -2thup -t2hur -t2hus -2thut -2t1hü -2thv -thvil4 -2thyd -ti3ac -4t3iago -ti5alei -ti3a2m -ti3a2p -ti3as -3tib2 -ti3ce -5ticket -tico2 -ti4dea -4tidee -ti4d5en4d -ti4dio -5tief. -5tiefe. -4tiefel -5tiefen -5tiefg -3tiefl -tieg2 -4tieg. -4tiegen -4tiegs -4tiegt -ti4e3in -4tiel. -tie4l3a -ti3ell -4tiels -tie2n -tien3e -5tiera -tie4r5ei -tieren6z -tie4reu -tie3s4t -2tieß -ti3eu -3tif. -ti3f2r -tifter6ke -tif4tr -3tig -ti4g3an -tige4ra -ti4g5er4z -tiges4 -ti4git -ti4g3la -ti4g3lä -ti4gle -ti4g3li -4tigmat -ti2gn -tigs4e -tigs4p -tig5s4tr -tik5age -tik5amt -ti4kanw -tik5a4sc -ti4kei -tikel3 -ti4k5ent -ti6k5erfa -ti4kim -ti4k3in -ti4k3la -ti6klini -ti4klu -ti2k3n -ti4kob -ti4k3op -ti4k3ra -ti4k3rä -ti4krei -ti4k3ro -tik3s -ti4kun -ti4kut -ti4lant -ti4lar -ti4lau -ti4l3e4b -ti4lei -ti4lel -ti4lent -ti4l3ep -3tilg -3tilie -3tilin -til5ing -til5ins -3tillo -4tillu -ti4l3oe -ti2l3ö -ti4meli -2timm -t5immat -timmer4 -tim6merg -tim6m5erk -3timo -2t3imp -ti3naf -tin5alb -tin3an -ti3nap -3tin2e -ti3nec -ti3ne3i -tinein4 -tinen4z5 -ti4n5erf -tine3t -4t3infe -4t3info -tinga4 -tin4g5ab -ting5as -tin4g3i -tin4gl -tingo4 -tings4p -ting5st -ti3n4in -t3i4n3it -2t3inj -tink3l -tin4kla -tin4kn -4t3inno -ti3no -ti4n3oe4 -ti4nor -ti2n3ö2 -t4ins. -4t5insel -tin4s5es -tin4spa -tin4sum -5tinte. -4t5inter -ti3n4um -2t3inv -3tio -ti4onv -3tip. -tip4pr -3tips -ti4que. -3tirad -t3iran -2t3ird -ti3re -ti3r2h -tirn3a -3tisc -ti5sche. -tisch5ec -tisch5ei -ti5schen. -ti5sches -ti4schl -tisch5lam -ti4schn -ti4schr -ti4schu -tisch5wa -tis5erk -tisex5p -2t3isl -tis4tal -ti3s4th -tis3ti -ti3s4tr -ti4sur -ti3ta -ti4tal -3tite2 -ti3ti -2ti3tu -3tiu -tium4s -3tiv -ti4van -tiv5ant -ti4v5atm -ti4v5att -ti4vel -ti4v5ent -ti4v5erg -ti4v5erh -ti4v5er4k -ti4v5er4l -ti4v5ero -ti4vet -ti4v3id -ti2vo -tiv3or -ti2v3r -ti2za -ti4z3in -ti4zir -ti2z3o -ti2zw -2tj -2t1k2 -tkege4 -tkraf4 -tkue3b -tkü3b -2t1l -tlang5s -tlei4bl -tlei6d5er6 -t2lep -tl4er -tle4r3a -tlergene6 -tle6ring -t3li -tlings5 -t3lö -tlungs5 -2t1m -tm4en -tmen6schl -tmen4t3 -tments4 -tmetaller5 -tmin4s -tmi2t -tmo4des -tm4or -2t1n2 -t3na -tn4am -tn4an -t3ne -tne5reig -tne4rer -tnergene6 -tnis3t -to3al -to2as -to3a2t -4tobj -to4boe -tob4re -to3cha -to3che -4t3ochs -3tocht -2tock -to6ck5ent -to6ckers -to6ck5er6z -t2od -3tod. -toder4 -tod5ern -todi4e -to4d3un -2t3o2dy -3toech -4toeck -2t3oe2d -toe2f -2t3oe2k -4to4el. -4t3oels -4toelu -to3e2m -3t4oen -3toepf -4toeps -to3erh -to3ero -t3oest -2toeß -t3oett -2tof -3tof. -toff3a -tof4f5ei -t5offens -tof6f5ent -tof6f5ertr -4toffi -toff3l -tof4fo -toff3r -toff3s4 -tof4f3u -3tofi -3tofo -3tofr -to3fu -togas3 -2t3ohr3 -2toi. -3toil -toi4ne. -toi2r -2toiz -3toj -tok2l -2t3okt -3tole -to4lym -2tolz -4toma. -tom5ans -tomar4b -to4m5ene -3tomie -3tomis -2tomn -to2mu -tom3un -3ton. -to4nad -tona5er -to4n3an -to4nap -to4nau -ton3d2 -ton3e4b -to4neh -tonend4 -toner6ke -to4nid -ton5ing -3tonn -tonne4 -to4nob -to4nof -3tons -ton4sin -2tony -3too -to4pad -to4pak -4t3o4pal -top5ang -to4pat -to4p3el -to4per -t2opf -3topf. -to3pfä -topf5erd -5topfes -top5flu -to4ph3a -to4p3hi -topi4e -to4ple -to4pli -2topp -tops2 -5topsy -3tor. -tor5aen -to3ral -to3ram -to5rand -tor5ans -tor5ant -tor5anz -to3rat -to4rän -tor6ch5el -4tordn -3tore -tor5eic -to4rein -to4r3el -to4rem -to6renna -tor4fan -t3orga -4t5orgel -to4r3i4d -torin4s -tor5int -tor3n -to4rob -to4roe -to4rop -to2rö -3tors -4t3ort. -tor5tan -4t3orth -4t3ortn -4t3orts -tort4st -tor3um -3torw -torz2 -3tos. -3tosa -tos2h -to4ska -to3s2m -3to3s2p -tosser4 -tos6s5ersc -to5stat -tos4to -to3s4tu -2toß -to4tae -to2tä -to5t4em3 -totemp4 -tote4n -to4tho -to4t3ri -tot3s4 -3t2ou -touil4 -to3un -to1x -4toxid -3töch -2t1ö2d -2t3ö2f -2t1ö2k -t1öl -2töl. -2töle -2töls -2tölu -3tön -3töpf -3törtc -2t3öst -3töt -4t3ött -2t1p2 -tpar4k -tpho2 -2tq -tr2 -2tr. -t4ra. -tra3a -tr4ab -tra4bu -tra5cha -tra5chl -3tract -4t3rad. -tra4dem -tra4der -4t5rades -t5radie -4tradio -5tradit -4tradiu -4t3radl -4tradp -4t3rads -3traeg -4traeh -4traend -5traene -4traeng -4traes -4traeß -tra4fah -4traff -4trafg -traf3l -4trafr -tra4g3l -3tragö -3trahi -4trahl -4trahm -3trai -tra5inh -3trak -4t3rake -3tral. -3trale -tra4leb -3tralg -tra6lins -4tramm -5t4ran. -4trand -tran4d5r -4trang -t5rangi -trang5s -t5ranki -tran4k5o -t3rann -5t4rans -tra5po -tras6terf -tras4t5r -4traß -4trateg -tra4t3h -t5ratin -tra4tr -3trau. -4traub. -4t5raubv -4trauc -4t3raup -4trauß -2t3räd -3träg -2träh -4tränd -3träne -4träng -2träs -2träß -4träuc -4träus -4träuß -t2re -2t3re. -2trea -t3reak -t3real -2treb -tre4bl -tre4b3r -2trec -t3rech -5treck. -5trecô -2t3red -4trefe -3t4reff -4trefl -4t3refo -2t3reg -2t3reh -trei4bl -4treic -4treif -4t3reig -4t3reih -4treik -4t3reim -4t3rein -trei4sc -t3reiß -4treit -trei6terp -t3reiz -2t3rek -2t3rel -t3rena -3trend -4trendi -5trennu -4t3rent -t3repa -4trepe -4t3repo -5trepp -4trepr -t5resek -5tresen -4t3resi -t5resso -tres4st -4trest. -tre5str -4trests -t3resu -tre4ta -3t4rete -tret3h -4t3retr -tre4tra -tre4t5ri -4trett -tr4e2u -treu3c -5treuem -5treues -3treuh -4treun -2t3rev -tre3x -2t3rez -t2ré -2t1rh -3tri. -3trib -4trich. -4triche -trichs4 -trid2 -tri2e -3trieb -trie5fr -tri5ens -tri5ent -3trier -tri3gl -3trigo -t4rik -tri4ke. -tri4kes -5triko -4trikt -3trim -4t3rind -4tring -4t3rinn -3t4rio -t2rip -3trisc -t3riss -tris5ta -4t3ritu -tri2x -trizi3 -3tro. -4trock. -4t3roec -4t3roeh -4troem -3troep -4t5roess -3troj -tro4kes -4trom. -tro6manp -4tromb -tro4men -tromer4 -4tromg -tro4min -4tromk -4troml -5trompe -4trompu -4tromr -4troms -4tromt -4tromv -4t3rond -tro6niks -t3ronn -tro3ny -t4r4op -3tropf -5trophä -3tross -tro5sta -4t3rot. -4trout -3trow -3troy -2t3röc -2t3röh -2tröm -2t3rön -3tröp -4t3röss -3trua -3truc -true5be -true5bu -4t3ruec -truecker6 -5truemm -t3rues -2truf -2truk -trum2 -3trum. -t3rumä -4t3rumm -trums3 -4t3run. -4t3rund -t3rung -5trunke -3t4rup -tru4th -2truw -trü3be -trü3bu -2t3rüc -trücker6 -3trümm -2t3rüs -t2ry1 -2ts -tsa2b -t3sack -t2s3a2d -t4s3aem -ts3aer -t3saeu -ts5aeus -t2sak -t3s4akk -t3sakr -t4s3all -t4s3alt -t4s3amb -t5samm -ts3amp -t4s3amt -t2san -ts3ang -ts3ank -ts3ant -ts3anz -t2sa2p -t2sa2r -t3s4ark -t2s3as -t2s3au -t3s4au. -tsau4d -ts4au4e -tsau4m -tsau4n -tsauto6r -ts2äc -t2sär -t2säs -t4s3äus -ts3b -ts4cap -t2sce -t6schart -t4s5chef -t6s5chine -t3schl -tsch6lic -tsch4lo -t6schmue -t6schmun -t4schmü -t6schor. -t6schore -t6schors -t4schro -t4sch5wo -t4sch5wö -tsco2 -t2s3e2b -tse2c -t2s3e2d -tse2e -tsee3i -t2sef -ts6ein. -t4s5eind -ts5einf -t4s5einh -t4s5einl -t4s5eint -t5seit -ts3e4kl -tse4la -t4s5e4mis -tsen5er -t4senq -t4s3ent -t4s3e4pi -ts3epo -t2ser -ts3er3e -ts3erf -ts3erg -ts3erh -ts5e4r5in -ts3erk -ts3erl -ts3erm -ts3ern -ts3er3o -ts3erö -ts3err -ts3ers -ts3ert -tse3r4u -ts3erw -ts3erz -t6s5essen -tse2t -t4s3e4ta -t4s3eth -t4s3eti -t2s3e2v -t2sex2 -t3s4exi -ts3ha -t2s3i2d -t3sig -t2si2k -ts3iko -ts3ima -t4s3imp -tsi2n -t4s3ind -tsing4 -tsin5gl -t4s3ini -t4s3int -t5skala -ts4kele -t5sklav -ts3ko -t3slip -t3s4mog -t2so2b -ts3oef -t4s3off -t3son -t2s3op -tso2r -t4s3ora -t4s3orc -t4s3ori -t4s3orn -t4s5ort. -t4s5orts -t3sos -t2s3o2v -t2sö -ts3öf -ts2öl -t3span -t5s4pare -t3spek -t4speri -t5sperr -t4s3pic -t4spins -t2spl -t5s6port. -t5s6portl -ts6ports -ts4prac -t4sprei -t5sprun -t3spue -ts4pul -ts5rei -ts3s2 -tsse5ru -tss4t -t1st2 -t6s5taeti -t4stafe -ts3tak -t4stale -ts5tank -ts5tant -t4s5tanz -t4s5tat. -t4s5tauc -ts3tav -t4s5täti -t4steil -t5stein -t4step -ts4terb -ts4tern -ts4tero -ts3tex -ts3th -ts4tig -t4stit -t4stoch -t4ston -t4stört -t4strac -ts4trap -ts4tras -t4stren -t4strie -ts4trom -tstro6ma -t4strun -t5s4tub -ts4tuem -tstu5er. -t2sty -t2su. -3tsubi -t2s3ul -t4s3um. -t4s3umb -t4s3umd -t4sumf -t4sumg -t4sumk -t4suml -t4sumr -t4s3ums -t4s3umv -t4s3umw -t4sumz -t2s3un -t2s3ur -t2s3ut -tswa4s -t5szene -2tt -tt3abm -t3t2ac -tt5achs -tt3aeh -tt3aer -ttafe4 -tta6g5ess -tt3ahl -tta4ke -t4t3alb -t4t3ana -t4tanb -t4t3and -tt5anka -tt5ankä -ttan4k5l -tt5anku -t4tanr -t4tanw -t4t3app -t4t3arb -tt3arm -tt3art -tt3äh -tt3änd -tt3är -tte2c -tt3eck -t2t3ef -t4t3eib -t4t3eif -tteil5a -tteile4 -ttei6leb -t4t3eim -tt3ein -t4t3eis -tte4l3a4 -tte4l5e4b -ttel5ein -tte4l5en -tte4l5in -tte4lit -tte4l3o -tte4lö -ttels4t -ttel5ste -ttem4bo -t4temo -t4temö -t4t3emu -t3ten -tte4na -tte4net -tte4nob -t6ten6sem -ttens4p -t4t5entb -t4t5entf -t4tents -t2teo -tte4rat -tte4r5ec -tte6r5ei6s -tteri4e -tte4rik -tte4ris -tter6klä -t6terneu -t4t5ernt -tter5oe -tte4r5oh -ttesa4 -ttes5ac -ttes5ae4 -tte4s3ä4 -tte4s3o -ttes5pr -ttes6ser -tte5stri -t4teti -tt5extr -t4t3heb -tt3hel -tt3hen -t4t3hin -t2t3ho -t2ti2d -tti3es -t4t3igi -tti5le -tt3imm -t4t3ind -t4t3inf -t4t3ins -tt5isol -tti3z -t4t3la -t4tli -tto3ef -tt3oel -tto5ent -tto3er -tt3ord -t4t3org -t4t3orn -tto3s -tt5rand -tt3rea -tt3rec -tt2ro -tt5roch -t3troe -tt5rose -t4trou -t3trö -tt3rud -tts5and -ttsau4 -ttschi4 -tt4sele -tt4s5end -tts5erb -tts3pr -tts5taet -tt4s5tät -tt4stem -tts4tu -tt4sum -ttu3en -ttu2f -tt3ufe -tt3uh -ttur4m3 -ttü1 -ttwa3r -ttz2 -tu3al. -tu3alm -tu3alp -tu3alv -tu3ant -tu3ar. -tu3ars -tuba3b4 -3tuch -tu4chel -tu3cho -tuch3s4 -tu3cka -2tud -tudie4n5 -3tudo -2tueb -tue5ber. -3tuech -2tueh -tu3ell -tu3en. -tu3enb -tu3eng -tu3enk -tu3ens -tu3ent -3tuer. -3tuerc -5tuere -tu3ern -tue4r3o -tu5ers. -4tuerz -tu3es -tue4th -4tuetz -2tuf -tuf4e -tu5fen -t3u4fer -3tuff -tu4gan -2tuh -tuhl3a -tuh4lei -tu3ist -tu2kr -2t3ulm -tul4pa -tul4pf -tul4ph -3tum. -tum4b3l -tume4 -2tumf -2tumg -2t3umh -2tumk -2tuml -3t4umo -2tump -2tumr -4t5umsat -t3umsc -tum4si -tum4s3o -tum4spe -2t3umt -2t3umw -2tumz -2t3una -3tuna. -3tunas -2tund -tund4e -tun4d5ei -tun3dr -3tune -tune4b -tun4en -t3unf -3tung. -4t3unga -4t5ungeh -4t5unger -4tunget -tungs5 -4tunif -4t3u4nio -4tuniv -2t3unl -2tunm -3t4unn -2t3u2no -2tunr -t3unsc -2tunt -3tunt. -2tunv -t3unz -2t3up. -2tupd -2t3upg -2tups -t2ur -tur5abe -tu4r3ac -tu4rad -tu4rae -tu4r3a4g -tur5aka -tu4r5alm -tu4ralt -tu4ram -tu4r5ang -tur5ank -tur5ans -tu4r5anz -tur5asp -tura5st -tu4rau -tur3c -tu4r3e4b -tur5eic -tur5ein -tur5eis -tu4rep -tur5erb -tur5erg -tur5erh -tur5erk -tur5erl -tur5ern -tur5ers -tur5ert -tur5erw -tu4res -tur5es5t -tu4r3e4t -tu4r3e4v -tu4rid -tu4ril -tur5ini -tur5ins -tur5int -tu2r3o -turo4p -tur5sa -tur3sk -tur3sn -tu2ru2 -tur3uf -tur3un -tu2s3a -tu4schl -tu4schm -tu2se -tu2s3i -tu2so -tu3spr -tu3t4a -tuts3c -tuts5p -2tüb -tü5ber. -3tüch -tück4se -3tüf -3tüll -3tüm. -3tüma -3tümb -3tümc -3tümf -3tümi -3tüml -3tüms -3tümt -3tümu -3tümv -3tümw -3tür. -3tür3c -3türe -3türg -3türk -3tür3s -3türw -3tütc -2tv -tvie2 -tvoran4 -2tw2 -twa3i2 -twa2r -twa2s -twegs4 -twe4n3e -twetterer5 -twi2e -3t4wist -2t1x -tya2 -3typ -ty2p3a -ty2pf -ty1s2 -2tz -tz1a2 -t2zat -t2z3än -t2zäp -t2z3är -tz3e4di -t4z3eie -t4z3eim -tze5ina -tze5ind -t4z3eis -tzei6t5end -t4zelek -tze2n -tzene4 -tzen5eb -t6zensem -t4z5entg -t4zentl -t4z5ents -t4zentw -t4zentz -t5zer. -tzer6bre -tze4reb -tze4rer -tz6erfe -tz6erfr -tzer6gre -t4z5erhö -tzerin6s -tz6erl. -tz5ermi -t5zern -tzero4 -t4z3erö -tzer4öf -t6z5ertra -t6z5erträ -tzer6werb -tz5erzi -tze2s -t2z3i2d -tz3imi -t4z3ind -tz3inf -t4z3inh -tz5insa -tz3int -tz3inv -tz3iso -tzo2b -tz3oef -t2z3ok -tz3om -t2z3or -tz3ti -tz4tin -tzu4gu -t4z3unf -t4z3uni -tzwan4d5 -t3zwie -2ua -u1a2b -u1a2c -ua4ch -uad3a -ua4dan -uad2r -uae2s -ua2g -u3al3a2 -u3a2l3ä -u3alb -u3ald -uale2 -u3a4leb -u3a4lei -u5alemp -u5a4l5ent -ualer4 -u5a4l5erf -u5a4l5erg -u5a4l5erk -u5a4l5erl -ua4l5err -u5a4l5erz -u3a4let -u3alf -u3alg -u3alh -u2ali -u3a4l3id -u3a4l3in -u3alk -u3aln -ua2l3o2 -ualp2 -u3alr -u3als -u3alt -ual5to -u3a2lu -u3alw -ualz2 -u1am -u3an. -u3anar -u2ane -u3ans -uante4 -uanten5 -uara4b -uarer3 -uar4k3a -uar4k3e -uark3s -uar4t5a4n5 -uart3s -u3arw -u3as. -uas2a -uasi3 -uas2p -ua2th -uati6ons. -u1au -uauf3 -u1ay -u1äm -uä2s -u1äu -2ub -ub5anke -ub3ant -u4b3arb -uba2s -ub3asc -ubb2l -ube4be -u4b3ehe -u4b3eig -u4b5eins -ub5eint -ub5ein4w -u4b3e4mi -uben3o -u4b5erde -uberin6t -ube4woe -ube4wö -u3b4is -u3b4it -ub2l -ub3läu -ub3los -u4b3lus -ubmi2 -u2bo2b -u2b3ok -u2bop -u2b3oz -ub5rank -ub2ri -ub2ru -ub2rü -ub4s5ang -ubsau4 -ub4s5auf -ub4s5aus -ub4sin -ub4s3or -ubst4 -ubs5trau -ub4s5tri -ubt2h -u4bueb -u2büb -2uc -uc1c -u3cha. -uch3ab -uch3ad -uch3ae -uch3al -uch3am -uch5ank -uch5ans -uch5ant -uch5anz -uch5arte -uch3as -uch3au -uch3ä -u3che -u4ch3e4b -u4ch3ed -uch3ei -u4ch5elf -uch5ell -uchens4 -u4ch3ep -ucherin6s -ucherin6t -ucher6ken -u6cher6lau -uch5erlö -uch4erz -uch5er6zeu -u5ches -uche4t -uch5eti -uch3i4d -u5chig. -uch3il -uch3im -uch3in -uch3l -uch3m -uch5ma -uch5mi -uch3n -u3cho. -uch3o4b -uch5oef -uch3op -uch5org -uch5ort -u3chos -uch3öf -uch3r -uch4san -uch6s5ein -uch4s5el -uch4sem -uchser6z -uch4s5in -uch4s5or -uch5stran -uch5stub -uch4tak -uch6t5eig -uch6t5erf -ucht5erl -uch6t5ersc -uch6t5er6t -ucht5erw -uch6t5erz -uch4t5in -ucht5sk -u3chu2 -uch3uh -u4chum -u4chur -uch3ut -uch3w -u1ci -ucin3 -u4ckar -uck5elf -u4ck5ene -u4ckent -u4ck5en4z -u5ckero -u2cki -uck3in -uck5spe -uck5stu -u1cl -u1cr -2ud -u3d2a -uda3d -ude3e -ude3i2 -udein4 -ude4n3e -udens4 -ude6rent -ude6rerr -uderna5 -uder3o -uder3ö -ude4str -udiener6 -udi2o3 -uditi4 -ud2ob -udo3c -u3d4o3e -u2don -ud4o3s -u4d3rat -2ue. -2uea -1u2e2b -2ueba -ueb3ae -2ue4bc -2uebd -uebe2 -ueber3 -ue3bi -ue4bk -ueb5le -ue3blo -ue3bo -ue4bs -2u2ec -uech4s5c -ueck5erb -ue6ckerf -ueck5erh -ue6ck5ers -ue6ckerz -ue2d3a2 -uede4c -ue5den. -ue2d3o4 -ue2d3r -ued3s2 -ued3t2 -2uee2 -ue3er -2uef -ue2fa -uef3ae -ue4f3ei -ue4f3in -ue2f3l -ue2f3r -uefs2 -ue4fum -2ueg -uege4l5a4 -uege6lei6s -ue2gl -ue2gn -u2eh -ue2h3a -uehan5 -ue4h3ei -ue4h5eng -ue4hent -ue4h5erf -ueh5erk -ue4h5er4z -ue2h3i -ueh4lei -ueh4l3o -uehn4s -ueho2 -ue4h3ob -ue4h3of -u1ei -2uek -2uel -ue3lak -ue4l3am -ue4lau -uel5eim -uel5ein -ue4lek -uel4er -ueler5ei -ueli2 -ue4lio -uella4 -uel4l5ad -uel4lar -uel4l5au -uell5e4c -uel4l5ei -uelle4n -uel4lo -ue4lor -2uem -ue4ment -ue4m5erz -ue4m3id -ue4m3in -uemm4s -uem2s -ue2mu -2uen -ue2na -uena4g -uen3ah -uen3ar -ue2nä -uen4d5ei -uendergene6 -uen4due -uendungsgene6 -uen5ema -uen5ens -uen5erk -ue4n5er4l -uen5esk -uen4fak -uen4gag -uenge6bu -uengene6 -uenge6schl -uenge6wan -u3enh -ueni2 -ue4n3in -uenk4l -uenk4r -ue2no -uen4seb -uen4ser -uen5ska -uens6por -u3env -uen6z5eig -uen4z5in -uen4zw -2ue1o -2uep -uepfel5 -ue2po -uer3a -uera4de -ue4ra4t -ue2r3ä -uer3d2 -ue5re. -ue4r3ec -uer3ed -ue3rei -ue5rei. -uerei5s4 -uer5ela -uere4m -uer5emi -ue4r5eng -ue4r5ent5 -ue4rerf -ue4r5er4g -ue4r5er4h -uerer4k -uerer4l -ue4r5er4m -ue4r5err -ue6r5er6sa -ue6r5ersc -uerer6sp -ue6r5er6st -uerer6za -uerer6zä -u5er5esk -u5eress -ueres6si -uere4t -uer5eta -u5ereu -uer6fahru -uer4fas -uer6g5eng -ue4r3i4d -ue4r3i4m -uerin6nu -uerin4t -uerk4l -uer4le. -uerme4c -uer4nan -uer4nau -uer4neu -uerns4t -ue3ro -uer5ode -uero3s -uer4rh -uerr4s -uers4k -uers4ta -uer5stag -uer5stau -uer6trag -u3e4rue -u3e4ruh -u3erum -u5erunf -u5erunt -u3erur -uer4zoe -2ues -uesch5af -ue4schl -uesen5e -uese3s -ue4sin -ues3l -uesli3 -ues2p -ues4sau -ues4s3c -ues4so -ues4st -ues5tel -ues4th -2ueß -2uet -ue4t3al -ue4tap -ue4tau -ue3te -ue4tik -ue4t3ro -uet6schw -uet4st -uety2 -2uev -2uew -u1e2x1 -2uez -u2fab -uf3abe -u2f3ad -uf3aen -ufae4ß -ufall4 -ufa2n -uf3ana -u4f3an3e -ufa2r -uf3are -uf3arm -u2f3aß -u2fa2t -uf3au -u2f3än -u2f3ä2ß -u2fei -uf3eig -uf3ein -ufel4s5a -ufel4s5i -u4f3ent -ufen4te -ufer4fo -uferin6s -uf5erke -ufer6lau -u4f5erne -ufe2t -u4f3eta -u4f3eth -u4f3eur -u2fex -uffer5e -ufferin6h -uff2l -uf4fot -uff2r -uf4fro -u2f3id -u2f3im -u4f3ins -u4f3int -u4f3inv -uf1l -uf3lim -uf3lit -uflos3 -u3fluc -u3flug -u3flüg -u2fob -u2f3of -u2fop -ufo2r -uf3ori -uf5orte -uf1r -uf4rass -u3frän -ufs3an -uf4s5eta -uf4s3in -uf5skie -uf2so -ufs4por -uf4spre -uf4spro -uf5stras -ufta4g -uft3eb -ufte4m -uft5erd -uft5er4g -uft5er4l -uft3in -uft3s4 -ufuen4 -uf3ums -uf3urk -ufus2 -2ug -ug3a4br -u4gabs -u4gabte -u2g3a2d -ug3aeh -ug3aku -u4g3ana -u4ganb -u6gangeb -u4gani -u4g3ank -u4g3ant -u4g3anz -u2g3ap -ug3ars -uga2s -ug3asc -ug3au -u4gaus -ug3d2 -ugd4e -ugeb2 -u2g3ec -u4g3eig -u4g3ein -ug5eise -u4gelem -ugena4b -ugen6dre -ugen6d5ri -uge4n5en -ugens4 -u4gentf -ugenz4 -u4g5erke -u4g5ersa -uges4 -u5ges. -u4g3esk -u4g3ess -ugge4l5a -ug3i4de -ugi3en -u2g3im -ugin4h -ugin6ner -u4g3ins -ugin4sp -ug1l -u4gläu -u6gleist -u6gleitb -u6gleits -u6gleitu -u2gli -ug3liz -u4glok -u2g3n -ugo1 -u2go2b -u2g3oc -u2g3oe -u2gof -u2gom -ug3o2p -u2g3o2r -u2gö -u4g3ram -u4g3rat -u4g3reg -u4g5reis -u4g3res -u4g5rind -u4grou -ug3rum -ug3rüs -ugs4alb -ugs4and -ugs2c -ug4sein -ug4sel -ug4sin -ug5span -ugs4por -ug5stan -ug5s4tie -ugs4tol -ug5stre -ug4s5tür -u1ha -u1hä -2u1he -uhe3a2 -uhe3b -uhe3e2 -uhein4t -uhe5rae -uhe3s2 -2u1hi -2uhl -uh4l3as -uh3lä -uhl5ein -uh4l5ent -uh4l5erb -uh4lob -uhmes3 -u1ho -u1hö -uhr3an -uhr3au -uh3rei -uhrei4s -uh4r3er -uhr3o -uh2ru -uh1s2 -uht2 -2u1hü -uh3w -2ui -ui2a -ui4che -ui3cho -ui4cker -u3idd -u3idm -u3ids -u3idt -u1ie -ui3em -u1ig -ui2ge -uik2 -uil4les -u1im -uin3it -u3inl -u3inw -u4ische -uisi4n -u3ism -uis3ta -u3isv -u2it -ui4te. -ui2t3r -ui1x -uji3 -u1ke -uke2n -uken3e -uk3er3e -u1ki -u1k2l -ukle3i -uk3ler -uk2n -u2k3o2b -uko3c -uko4ma -uk2r -u4krai -uk2t3a -uk2t3ä -uk4t3eb -uk4t3el -uk4t5ent -uk4t3er -uk2th -uk4t3i4m -uk4t3in -uk4top -uk4t5o4ri -uk2t3r -uk4t3um -uk2u -ukue3b -uku2s -uk2ü -ukü3b -u1la -u3la. -u2l3af -u3l4ag -ulan4di -u4lanf -ulap3 -ula4ra -ularen4 -ula6r5end -ularz4 -ula2s -ulasch5 -ul3aus -u1lä -ul3äm -ulb2 -ul1c -ulda3e -uldan3 -ul4dar -ulde4n -ul4d5erf -ul4d5erk -ul4d5erl -ul4d5ers -ul4d5rue -ul4drü -ul3eig -u2lek -ul3elf -ul3elt -ul3emb -ule2n -ulen3e -ulen3i -ulenk4 -u2lep -ul3epe -ul3e4po -ul3er4h -ul3er4r -u4leru -ule4s3a -ules3t -ule2t -ul3eta -ul3ext -ulf2 -ulg2 -uli2k -ulik5an -ulik3o -uli3pf -ulk4a -ulk2l -ulk2r -3ulkt -ul3le -ull5eic -ull5ein -ul4lem -ull5endu -ull5erkl -ul4lid -ul4l3o4d -ul4lop -ull3s2 -ulni2 -ulo2i -ulom2 -ulomb3 -u2l3o2p -u2l3or -ulps2 -ul4sal -ul4sam -ul4s3ec -ul4ser -ul4s3in -uls6t5erk -uls4t3h -ul4sum -4ulta -ult3ar -ul4tau -ultauto6 -ult5ein -ulter6min -ulti5t -ult3s2 -u1lu -ul3umb -u2l3ur -ul2vr -ulz2w -u4m5a4ben -uma2c -u4m5aest -u4m3aku -um3alb -u4m3all -u4m3alt -u5mann -u4m3app -u4marc -u4m3arg -u4m3arm -um3aro -u4m3art -u4m5asts -u4m3atl -u4m3atm -u4m3aus -u4maut -um3ärg -u4m3äst -umd2 -u2m3e2b -u4m3e4ck -u2m3ef -um3eic -u4m3ei4n3 -u4m3elf -umens4 -u2m3e2p -umer4a -u4merd -u4m3erf -u4m3erg -u4m3erh -u4m5erku -u4m3erl -u4m3ero -u4m3erw -1umf -1umg -2umgü -um3ill -um3inh -um3ir -um3iso -umi2t -um3ite -1umk -1uml -um3me -4ummel -ummi5erz -um3n -u2m3ob -u4moel -3u2m3o2f -u2m3o2p -3umöf -ump4fa -ump4fin -4umpfs -ump5ler -1umr -umsch4 -umsche6f -um4s3ed -ums5ein -um4ser -ums5erw -um2sk -um3s4kl -ums4ter -um4sum -umt2 -um3to -umu2r -u1mü -u2m1y -1umz -2umzw -un1 -4una. -u2nab -un4abe -4u3n4abr -u3nac -u3nae -un2ag -u3n2al -un4apa -un4ara -4un2as -una3st -u3n4au. -u3naue -u3naus -u3naz -un2är -2und. -un4da4b -und3ak -und5art -un4d3as -un4dän -3undd -2unde -un4d3ec -undein6f -und5eis -undemi4 -unde5ras -under5ei -underer6 -und5eroe -un4d5erö -underten6 -under6tend -un4d3ex -4undg -4undi -un4d3id -un4dim -und5in5i -3undn -2undo -un4do4b -un4d3op -4un2dr -und3rä -und3re -und3ri -und3rü -4undsc -undue6b -un4d3um -undü4 -un4düb -3undz -un3ec -une2d -une2h -un4ei. -un3eid -un3ein -un3eis -une2l -u4n3elf -3unemp -une2n -unen3e -uner5o4b -u4n5erz. -un2es2 -uneu3 -u4n3eul -unf2 -un3fa -3unfä -4unfe -3unfr -un4g3ab -un4gad -un4g3a4g -un4gak -un4gam -ung5art -ung5ass -un4gat -un4g3au -ung5eig -un3gen -ungen5erg -ungen6te. -ungen6ten -unger4e -3unget -3ungew -3ungez -un4g3ig -un4g3in -ung5lat -3unglu -3unglü -un2go -un4g3rä -un4g3ri -un4gro -ungs5pa -ungs5tae -ung5stang -un3i4de -3u2nif -unik2 -un3iro -u3nisc -un3isl -3u2niv -2unk -un4k3ak -un4k3an -un4k3au -unker6ke -un4ket -unkgene6 -un4kne -un4kno -un4knu -un4k3ro -un4k3ru -unk3s2 -unk4tit -unk4top -unk4t5ri -unk4tro -un4kum -un4kur -un4kut -un4küb -unloe4 -unlö2 -1unm -unmen4s -unmut4 -un4n3ad -unne2 -unnel3 -unn6en5e -u2no2b -uno2r -1unr -uns2a -unsch5el -un5schen -unsch5wi -3un3si -uns2k -un3ska -uns2p -unsta4g -unste4c -uns6t5erw -unstu4 -un2sz -1unt -2unt. -unt2a -unt3ar -5unterd -unteren6 -unte4ri -unt6ers -2unth -2unti -unti4m -2unto -unt2ö -unt3s2 -un3t4u -unuss4 -1unv -2unvo -unvol4 -unvoll5 -1unw -2unwä -u3ny -unz2w -2uo -u1o2b -u1oc -uoe2d -u1of -u3or. -u3or3a -u3or3c -u3o4ret -u3org -u3orh -u3o4ris -u3ork -u3orm -u3ors -u3ort -uo3ru -u3orv -u3orw -uos2 -u3os. -uote2 -u1ox -u1ö -uö2d -u2ös -1upd -upe2r -upe4r3a4 -uper3e -uperer4 -upf3au -u4pfen -upf6erp -u2pfi -u4pf5lau -u2p2fo -u1pi -up2l -2u3po -2upp -up2pf -up2pl -uppor4 -upport5 -u1pr -up3sl -up4t3a2 -upt5en5e -upt5erf -upt5erg -upt5erk -upt5err -upt5ers -upti4d -upt3il -upti4m -upt3in -upt3o -upt3r -2ur. -u1ra -u4r3abb -ura4ben -ur3abt -ur3adr -ur3ael -ur3ae4m -ur3aen -u4r3akt -ural6leg -u5rallei -u5rallo -ur3alt -ural4ta -ural4te -ur3alu -ur3amp -3u4ran. -u4ra4na -uran5at -uran4ba -uran4bi -u4ranf -uran6fän -ur5angs -u4rank -u4rann -ur4anr -uran5s -u4r3apo -u2rar -ura4ren -ura4ri -u4r3atl -u4r3att -ur3auf -ur3auk -ur3aus -u2r3a2v -ur3äl -ur3ä2m -ur3än -ur3b4a -urchi4 -ure3am -ure4be. -ure3e -ur3eff -ur3ehr -ur3eig -u4r3ele -ure4ne -u4r5ener -uren6gag -urens4 -uren6sem -u4r5entg -uren6thu -u4r5entn -uren4zy -ur3epo -u2r3e2q -urer5eig -ure3un -u4rex -urfar4m5 -urf5eig -urf5erg -urf5erk -ur4f5erl -ur4f3li -ur4f5rec -ur4fro -urf5str -ur4f3uh -ur6gense -urg5inn -ur4g3ri -urgros4 -urg3s2 -u1ri -uri4c -ur3ide -ur3ill -ur3imp -u4r3ind -u4rini -urin5it -ur6ins. -ur5in6sek -urin6stin -u4rint -u4r3i4so -urk2l -urk2o -urk3sp -ur4m3ab -ur4maeh -ur4m5ang -ur4m5art -ur4matt -ur4mäh -ur5me. -urm5eie -urm5einh -ur4m5ere -ur5mes -urms2 -ur4mum -ur4m3ur -ur4n5ere -ur4nol -u1ro -ur3ofe -ur3off -u4r3orc -ur3org -u1rö -urp2 -urr2a -3ursac -ur4san -urs5ang -ursau4e -urs5auf -ur4seb -urs5ere -urs5erf -urs5erk -urs5ert -ur4sex -urs5inf -urs5int -ur2sk -ur4staf -urs5tak -urs5tele -urster4 -ur4s3um -ur4s3un -ur4tai -urt5erz -urt2s -urts4c -u1ru -urue5cken -urueck5er5 -uruf2 -uru3fl -ur3uhr -ur3umb -ur3umh -ur3ums -uru3s -u1rü -urück5er5 -urve4n -ur2za2 -urz3ac -ur4z3ec -ur4zeg -ur4z3ep -urzer3 -ur4z5ere -ur4z5erk -ur4z5erl -ur4zin -ur4z3op -ur2zw -us3aar -usa2b -us5achs -us3aeh -us3aer -us3aes -usa4gi -usa2k -usa4la -us5alar -u5sali -us5alta -u5salz -us3amb -us5amma -us3amt -u4s3ang -us3ant -us3anz -u2sar -usa4ro -us3ast -usauto4 -u2säh -u2sär -u2s3äs -us5bli -usby2 -u2sce -us2ch -u4schab -u6schaer -u4schak -u4schar -u4schas -u4schär -u4schef -usch5eic -u6sch5eind -usch5einh -u6schene -u6schent -usch5eul -us4chi -u6schimp -u6schlan -usch5lap -u6schlauf -u6schlem -usch5mis -u4schmo -u4schmö -u6schmus -u4schof -u4schok -u4schor -u4schur -u2sco -usde3s4 -use5be -u2s3e2c -use3en -u2s3ei -u3seid -us5e4kel -us2en -usen6dac -usen6dere -usenk4 -u6sen6sem -u4sentl -us3erd -use4rec -u4s5er4pe -u2se2x -us3g -u3shid -u2sid -usi5er. -usi5ers. -usi4kas -usi4kat -usiken4 -us3ind -us3inf -us3int -u2sk -us5lob -us3oc -u2s3oh -u2s3op -us3orb -us3orc -us3ost -us3ot -us3ou -us4pate -us4patz -u3s4pec -u3spek -us3pic -u3s4piz -us6port. -us6ports -u4sprae -u4s5prä -us4s3ad -us4s3eb -us4s3ec -usse4g -uss5einf -uss5eins -us4seka -usse4kr -us4sema -us4semä -usse4mi -usse4n -ussen5e -us4sep -us5s6er. -usser6fa -usser5kr -us6s5erla -uss5er6le -usser5w -uss5erz -us4sesp -us4sete -us4sez -us4sph -uss5tal -us4stor -ussu2 -us4s3um -uss5urt -u4st. -u3stad -ust5arm -usta4v -ust5ave -u6stele -ustell5a -ust6er. -uster6fü -uster6tr -us4t5ess -us5thal -u3stir -u3s4tol -u4stöp -u5strah -u5streck -u5s4tuec -u2sue4 -u2s3uf -u4s3umb -u4s3umd -u4sumg -u4sumz -u2sü -u2sya -u4s3zi -2uß -u2ß3eb -uße2n -ußen3e -u2ß1u -2ut -ut5ablo -ut3abr -ut3adl -ut3aeh -ut3aer -u5tafe -ut3aff -u2tah -ut3akt -ut3alg -ut3alk -ut3al4l -u4t3alt -u2t3a2m -ut3ana -ut3and -u2t3ar -ut3ass -ut3auf -u4taut -ut3äh -u2t3är -u3tea -ut3ede -ute3e -uteg2 -u3teh -ut3ei. -ut3eie -u4t3eig -ut3eiw -ute4la -uten4ac -ute4n3e -utens4 -u4t5entf -uter3a4 -utergene6 -u4t5erhö -uteri4e5 -ute6ring -u4t5ernt -u4terwä -ut4es -ut4et -ut4ev -u2tex -utfi2 -ut3hel -u2t3hi -u2t3ho -u2t3hu -u2t3i2d -u4t5igel -3u4tili -ut3ind -u4t3inf -u2t3ir -u4tiso -uti4vi -uto3c -ut3oef -uto5erf -uto5ers -uto5erz -u3tof -u3tom -u4tops -u4tord -uto6rena -uto6renb -uto6renc -uto6rend -uto6rene -uto6renf -uto6reng -uto6renh -uto6reni -uto6renl -uto6renm -uto6renr -uto6renv -uto6renw -uto6renz -ut3ort -uto3ru -uto3s2 -u2töl -ut5raeu -u4t3räu -ut3rea -ut3res -ut3ril -u4t3rou -ut3röt -u5trup -ut3rü -utsau4 -ut4schl -ut4scho -ut4schö -ut3ser -ut4sin -u4tsk -ut3sky -ut3sp -uts4pe -uts4to -uts4tu -ut3t2l -ut3to -utto3e -ut4t3ro -utt3s2 -utu3en -u2t3um -utu2n -u4t3une -utu4re -utu3ro -utu3ru -ut2za -ut4zed -utz5ehr -utz5eig -ut4z5ene -utzer6tra -utzer6trä -ut4z3e4t -ut4z3in -ut4zis -ut2zo -ut2zö -ut2zw -2u1u2 -uum1 -uume2 -uumen5e -uungsex5 -u1ü -u2ve. -uve3na -uve3ra -uve3rä -uvoran4 -uv2r -2u1x -ux2a -ux1o -ux2or -ux3t2 -u1ya -2uz -uz3al -uz3ar -uz3eck -u4z3ela -uze2n -u4z3ene -u6z5en6tro -u4z3en4z -u6z5er6hoe -u4z5er4hö -uzer4l -u4z5erla -u2z3i2k -u4z3inf -uzi3o -uzo2f -uz3or -uz3ot -uzu4pf -uz3z2 -uz4zb -ü1a -1ü2b -2ü3ba -üb1ä -2übc -2übd -übe2 -2ü3bef -übe3le -ü3bem -übe5n4au -über3 -ü3bes -ü3bi -üb3l -üb5len -üb5les -üb1r -üb4s3te -2üc -ü3che -ü3chi -üch4s3c -ücke4n -ü5cken. -ück5erb -ü4ck5erf -ück5erh -ück5er6ke -ück5erla -ü4ck5ers -ück5erw -ü4ck5erz -ü4ckin -ück4sor -ü4ckum -ü2d1a2 -ü2dä -üde2c -üde2l2 -ü3den. -üden4g -ü3dens -üdi2r -üdi2t -ü2d1o2 -üdo4s -üdost3 -ü2d1ö -üdpol4n -ü2d3r -üd3s2 -üd3t2 -üdu2 -üd3ug -üe2 -üeb3l -ü1ei -2üf -ü2f1a -ü2f1ä -ü2f3ei -ü4f3ent -ü6fer6geb -üfe2t -üf3eti -ü2f1i -üf1l -üf1s2 -üf4tan -üf2t3o -üf4t3ri -üf4t3ro -üft3s -ü2fum -üge4l3a4 -üge4l3ä -ügel3d4 -üge4lec -üge6lei6s -üge4len -üge4ler -üge4lo -ügen3s -ü2g1l -ü2gn -ügs2 -ügs3t -üh1a2 -ühan3 -ühan5d -ü1he -ü2h3ei -ühe2m2 -üh3emb -üh3emi -ü4h3eng -ü4h3ent -ü4h3erf -ü4h3er4k -ü4h3er4z -ü2hex -üh1i2 -ühla4c -üh3lam -ühl3au -üh3lä -üh4l3ei -üh4lel -üh4l5ene -üh2lo -üh2l3ö -ühl4sin -üh3ne -ühn2s -üh1o2 -üh3rad -üh3reh -ühr5ei. -ühr5eie -üh4r5eis -ühren5e -ührens4 -üh3ro -ührungsgene6 -üh1s -ühs2p -üh5stei -üh3t2a -ü1hu -üh3w -ü1k -ül1a -ül2ad -ü2l3ef -ül3eim -ül3ein -üler3a4 -üler5ei -ülergene6 -ülerin6f -ülerin6t -ül3io -ül2la2 -üll3ad -üll3an -üll3au -üll3e4c -ül4l3ei -ül4lent -üller4s -üller6zeu -ül4lid -ül2l3o2 -ül2lö -ülls2 -üll3sk -ü2l1o -ü1lu -ü2m1a -ü4ment -ü2m3e2p -üme4r3a -ümerin6t -ü4m3erz -ümi2 -ü2m3id -ü2m3in -ümm2s -ü2m1u -2ün -ün1a -üna2c -ün2da -ün4d3ei -ünde4l5a -ündergene6 -ün2dr -ünd3s -ündungsgene6 -ün2dü -ünen3 -ü4n3erd -ü4n3er4l -ün2f -ünf3ac -ünf3ak -ünf3ei -ünf3le -ünf3li -ün2gl -ün3gla -ünkoh4 -ünkohl5 -ünn4st -ü2n1o -ün2st -ün3sta -ün5stei -ünste4r -ün3str -ün3t -ü1nu -ün2za -ünze4n -ün2z3i -ünzu2 -ün4z3un -ün2zw -ü1o -2üp -üpfel3 -üpf3l -ü1pi -üp2p3a -üp2p3l -ür1a -ü2r3ei -ü2r3e2l -ü4r3en4t3 -ür4f3er4 -ür4g5en4g -ürge4ra -ü1ri -ürk2l -ürme4c -ü1ro3 -üroe4 -üro5fe -ürom4 -üror4 -ürp2 -ür2rh -ürr2s -ür2st -ürte4l3 -ü1ru -ür4z3au -ür4z3in -ür2zo -ür2zö -ürzt4 -ür2zw -üsch5af -ü3schi -ü4schl -üse3e -üsen3e -üse3r4 -üse3s -üs3l -üsli3 -ü1s2p -üs2s3a -üs2s3c -üsse6l5ers -üsse4l5i -üs2s3o -üs2st -üst3a -üs4tei -üste4n -üs2t3h -üs4tl -2üß -2üt -ü2t3al -ü2t3au -ü3te -üte2n -ütens4 -üter3a -üte6r5eis -üte4rer -üterich6 -ü2t1h -ü2t1r -üt2sa -üt2se -üt2st -ütte4n3 -üt2tr -üt2za -üt2zw -1v -vab2r -2vabs -va1c -va3e2m -va3er -va3fl -v2ak -va3lam -val4en -val5ero -val5erp -val4lan -val4lo -vall3s4 -val4sa -val4sp -val4su -2v3amt -v3ansa -v3ansc -2v3arb -var4d3r -2v3arm -2v3art -va1s -va4s3ek -2v3ass -vas2t -va4s3ta -va2t3a2 -va4tag -va2tä -va4t3ei -vatem4 -va4t5emp -va4ten4g -va4t5enz -vat5ermi -va4tess -va2t3h -va4t3id -vatik4 -va4t3in -vati6ons. -va4t3op -va4t5ora -va4t5ord -va4torg -va2t3r -vat3sa -vat3s4p -vats4t -va2t3u -va2ty -vat3z -2v1au -2v1ax -2vb2 -2v1c -2vd2 -vdi2e -ve2a -ve3ak -2ve3al -ve3an -ve3ar -veb2 -ve1c -ve1e2 -ve4ff -veg2 -2v3eig -2v3ein -veit2 -veits3 -ve3l4ar -ve4l3au -velo5p -velo3s -vembe4 -2v3emp -vendi4 -4v3endu -vene4n -4v5energ -vener6sa -ve4n3in -venk2 -ven4t5a4g -4v3entd -vent4st -venu2 -venzer5 -ven6z5ere -ve3of -ve1p -ve3pn -ve2r1 -ve3ra. -ve5rada -ve5rade -ve5rand -ve5rane -vera4s -ver6b5art -ver5blei -ver5b4li -ver3d4 -vere2 -ve3rec -ve3reg -ve5reif -ve5reig -vere6in -verein6s5tr -ve3rem -ve3res -ver5est -ve4rf4 -ver5fl -ve4r3g2 -ve3rit -verk2 -vermen6s -vern4a -ver3n4e -ver4sep -ver5sta -ver3sw -ve4rt -vert6er -verue4 -ve3rus -verü4 -verz2 -ve2sp -ves3ti -vesti4e -vete3r -veto3 -ve1u2 -ve2ur -2v1f -vf4gh -2vg2 -2v1h -vi3ana -vi3a2r3 -via3t2 -vi2c -vid3s4t -vie4h3a -vieh3s -vie4l3a4 -viele4 -vie6l5er6z -vi4ene -vier5ad -vie4rec -vie4r5ei -vie5r4er -viers4 -vie2w3 -vig2 -vi2l3a -vi2lä -vi4l3e4h -vi4lei -viler4 -vi4l5ers -vi4l3in -vil4le. -vi2l3o -vi2ma2 -vi2n3a -2v3inv -vin4zin -vire4n3 -vi3s2a3 -vise2 -visen3 -vi3si -vis4o -vi5sor -vis2u -vit2 -vi4tam -vi3z -viz4e -vize3i -vize3s -2vj -2v1k2 -v1l2 -v3le -2vlic -v2lie -2v1m -vmwa2 -2v1n -v1ob -vo2be -vob4l -voge2 -vogel3 -vo2gu -vol4l3a -vol4lei -vollen4 -vol6l5end -voller4 -vol6lerf -vol6lert -vol4li -von3s4 -vo2pe -vor1 -vo4ra -voran6schl -voran6ze -vor5de -vo2re2 -vor3es -vo4rie -vo4rim -vo4rin -vo2ro2 -vo2rö -vors2 -vorsch4 -vor5ta -voy1 -vö2c -2v1p2 -v3ps -2vq -2v1ra -v1rä -v2ree -v3ris -2vs -vsau2 -vs2c -vson2 -v1s2p -v3sta -v3stel -v3steu -v3s2tr -vsu1 -v1s2z -2vt2 -vue1 -vu3en. -2vumf -2vumg -2vumk -2vv -2vw -2vz -1w -3waa -wab4bl -w2ac -wa3cho -3wacht -wach6tra -wach6trä -wa3chu -3waeld -4waeng -3waes -w3aeu -waffe4 -waffel5 -wa3ge -3wage4n3 -wa2g3n -3wah -wahlen4 -wah6l5ent -wah4ler -wah4l3i -wahl3s4 -wai2b -wak3l -wa3la5c -3wald -wal4d3a4 -wal4dec -wal4del -wal6denk -wal4din -wal4d3r -wa3le. -wa4les -walf2 -wa3li -wal2m3 -wa2lo -wal4t3a -walter5e -wal6t5erf -wal6terl -wal4t3h -wal4t5in -wal4t3o -wal4tri -walt6sta -wal4tur -3walz -walze4 -walzen5 -walzens6 -wal4zw -wam4mei -wa3na -wa4nau -wan4dab -wan4dan -wan4d5ar -wan4d5as -wan4d5eb -wan4dec -wandels6 -5wander -wand5in -wan4dob -wan4dr -wan4due -wan5ge -wangs3 -3wann -wan4z3a -wanze4 -wan6z5en6d -wan4z5er -wa2p -3ware -wa4ref -ware3i -warein4 -wa3ren -war4ma -war4m3i -3warn -war4th -wart4st -war3u -war4za -war4z5ei -war4zel -war6zent -3was -wa3s2a -wa4scha -wa5sche. -wasch5ei -wa5schen -wa4sch5l -wa4schm -wa5schun -wa4schw -wa3se -wa3s2h -wasser5e -wa3su -3watt -watte5s -3wäh -wäh4lei -wäh3ri -3wäld -wände5s -2wäng -wärme5i -3wäs -2w1äu -2wb2 -wbu2 -2w1c -2wd -we2a -we2b3a -webeat5 -webe3e -we2b3i -we2b3l -we2bo -we2b3r -web3sp -w2ec -we3cha -we5cke. -we5cken. -we5ckes -wecks4 -wee2 -weed3 -2wef -we2fl -3weg -we2g3a -we4g3ei -we4g5en4g -we4g5erk -weg5ersc -we4gim -we4gin -we2g3l -we2g3n -we2g3o2 -we2g3r -weg5s4a -wegs4t -3weh -wehr3a4 -weh4rei -weh4r5er4 -wehr3o4 -wei5be -wei4ble -weib4s -wei3de -weide5r -wei3dr -2weie -2weif -weifel6d -weifs4 -wei4gra -weigs4 -2weik2 -3weil -wei4na -wei6nenz -wein5erz -weinsau6 -wei4san -wei4sas -weiser6g -weiser6h -weisex5 -3weish -weis6sel -weis6spi -wei5s4tr -wei6takt. -wei4tal -wei4t5ra -weit3s -wele4n3 -wel4kl -welle4 -wellen5 -wellende6f -wel6schl -wel6schr -wel4t3a4 -welte4 -wel4teb -welt5ei -wel4tel -wel6t5en6de -wel6tent -wel4ter -welt5ere -welt5ern -welt5ers -welt5erw -welt5erz -wel6t5esc -wel4th -wel4t3i -wel4to -wel4t3r -wel4t3u -wena2 -wendegene6 -wendes4 -wen3f -wen4kau -wen4k5la -wen4k5ri -we2no -wens2 -we4r3a2 -werbe5e -werbe5i -werbes4 -werb4s3 -3werbu -2werc -werde5i -5werdens -3werdu -werer4 -werga4 -wer4g5am -wer4gan -wergel6 -wer6g5els5 -wer6g5en6t -wer4g3l -wer4gn -wer4g3o -wer4g3r -wergs4 -w4e3ri -werin4d -we4ri4o -3werk. -wer4k3a -wer4kä -3werke -werk5erz -wer4k3i -wer4k3l -wer4kne -wer4kop -wer4k5or -wer4k5re -3werks -wer4ku -wer4kü -wer3o -wer3ö -wer3s4h -wer6star -wer4ta -wert5ab -wert5ak -wert5an -wer4t5än -wer6t5eig -wer6ter6g -wer6t5erm -wer6t5ero -wer6t5erö -wer4t3h -3werti -wer4t5in -wer4t3o4 -wer4tre -wer4t5ri -wert5sk -werts4t -wer4t5um -wer4t5ur -3wese -wesen4s -we2sp -we4st -west3a -weste4 -west5ec -west5ei -wes6temp -wes6t5end. -west5ende -wes6teng -wes6t5erwei -wes4tex -wes4th -wes4ti -west3o4 -wes4tor -west3r -wes4tu -2wets -3wett -wet4ta -wett3r -wett3s -we1u -2w1ey -2w1f -2wg -whi2 -whi4t -w3ho -wicht6se -wi3cka -3wid -2wieb -wi3ec3 -wiede4 -wie5la -wien4e -wi2gl -wigs4c -wi3k -wik4i -3wild -wil4d3a4 -wil4del -wil4dr -wim4ma -wim6m5ent -wim4m3u -wi3na -win4d3a -win4d5e4c -win4d5ei -win6d5erz -3win4dr -wind5ri -wine2 -winen3 -win4g3l -win4g3r -wings4 -wing5se -win4kl -win6nerh -win6n5er6sc -win6nerw -win4no -win3sk -wi2r -3wirk -wir6kene -2wirn -3wirt -wi6schef -wi4sch5l -wi4scho -wi4schr -wis4e -wi5sen -3wiss -wiss4z -wis4t3r -wi3th -3witzi -wit4zin -3witzl -w2j -2w1k -2w1l -2w1m -2wn -wn2s3p -3wo1c -wo4cha -woche4 -3woh -woh4lei -woh4l5e4t -woh4na -woh3r -3wolf -wol4fr -wol4l3a -wol4lei -wolls4 -wor3a -wo2r3i -work3a -wort3a -wort3ä -3worte -wort5ei -wor4tel -wor6t5erf -wor6t5erh -wort3h -wor4to -wor4t3r -3wort3s4 -wor3u -wor3ü -wöl4fa -wöl4fo -4wölft -wör2g -2w1p -2wq -2w1ro -2w1s -wser3e -w3s2h -w2ska -ws2ki -ws2ky -ws2t -w2s1u -2wt2 -wti2 -3wuc -wuch4sa -wuch4sc -wuch4sp -3wuec -wue3le -3wuerf -3wuerg -wuer4s -5wuerst -2w1u2f -wun4d3a -wun4dä -wund3r -2wung -wun4g3r -wung5sc -wun2s -wunsch5ei -wunsch5l -wur4f3a -wur4f5ei -wur4fel -wur4fer -wur4f3l -wur4f3o -3wurm -wur4m3a -wur4mei -wur4mer -wurmes4 -wur6m5ess -wur4m3i -wur2s -3wurst -wurst5a -wus2 -wus3te -wu2t3 -wutz2 -3wüc -3wüh -3würf -3würg -3würst -2ww -2wz -x1a -1xa. -2xab -xa2be -xa2br -xaby2 -1x2ac -1x2ad -1x2a1e2 -xa3fl -1x2ag2 -xal3a -xal2l3 -xa2m -xa3mer -2xan -x2an. -xa2na -x3anb -3x2ane -xant4h -3x4anti -3x4anzi -xa2r -1x2as -2xau -xa2z -2xb2 -2xc -xcal2 -x1ce -x1ch -x1cl -2xd -xda2 -xdi2e -1xe -2x1ef -2x3e2g -2x1ei -2xek -xe2l -3xel. -xel3a -xe3lei -2xemp -x2en -xenk2 -xen3s2 -2x1e2p -2x3erl -xer4la -xer3s2 -x3erz -2x1eu -2x1ex -2x1f2 -x3fi -2xg2 -2x1h -x2i -2xia -xib2 -xi1c -xich4 -xi3dan -xide2 -xi4d3ei -xi4d3em -xidi2 -x3i2do -3xi2e -3xik2 -xi4lar -xi4l3au -xi4l3er -xi2lo -xi2l3u -1xim -x3imp -xin3s2 -1xip -xi2s3a -3xisc -xi2s3e -xi4s3or -xiss2 -xi2su -x3i2tu -2x1k2 -xkal2 -2x2l2 -x3la -x3lä -x3le -x3lo -2x1m -xmon2 -2x1n -x1od -xo2du -2xo1e2 -x1o2l -xonen4 -xo4n5end -x1o2r -xos2 -x2ot -2x1ö2 -2x1p2 -x3par -x3pau -x3ph -x3plor -xpor4ta -xpor6t5er6 -xpor4t5r -x3pu -2x1r -2x1s2 -x3sh -xst2 -2xt -xt1a -x2ta2b -x2tak -xt4ane -x2tau -xt1ä -x2tän -x2täs -xtblo4 -xtblock5 -x2t3e2b -x2t3e2d -x4t3eig -xt3ein -x2t3el -x4t3ent -x4t3er4f -x4t3er4g -x4t3er4k -x4t3er4l -xter4s -x4t5ersc -x4t5erse -x4t3erw -x4t3er4z -x2t3e2v -x2t3ex -xtfi4l -xt1h -x2thi -x2t3id -xti4la -xti4lin -xt5il4lu -x2t3im -xt3ind -xt3inf -xt3inh -xt3ins -xt1o2 -x2tob -x2tor -xtra3b4 -xtra3c -xtra3l -xt5rand -xt5raum -xt3rec -x4t3rep -x4t3res -x4t3rol -xt3s2 -xtu3e -x2t3um -x2t3un -x2tup -xtu5ral -1xu -xu1a -xu1e -x1um -xu2n -2xunt -xu2s1 -xusa2 -xuss2 -xus3t -2x2v -x3ve -xvil2 -x3vo -2xw -1xy. -1yac -y2ach -yach4t5r -yacht5s4 -ya1h -ya1i2 -y3al. -ya3la -y3alb -yan2g -y3ank -y3anz -ya2ro -ya3s2p -y1au -y1ät -yb2r -y1c -ycer4 -y4chia -ych3n -yd3al -ydri2 -ydrid3 -ydro3 -y1e -y2ec -ye2d -y2ef -y2el -ye2ne -yen2n -3y2eno -yer3a2 -y2eri -yer2n3 -y2es. -yes2p -ye2th -y1f2 -yfen2 -yg2l -ygro3s -y1h -yhr2 -2y1i2 -y1k2 -yke3n -yk3s -yk3t -yl3ac -yl3aet -y3lak -y2l3a2m -yla2n -ylan3e -y5lant. -y5l4ante -y5l4anti -yl3ar -y3lat -yl3ät -yl3c -y2le. -yle3is -y2l3es -y2l3e2t -ylinde4 -yli4ne. -yli4nes -ylk2 -yl4l3ak -yloid3 -yloni5e -yl3ora -yl1s2 -yltri3 -y1lu -2ym -ym4ae -ym4an -ym4är -ym4el -ym1p2 -ym4pha -ym4phor -ympi3e -2yn -y1na -ynae4r -y2n3an -ynä2r -yndro6ma -y3ne. -yne4c -ynk2 -yn1o -yno2d -yno2t -yno2v -yn3t2 -ynta2 -y1nu -y2od -yof2 -yoff3 -1yo3g -yog4a3 -yonal5s4 -yon4ne -y3ont -y3o2pe -yo3ru -yo1s -y3os. -y3ot. -y1ou -yout3a -2y1p -yp3a2b -ypa2n -yp3än -y2pec -ype4r -y4pesc -y2p3i2d -ypi2e -y2p3in -y2p3lo -ypo3t2 -y2p1s -yp3th -y2p3um -y2pü -y1r -yrae3i -yras4t -yrä3i -y2rh -yri5er. -yri5ers -yro1 -yro4s5te -yrr2 -2ys -ysau2 -ys2c -yse1 -ysein4 -yse2n3 -yse3s2 -y3sho -y3s2n -ys2po -yst4e -ys5tem -yste4n -ys4the -ysti5er -ys4tol -ysto4le -ys4tri -y3s2ty -ysu2 -y2s3ur -y1s2z -yte2n3 -y2tes -yt2h -yt4he -ythi3e -y3to -yto3s2 -y1u -yu2r -yure3 -yvet2 -yvon4 -ywurs4 -y1x -y1y -yze5r4i -1z -2z1aa -2z1ab -2zacc -2z3ach -za3cha -za3chä -2z1a2d -za2e -z3ae2c -z2aeh -2z3aem -4z3aend -4z5aengs -z3aerm -za3ess -2z3aet -4z3aeus -4z3aeuß -za3f4a -2z3aff -za3fl -za2ga -za3gr -3z2ah -zah4n5er4 -za3hol -z2ai -za3im -z1ak -2z3akk -za3kl -2z3alb -2z3ald -zal4e -2z3all -2z3alp -2z3alt -2z1am -2z3a2na3 -2zanb -zan3d2 -2zanf -4z3anga -4z5angeb -z5angel -z3angl -4z3angs -2zanh -zan4ka -zan4kl -2zanl -2z3anm -2zano2 -2zanp -2zanr -2z3ans2 -z4ans. -zanti3 -2zanw -zao2 -zapf3l -2z3a2po -zap4ple -2z1a2q -3zar. -2z3arb -2zarc -zare3s -2z3arg -2z3arm -4z3arti -zar4t3r -zarts4 -2z3arz -z1as -zas2b -zas2p -z1at -3z4aub -2z3au2f3 -2z3aug -2z3auk -3zaun -z3aur -z3ausb -z3ausf -4z3ausg -4z3ausl -4z3auss -4z3ausw -2z3auß -2z3aut -2z1ä2c -zäh4lei -zäh4lin -4zähnl -zäh3r -2z1äm -2z3änd -z3ängs -z1äp -z3ärg -2z3ärm -2z1ät -2zäug -2z3äus -2zäuß -2zäx -2zb2 -z3be -zber4e -z3bo -zbue3b -zbü3b -zby2 -2z1c -2zd2 -zdan2 -zdä1 -zdi2e -zdi5str -z3ds -zdue3b -zdü3b -4z3e4ben -zebra3 -4z3echo -zech3s -zed2 -ze4dit -ze1e2 -2z3eff -zef2l -zeg2 -ze4gal -ze4hem -zehe4n3 -zeh2l -zehn3a -zeh4n5en -zehns4 -zei5cho -zei3f2 -zeik2 -zei5le4 -zeilen5 -2z3ein -ze3in. -z4ein4a -zein4de -zei4n3e -zeinen4 -z4eino -ze5in4se -zeis2 -zei3sk -zei3sp -zei3st -zei4t3a -zei4tä -zeite4 -zei4teb -zei4tei -zei4tel -zei4t5er5 -zei4t3o -zei4tr -zeit5re -zeit5ri -zeit6sto -ze2la -zel5abr -zel3a4d -zel3ae -zel3am -zela4n -zel5an5e -zel5ang -zel3au -ze2l3ä -zel3d4 -ze4led -zelei6er -ze4l5eig -ze5leis -z3e4lek -4zelekt -4zeleme -ze4l3en -ze4l3er -zeler5e -ze4les -zel3ex -zel3id -zel3in -ze4linf -4z3e4lit -zel4lab -zel4l5ac -zellar6t -zel6lein -zeller6z -zell3s4 -ze2lo -zel3ob -zelp2 -zels4t -zel5ste -zel3sz -zel4tau -zel4t5in -zelu2 -zel5unk -ze4lus -zembe4 -zem4e -ze4mis -2z3emp -zen4ade -zena4g -ze5natt -zen3au -ze2nä -zende4c -zende4k -zende4s -ze3nec -zen5en5e -zen5erke -zen5ern -zen5ertr -ze4nerw -zen3f2 -4z5en4ge. -zenge6ne -zengene6r -4zengp -zeng4r -ze4nid -zen5inn -zenk2 -zen3n -ze2n3o -zen4san -zensch4 -zen4sem -zen4ser -zens4po -4z5entna -zent5rae -zent3s -zen4z5ar -zen6z5ers -zen6zert -zen6zerw -zen4zin -zen4zy -zeo2r -ze4pen -ze3p2f -zeppe3 -zer3ad -zer3ae -zer3a4g -zer3an -ze4r3at -ze2r3ä -6z5ereign -ze4rein -ze4remi -zer5end -zer5erw -ze4r3e4t -4z5erfol -4z5erfül -4z5ergae -4z3ergä -zer4gon -4z3er4gu -ze4r3i4d -zerin6di -zerin4f -zerin4h -zerin6te -5zerl. -zer6lang -zer6laub -zer4läu -4z5erleb -zer4lei -zerlo4 -zer6maec -4z5er4mäc -5zerme -zer4nan -zer4n5e4b -zer6nerb -zer5obe -ze3ros -ze4rou -ze3rös -2z3erq -zers2 -z5ersae -4z5ersat -zersau4 -4z3ersä -zer3sk -6z5er6strec -zer4t5a4g -zer4tar -zerte4 -zer6t5emp -zerten4 -zerter5 -zer6t5ere -zer6terl -zer4t5in -zer4ton -zer6t5rau -zert3s4 -6z5erwerb -zer4zä -z4es. -zes2a -zes3au -ze3sch -zes3er -zes4ph -zes4sa4 -zess5ag -zess5an -zes4seb -zessen4 -zes6s5end -zes4ser4 -zess5erf -zess5erz -zes6s5ort. -zes6s5or6te. -zes6s5or6ten -zes6s5orts -zes4spa -zes4spo -zes4sum -zes4s5ur4 -ze5stau -zes4tie -ze5stra -4z3e4tap -4zetat -2z3e2th -4zetts -zeug3a -zeu4gei -zeu4gel -zeu4g5e4m -zeuge4n -zeu6g5ersc -zeu4g3r -2z3eul -ze3um -ze3ur -2z1e2x1 -2z1f2 -zfen2 -2zg2 -zge3n4a -zger4a -zge6r5in6nu -zger4s5e -2z1h2 -zhau4t3 -zi3af -zi5alei -zi5aleng -zi5alim -zi3ang -zi2ar -zib2 -zi3cho -zich6t5er6 -zi4d3ak -zi4d3ei -3zie -zie4hei -zie4l3a -ziele4 -zie4l5eb -zie4lei -zie4l5er -zie4let -zie4l3i -zi3ell -zie2n -zierde4 -zie4r5ei -zi3es. -zi3ess -zi3est -zi3esu -zi3esz -zif4fr -zi3fl -zi4gec -4z3i4gel -zig3sp -zi4kon -zil4e -zi4mit -2z3imp -zim2t3 -4z3inde -zi4n5er4z -zi4n3et -zi4n3in -4z3i4n3it -zin4kae -zin6k5erz -zin4k3l -zinks4 -zin4na -zin4sa -zin5sas -4z5insel -zin4ser -4zinsuf -zi2o -zio3d -zi3on. -zi3ons -zip4a -zirk2 -zirks3 -4zirri -zi3s2z -3zit -zi4t3an -zite2 -zithe4 -zi2t3o2 -2zj -2z1k2 -z3ko -zko3ef -z3kr -zkue3b -zkü3b -z3ky -2z1l -zlan6d5er -zlei5ti -zle3s -z3lo -2z1m2 -zmau4s -2z1n2 -zna5che -z3ne -znebe4 -znei3 -2z3o4as -2z1ob -2z3oe2d -zoe2f -3z4oeg -2z3oek -4zoel. -4z3oels -z1of -zo2gl -2z1oh -zo4lig -zol4l3a4 -zol4l5ei -zol6l5en6t5 -zoller4 -zol6lerf -zol6l5erk -zol6l5erl -zol6lert -zol4lin -zol4lo -zolls4 -zoll5ste -z3o2ly -3zon -zon3s2 -zon4teb -zon4t5er -3zo2o -zoo3s -2z3o2pe -zop4f5en -2zopt -2z3ora -z3orc -z3ord -z3org -zo2ri -zor4n3a -zor4ner -2z3ort -2z3osz -zo2t3a -2z1ou -zo2w -2z1ox -zo2ze -2z1ö2d -2z1ö2f -3zög -2z1ök -z1öl -2zöl. -2zöle -z2öli -z2öll -2zöls -2z1p2 -z3pn -2zq -2z1r2 -z3ra -z3ru -2z1s2 -z3sa -zsau2 -zsch2 -zse3r4u -z3sh -zsi1 -z3sk -z3sl -z3sp -zst2 -zsu1 -zsui2 -2zt -zt3abr -zta2n -z4t3an3e -z4t5anze -z2t3ar -ztee2 -zte3eh -zt3ehe -zt5eins -z4t3ent -z4t3erz -z3t4es -zte3s4k -zte5str -zt3hel -z2t3hi -z2t3ho -z2thu -zt3ind -zt3inf -zt3ins -zt4or -zt4ram -zt3rec -zt3s2 -z4t3urs -ztz2 -zu1 -zub2 -zucht5o -zucht5r -zuchts4 -zud2 -zudi4e -2zueb -zu3ebb -zu3ebn -zue2k -zu2el -zu2en -zu3ent -zu4erc -zu4ern -zue4r3o4 -zu4ers. -zue2t -zu3f2 -zu4g5ans -zu4g3ar -zu4g3at -zug5eis -zu4g5ent -zugg2 -zug5inn -zu4gla -zu4glä -zu4glö -zug2r -zug5rau -zug4san -2z3uhr -zui2 -zu3le -2z3ult -zu3lu -2z3um. -2z3umb -zumen4s -2zumf -2zumg -2z3umh -2zumk -2zuml -zu3mo -2zumr -2z3ums -2z3um4t -zu3m2u -2z3umw -4z3unab -zun4am -zu3n2e -4z3unem -4z3unfa -4zunfä -zunge4n5 -4zunget -4z3ungl -zungs3 -4z3u4nio -2zunr -2z3uns -2zunt -2zunw -zuo2 -zup4fi -zu3r4a -2z3urk -2z3url -2z3urn -2z3urs -2z3urt -zu3s2 -zu5sch4 -zus4e -zus4p -zu3t4a -zu3te -zut4r -zuve4 -zuz2 -2züb -zür3c -2zv -4zwaer -4zwaes -2zwaf -2zwag -2zwah -2zwal -4zwand -z4wang -2z3wap -2zwa2s -zwasse4 -2zwäc -2zwär -2zwäs -2zweb -z2wec -4z3wech -2zwed -2zweg -zweg3s -2zweh -5z4weig -4zweil -4zwein -zwei5sc -zwei6t5er6k -2zwel -2zwen -z4werg -4zwerk -4zwert -2zwes -z2wic -2zwid -zwi2e -4z5wied -4zwild -5z4wing -4z5wint -4zwirr -4zwirt -z4wisc -4zwiss -z4wist -2zwur -zwur4m3 -2zwü -zy3an. -5zyklu -zy2le -zy2m3i -2zz -zz2a -zza3t -z2z3id -z2z3i2k -zzi2n -zz3ini -zzi3s2 -zz2o -zzol2 -z2z3or -zz2ö -zzug4s -zz2w -} - -\endinput - -%%% Local Variables: -%%% mode: tex -%%% coding: utf-8 -%%% End: diff --git a/assets/hyphenation/en-us.dic b/assets/hyphenation/en-us.dic deleted file mode 100644 index a09009145..000000000 Binary files a/assets/hyphenation/en-us.dic and /dev/null differ diff --git a/assets/hyphenation/en-us.pat b/assets/hyphenation/en-us.pat deleted file mode 100644 index 92dc61f3d..000000000 --- a/assets/hyphenation/en-us.pat +++ /dev/null @@ -1,1633 +0,0 @@ -% DesignCraft US English hyphenation patterns (Liang/TeX notation). -% Generated by crates/compose/examples/hyphgen.rs: trained with DesignCraft's patgen-style trainer -% from the Moby Hyphenator II word list by Grady Ward (public domain, Project Gutenberg #3204). -% Released into the public domain (CC0-1.0) by the DesignCraft contributors. -'2c '4cl '4do 'emil4 'emili4 'exi4 '4fa '4fl 'ha4 'immor4 '4in 'l2 -4'll 4're 2's 4's. .a2 .a4ba. .a3bas .a4b3er .ab3h .a4bia. .a4bie .a4bil -.ab5ir .a4bl .ab3la .a4bo. .a4bos .a3b4u .a6cetyl. .a4cl .a5coe .ac3t .ac4te .ac4u -.a4cus .acu4ta .a4cy .ad4ded .ad4der. .ad6din .a4dh .a4di .ad3ia .ad3ip .a4don .ad3um -.a4dur .a4dy .ae4r3 .ae4ta .af1 .af3t .a4gan .a4g3a4t .ag4e .a3gen .agis4te .a3gl -.a4glet .a3gu .aile4 .ak2 .alar4 .ald4 .a4ley .al4fa. .ali4as. .al3ie .al3im .a5lin -.al4ist .alk4 .al5ky .al3l .alli4e .alm4 .a3lon .alth4 .amalgam5 .ama4li .ama4ra. .amato6ry -.amb4 .ame4l .a4ment .ami4r .a4mosi .amp4 .an1 .a4na. .a6nac .an3ar .a3nath .an3c -.anchis4 .an3g .a4n3i .an3k4 .an4nem .an3or .an3s .an4tal .an4tat .anti3a .anti5f .anti3s -.an3y .a3o .a4pal .a3par .ap3at .a6perie .a4phar .apho3 .aph3or .a4plan .ap4le .a5por4 -.appe3t .a3pr .aqui3 .arbo6rea .archa5i .ar4chan .ar3d4 .are4l .ar3g .ar2i .aria4 .ar4ia. -.ar4io .ar4iu .ar4mes .a3rou .ar4rie .ar3s .arsen6 .ar4sp .ar4sy. .ar3t .ar4tier .ar4tin -.ar4tl .ar4ty .as4a .as5ce .as4d .as4e .as3p4 .as4r .as1s .ass4e .as6teria .as4tom -.as6tron. .as4u .at5ar .a4t4e .ate4l .ath3a .ath4em .a4to4 .ato2m .atom3i .at4ty .au3b -.au4rou .aus3 .aus4a .a4val .a4vat .a4ven .a5veng .a3vi .a4vid .aw4r .ax3 .ax4en -.ay4l .a3yu .a4zi .b2 .ba4bel. .ba2bi .bach3 .ba4cl .ba4di .ba4ga .ba4la. .bal4lu -.ba2lu .banda4 .ba4ni4 .ba4r .ba4san .bas6tia .bate4 .b4b .b4d .be4a. .be2d .beef3 -.bel4lo. .be3ni .ber2a .ber4i .be3str .bev3 .b4h .bi2al .bi4e .bi4lan .bill4a .binar4 -.biz4 .bk4 .b4l4 .ble4 .bli4n6 .b4m .b4o .boan4 .bod2e .bo4ral .bor4t .b4r -.brea4 .bre4c .brel4 .bres4 .bre4t .bri4t .bro4s .b4s .b4t .bul4bar .bun4di .bunk4e -.bun4t .bu4ri .bz4 .c2 .c4a2 .ca4bel .ca4dil .ca4le .cal3o .ca4mas .candi4a .carbon5y -.ca4ro. .car3ol .ca4t .cat4as .cat3e .catter4 .c4b .ce4br .cec4 .ce4lan .ce4li4a. .cens4 -.cer2a .cer4i .ce3r4o .cess4 .c4h4 .che4m3 .chi4g .chiv3 .chol4i .cho3ro .chthon4 .church5 -.chy4p .c4i .ci4a .cic5e .cid4e .cin3 .ciner4 .ci4s .ci4t .cit3i .cit3r .cit3y -.c4l4 .cla4d .cla4r .cle4l .cli4nal .cli4t .clu4 .c4n .cnid4 .co3ed .co5et .co4let. -.co4lo. .co4mat .convin6 .cop2e .co4pl .copol5 .co4p3y .corn4in .corre5s .cotti4 .cotu4 .cout4 -.cow5a .cp4 .cret4 .cre4ti .cro4ma .cro4p .cro4se .cru4d .c4to .cu4mi .cun4 .cu4rati -.cur4b .cur4vy .cu2t .cutin3 .c4v .cya4ne .cyan4o .cy4n .cys6tic. .cy4th .cz4 .d2 -.d4a2 .da4ce .dacty4 .da4li .da4m .da4ni .dar4ia .dar4i4u .dar4te .da4to .da4vit. .dd4 -.d4e .de4an. .dea4t .de2bo .de4bra .de4cil .de4lis .de4mi. .de4moi .de4mons .de4mos. .dent4 -.de3r4a4 .dern4 .de3sce .de6sert. .de3t2 .de4vil .de4war .dez4 .di4a. .di6able. .di4al. .diar4y -.didy4 .di4f .di4le .di4os. .diot4 .diri6gi .dis3 .disar4m .d4m4 .d4na. .do2 .do4co -.dola4 .dol2o .dors4 .dous4 .do4v .dow4y .dr4 .dre4 .dro4 .dro2m5 .d4s4 .d4u -.du4ca .dul4la .dun4nit .du3r4a .du6rativ .dus4 .d4v .dy4f .dy4nas .dy4s .d4z .eac4 -.ea3g .eak4 .eal4i .ear4t .east3 .ea4sy .ea4te .e2b .eb3o4 .ech3o .e4cla .e2cle -.e4clo .e4col .e4con. .e2cos4 .e2cot .e4da. .e4deli .ed4h .ed4it. .e4do. .ed4t .ee4 -.eer4 .e4f .efflu6e .eg4ge .e2i .eid4 .el4a .elab3 .e6later. .e4lay .eld4i .e3le -.e4lea .e4lect. .el3e3g2 .el4egit .e4len .ele3p .el4f .el4i .eli4as .e4lie .e4lio .e4lip -.e3lis .eli4so .e2ly .e2man .em3ana .e4mer .e4met .em5igra .emil4 .emili4 .emi4r .e4mis -.e4mog .e4mor .e2na .en3act .enam3 .e3name .en3an .en3c .en4cy. .en3e .enervat6 .enes4 -.e4nosi .ent2 .eon4 .eo3si .e2p .eph4 .epho4 .ep3ic .epid4 .ep3in .e4pio .e4po -.e6quine .eq3ui5t .e6quiva .er2 .e1ra .er4e .erg4 .e4ri .eri4d .er3m .e1ro .ero6g -.er4rab .er4ri .e4s3 .es4op .eso3t .es4u .et4al .e3te .e3ther .eth4yl. .e4tia .e4tow -.etu4 .eun4 .eu4ra .e4va. .e4van. .evapor4 .e4var .e4vat .ever2 .evers4 .ever4t .eve3ry -.evo4c .e4wan .ex3 .exhaus4 .ex4or. .ex5u .ey4a .f2 .fanat3 .f4b .f4c .fe4me -.fen4d .fer4a .feri4a .fe4ta. .f4f .f4i2 .fi4do .figur5at .final4 .fi6nan .finea6 .fin3g -.fis4ti .f4l .fle4 .frin4 .f4s .fu4el. .fu3si .g2 .g4a2 .ga4da .ga4m .gam3i -.gam3o .ga4p .ga4ri .gar4th .ga4s3 .ga4th .g4b .g4c .g4d .ge2a .ge4li .gen4f -.geni4a .genom4 .gent4 .gen3tl .geo4me .ger4ent .ger4i .ger4o .ghi4 .gi2 .gian4 .gi4b -.gin5ge .g4l .glass5 .g6le .gle4a .gluti6na .g4n .gne4 .go4l .go4me .gon3o .go4r -.go4s5p .gou4t .g4r .gra4ce .graduat6 .gra4p .grip4pa .gro4g .gu4g .gu4ri .gu4t .gy4b -.h2 .ha2 .ha4c .hae2 .ha4g .hag3i .ha3la .hal3i .ham5ad .hand3i .han4g3 .hank4 -.ha4no .haran4 .has4k .has4t .hast6i .ha4v .he2 .he4a .hec3at .he4i .he4la. .hel3ic -.help5 .hem3i .hem4ic. .hemis6 .he3mot .hemo6tr .hem4p .hep3a .he3red .he5resi .her6in .hero4p -.he4s .hi3b .hi4c .hi4dr .hi4lary .hil5d4 .hiller4 .hil4li .hil4ly .hi4m .hin4der .hin4g -.hi4o .hi4s .hi4t .h4m .ho4a .ho4do .hoe4s .ho4i .ho4la .ho4le .hol3o .ho4ma -.ho4mon .ho4my .hoo4 .hoosi4 .ho4p .ho4rac .ho4sen .ho4th .hov5er .h4s .huff3 .hurs4 -.hy3g .hyo4 .ia2 .iae4 .i5am .i4as. .i4ast .ia4t .i4ata .iat3r .i4br .i4ca -.i4cel .ich3 .ich4i .i4da. .idel4 .i4des .i4d3i .i3d4l .ido2 .idol3 .i4e .if4u -.ig4f .i4gl .ig3n .i4h .i4j .ikar4 .ile4a .i4li .ili4s .ilk4 .il4lima .illo4 -.il4lus. .i4lo. .i2m .im3age .imi4d .im4in .im4it. .im3m .i4mo .im4ph .im5pin .i4n3 -.ina4m .inc4 .in5ce .ind2 .in4dic. .indi6cat .ine4a .in4gly .in4k .inow4 .in6suran .i1o -.i4oa .io4b .i4or .i4ou .i4pa .ipi4 .iq4 .i4ra. .i4ran. .ir2i .i3ric .ir3id -.iri4s .i4ro. .ir3r .i4si .i4so. .i4ta. .i4tac .ital3 .i4tal. .i3tem .i3ti .i4to. -.i4tu. .iv2 .ixo4 .j2 .jame4 .jh4 .joli4 .juni4 .ju1r .jus3 .k2 .ka4le -.kal4i .ka4li. .ka4te .ka4ti .k4c .ke4g .ke4p .ke4r .kh2 .ki2 .kian4 .ki4b -.kil1 .ki4l3o .kin3em .k4l .kle4s .k4n .kni4f .kold4 .kon4e .ko4ni4a .k4u .k4w -.l2 .l4a2 .la4br .lab5y .la4cy .la4ga .lalish4 .la3ma .la4me .la4na .lan4dr .laps4ib -.lar4g .lar4i .las4t .lat3er .la4th .la4ti .lati6na .latin5i .lau2d .laz3a .l4e2 .lea4 -.lear4 .le4b .lech3e .lecith4 .le4gat .le4m .lemo4 .len4g .le4om .lep3r .le4pu .le4va. -.lew3i .l4h .l4i2 .lid4i .lie4 .li4f .li4go .li4me .limo4 .linar4 .lin4eag .li3o4 -.lis6ter. .li4tu .l4l4 .lo2 .lo4chi. .lo4g .lo4i .lo4n .lo4p .lor4in .lot4i .lo4v -.l4s .l4u .lub4l .lu4e .l4y .ly4b .ly4l .m2 .ma4bi .ma2c .mack4er .maga3 -.ma4la. .mal3ad .ma2n .mand4 .man4is .mans4 .mar4ia. .mar3ti .ma4thi .ma4tin .may4er .m4b -.mc3 .mensu4 .mer4a .mer4ise .merit6e .me4s1 .met4al. .me6te6or. .meth6yl. .me4to .m4i .mia4 -.mi4a. .mi4d .mif4 .mi4na .mi4ne .mines4 .mi4n3i .mini4a .mi4s1 .mis4er. .mk4 .m4la. -.m4m .m4ne .mo2 .mod1 .mo4da .mol4o .mo4no. .moth3 .mou4 .mould5 .mo4v .m4p -.m4s4 .mul3 .mun4ga .muni4 .munis4 .mus5ke .mw4 .mye3 .my4i .my4t .n2 .naba4 -.na4bat .na4ch .nadi4 .na4i .nal4g .nan4i .na4ri .na4to .na2v .naw4 .n4c .n4d4 -.n4e2 .ne4bri .neb3u .ne4gro. .nem3at .neph3e .ner2 .ne4th3 .neva4 .ne4vi .n4g4 .n4i -.ni2c .ni4da. .ni4e .ni4ne .nish4 .nit4e .n4n .n4o2 .no3e .no4m .no4n .no4r -.nor4thi .no4s .noy4 .ns4 .nu4i .nu4n .nur4s .ny2 .o'4 .oax4 .obe3l .ob5elis -.o5ber .o4bit4 .o4bl .ob5la .ob5lo .oblon4 .o4br .o2c .o4ca .oca4l .o4cel .o4ch -.och3l .od4 .odel4 .o6donta .o4dr .o2dy .oe6co .oes3t .o4h .oil3 .ois4 .oit4 -.ok4 .o3le .ole4c .ol3ig .ol4la .o3ly .o4mad .on4as .ond4 .on4de .on4eros .oni4 -.onk4 .on3o .ood4 .o5og .o3o3ph .oos4 .o3pa4 .o4pec .ophe4 .o3pin .o2r .or3ato -.orbi4 .or3c .or3d .o5ron .ors4 .ory4 .os3 .os4a .o4sa. .o4sc .os4i .o4st -.os4tl .ote4 .ot4i .o4tr .ou4 .ous4 .out3 .ov2 .over3 .o4ver. .o4vu .ox3 -.oy4 .o4za .p2 .p4a2 .pa4b .paci4 .pa4k4 .pa4li .pan5at .par1 .par3at .par4ch -.pa4ri. .park4e .parti4a .part4ib .pe4a .pear2 .pearl3 .pe4ba .pec3t .pe4lik .pel4te .pend4 -.pe6nult. .per4a. .pe4r3ia .per4lie .peron4 .pe3ru .pet3a .pe4ti .p4f .p4g .ph4 .pha4c -.pha4n3 .phar3i .phil4ia .pho3n4 .phtha4 .p4i2 .pic5r .pi3ge .pi4mo .pin4dl .pin4er .pi4ol -.pi3ra .pi4t .pit3y .p4l .plas3t .plat3i .pli6catu .po4dite .po3e .po4et. .po4k .po4lis -.po4ni .po4p .pop5i .pop5l .port4e .post3a .post3h .post3o .p4r .pre3ad .pre4cat .pre4lat -.pre6sent .pri4c .pro3a .proc4i4 .prolo6gi .pro4phy .propos6a .p4s .psa4 .p4t .pter4on .ptot4 -.ptyal4 .p4u .pur4ri .put4ter .p4v .q2 .qi4 .q4t .qua4m .r2 .r4a2 .ra4be -.ra4bid .ra4ce .ra4cy .ra3d .ra4da. .ra4du .ra4f .ra4gi .rain4e .ra4pi .rap4pis .ra4to. -.rave4 .rawl4i .r4c4 .re1 .re4an. .recho4 .re4col .rec5oll .rec3re .red4ded .red4din .red5in -.redriv6 .reduc4e .re5end .re4go. .re4ist .re3ma .ren4ta .re4par .rep4i .re4rem .res4t .rest4i -.ret2 .re3te .re4trib .re3vi .r3ge .rgen3 .rhag4 .rhet4 .rhe4to .rhe4u .rhot4 .ri2 -.ri4ba. .rib4e .ri4bl .ri4co .ri4e .ri4i .rio4 .ri4sh .ri4ta. .riv3e .r4ma .r4n4 -.r4o .ro4ba .ro4bi .rob4o4 .ro4ch .ro4g .rol4lin .romani6a .ro4p .ro4q .ro4san .ro2sc -.ro4set. .ro4sin .ro4sy .ro4th .rough3 .ro4ze .rp4 .rs4 .r4sa .rt4 .rud4e .ru4in. -.ru4l .run4i .runt3 .ru3r .r4w .ry4c .s2 .sa2 .sa4do .sak4e .sali6na .sal4is -.sal4te .san4de .sani3 .sa4t3in .satur4a .sa4ve .s4c4 .sca4li .scar3a .sca6tol .schil5 .sci3e -.s4e2 .se4a .se4lag .sel4e .self5 .sel4i .sel4li .sem3 .se4pa .sepho4 .ser4ice .ses3 -.se4v .s4f .s4g .s4h4 .shan3t .shing4 .shov3 .s4i2 .si3al .si4b .si4de .sid4eri -.silk3 .si5lo .sin4ga .sin3g4l .sin3i .sis3 .s4k4 .s4l .sli4 .s4m .smal4 .smar2 -.smell3 .smok4in .s4n .sna4 .sne4 .so4a .soap3 .sodom4 .so4lute .so4me .som5er .son4a -.soni4 .sot4e .soth4 .s4p4 .spel4 .spen4 .sper4o .spi4c .spi3d .spi4e .spi6ral. .spi4t -.spo3ro .s4s4 .s4t4 .sta3li .stal4l .ste4n .ster4ic .stin4 .sto4le .sto4ria .stort4 .stot4i -.sto4v .sub5l .sug3a .s4w .sy2 .sy5ring .sz4 .t2 .ta2 .ta4b .tac3i .ta4gr -.ta4l .tal4ly. .ta4mi .tani4a .tanis4 .tanta3 .ta4p .tark4 .tas4si .tati4 .t4bil .t4c -.te4 .tecto4 .tel1 .tel3a .tel3et .tel4li .ten3d4 .ten4o .ten4ti .ten4ty .tep4 .ter4as -.ter4ent .ter4me .termin4 .ter4mo .teu3 .th4 .the3a .tho4m .tho6rax .thro4 .t4i2 .tia4 -.ti4f .ti3ge .til4lag .timet4 .ting4 .ti4p .tisa4 .tita4 .ti4ti .to4e .to4i .to4li -.to4mal .tona4 .to4p .tor4c .to4th .trache5 .tra4s .tra4v .tre2 .tre4a .trep3o3 .tristi4 -.tri4ti .tro4po .t4s4 .tsu3 .t4u .tu4r4i .tuy4 .t4v .t4w .twin4i .ty3ro .tz4 -.u2 .u3b .uc4 .uca4 .u4da. .u4di. .udo4me .ugar4 .umen4 .un3 .unch4 .und2 -.under5e .under5s .u4ne .un5er4 .une4s3 .un5et .ung4 .un5in3 .unk4 .u4no .un5p .un5s -.unsel4 .un5t4 .unta4 .u4p1 .u3ra .ura4l .u3re3 .ure4ti .u3ri .u4rial .u4rid .u3ro -.u3ru .u4ru. .u4sa .u4si .usur4 .uta4 .ut4e4 .u3ti .u3tr .ut4s .u3v .v2 -.va2 .vac3i .va4len .val4is .va3p .vapo4 .var5ic .va4s .vas3o3 .v4c .ve2b .vei4 -.vel4la .ve3l4o .ven4er. .ver4se .ver4sy .ve2s .ve4ti .vi3gn .vi4ne .vin4i .vi2t .vi5va -.vivipa6 .vi5zi .vo2l .vol5v .vo4r .v4r .v4z .w2 .wap5 .ward4er .war4e .wc4 -.whirl3 .wi4 .wk4 .wo4m .wr4 .x2 .xe3r .x4i4 .xn4 .x4t .x4v .xy3 -.xyl4 .y2 .ye4 .yes3t .yh4 .ym4 .y4o .yog4 .ys4e .y4u .yw4 .z2 -.za4c .zar4i .za4s .ze2 .zh4 .z4i .zi4b .zo4i .zo4n .zo4oty .zori4 .z4v -2aa2 aad4 aa4f aar4d aar4i ab2 4a4b. 4aba. 4abag 4abaj abal2 a4bala -a3ball abal4lo a3ban aba4ri 4abasi a4basine abas4si 4abas4tro a4batea a4batis a4batt 4a4b3b -abba4s ab3d abe2 4abee a4bef a3bel a4bela abel3i a4bell. 4aber a4berc ab3erd -a4bern a3bet ab3ho 4abhu 4abia4 ab3ica abi4d a4bidit a4bien a4biet ab3if 4abile -4abilia abilis6e a6bilism abiliz6e a4bily 4abin4 abini4 a4bino ab5irr a4bist 2abi2t abit3ab -ab3itat ab3j 1a3bl a4bla ab3lat ab4lin a5bling ab3lis a4blu ab3n 2a2bo 4abo. -4aboa 4a3bob 3abolic ab5o5lise ab5olism ab3o3liz ab3olo abon4 4abor ab3ora 3about a2br -4abra. a4brad 4abran abras4 4abria ab3ric ab3rog a4b3s4 4absh absin4 a2bu a4bu. -ab3ul a3b4un 4abur a3but 4aby abyl4 aby3r abys4s 4aca. a2cad acan4d a5cap -4acara acare4 ac3a5ro 4acas. acas4se ac3c4 4acch acci4a ac5edl ac1er ac4er4o acess4 -acet2 ac3eta 4acetam ac5eton 2ac4h 4ach. 4acha achal4 a3chan ache3a ache4t a3chian -a4chic ach5ic. 5achiev ach5inat a6chiop ach5ronis ach3ry a5chu 3achut a2ci acic3 ac3id. -ac3ido a3cien ac5ifie ac5ify ac5ile a3cili aci4nate 2a3cio a6ciology 2acit ac3ita a4city -aci4u ack4erel ac4l a4clav a3cly ac3m acoat4 a4coco acoid4 a4coli aco4pa aco4ph -acorpo4 a3cou 5acoust ac3ram 4acre ac1ri ac3rob ac3roc acros4c acrot4 ac4so 4actac -acte3a ac3ti actuar4y acu1 ac3ua a4cue ac3ul ac4ulifo a3c4um acun2 a4cuse a3cut -a3cy a4cy4l a2d 2a4d. 4ada. a4daea a4daeu ad5aman 3adap 4a3das adav5e a4dc -a4dd4 4addai ade4a. ade4mia 3ademic 6ademici a3denc ad3eno a3dent ad3er. ader4y adev4 -a4df adge4t a3dha 2adi a3dia adia4p adiaph5 a4d3ic. a4d3ica a4dice a3dii a4dina -a4ding a4dinit a3dio adio3t adi4ph ad3ipo adi4ra adis4c a4dish a3d4it a4ditiv a4dito -a3diu 3adju 4adk adm4 4admil 3admit a4dn 4ado. ado4ce ado4e a4doma a4dona -a4doph adop4te ador5ab a4dot a3dou 3adox adria4 ad2t a3d4uc a4d3ula ad3umb 3adv -adwom4 a4dyn 4adyr adys4 2a2e 4ae1a ae1di ae3g4 ae4gl ae3i ae2l ael3it -ae1m ae2ma aem3or ae1n aen4i aeo4logu ae2r aer4ar aer3o1 3aerob aer4on. aer4ono -ae4sal aes3t aes4ta ae3t4 ae4tat aeth4i ae1u ae4vu aey4 a2f 4af. a3fa -a4fae a4fag af4e 4afe. afem4 affi4c a4fia a4fie a3fil a4flat a3fo4 afor4 -afri3 a3fu af4y4 4ag. agan3d aganis4t agar4ic. a4gath a4gaz ag3azi ag4c 2agea -agean4 agel4i ag3ella age3na 4a3ge2o a4geol ager4y age2s ages3i 4aget 4ageur ageus4 -4aggi aggo4 4agh aght4 ag1i a3g4ia agi3an agiar4 ag3ic. 4agie agin2 ag4ines -agio2 ag2is ag2l agli4 a3gly 4aglyphy agmen4 agn4 4agna ag4nac agni4s agnoli6 -a2go ag3oc ag1on 4ago3n4i agor4i a5gos 3agou agout4 ag3p a3g4ra a4grag agra4p -a4grapha a3g4re a4grop agrue4 agth4 4agua 4ague a3gui agus4 a2gy ag4y. agy4r -a3ha aha4li aha4n a3he ahe4m a3h4i ahi4e ahil4 ahi4m ahli4 a3ho aho4g -a3hu a1hy 2ai2 4ai. ai4a ai3b a1ic ai3ca a3i3ci ai4d aig2 ai5ko -ail3ab ail3i ail4ia. aim3ab aim3er 4a4in ain3ag ai4ne ain3i ain3ou ain2t ai4o -a4ip4 aiq4 air3l air3m ais4e aish4e a3ism aiss4 a3isti aite4 aitor5 a4ity -aiu4 aiwa4 4aj. a3ja aja4m a4jer a4jl a4jn a3jo a4joi 2ak ak4al -aka4n a3kar ak1er ak4erli ak4et ak4ey ak1i a3ki. ak4ings ako4i aks4 aky4 -ak3y. al1 4ala. 4a5laa al3ab al4abr a3laci al3a3co ala4f a3lak a4lal. 4alam -al3ama al3amu al3an4c a3lang al4ar2 a4laro al3ata al4ate a4lath al4ati a4latt a4laty -ala4zi albur4y al3c 4alci 2ald4a alde4b ald4er alder4a 4aldi al4dis 4aldu 4ale. -ale3a a3lee. aleer4 alem4a a1len a3lene. a4lenti al5entin a3leo a3lep aler4a a4les -ale4so a5less. 4alet aleth4 a3lexi a4leya alfi4 al3g4 4ali. 2ali2a a3l4ia. al3iat -alib4 2alic a4l3ic. al3ida ali2e a4lifa a1l4ig ali4ge a4liger a4lika al5imen al3ing -a4linite a4lio ali4ol alipa4 al5ipe 6alipo alipu4 alir4i 4aliste alit4e al3iva alk3at -allad4e alla4l al4lena alle4t al4leye al3li 2allic 4alloce 4allop 4alloth 4allu. al3ly -al2mer al4mic al4min almon4d alm4s al2o aloblas4 a4locy al3ogiz a1lon4 a2lony a3loo -a3lopi al4ora al3ori a3los aloy4 alp4 4alpa als4i al4sie. 4alta. alth3i al4tin -al4tis 4alu. a3lu3b al3ue al3ui a5luk a1lum a3lumi a3lumn a1lus a5lus. a2lust -a3lute a3luti alv4 al3va aly2 4alye 4alyn a3lyp 4alysan 5alyse 4alysi a3lyst -1alyt 1alyz al4z 2am. a2ma 4ama. am3ab ama3c ama3d4 am3age am3agi amak4 -a4maka a4male ama4lia a3man a4mandu. ama4ne a4manit ama3ra amas4c a4mash a3matic am3atis -am4atis. am3atiz am3ato am3atu a3maze 4amba. ambar4 am3bi ambi4er 4ambo 4ambr am4da -4amele ameli4a a3men ame3na amen4d amendato6 ame4ne. ament4a a4mentab amenta5ry ameral4 am5eris -am3ero ame4si ames4o ami4b 2am1ic a3micr am4ien am3if ami4la am3ily ami4m am3ina -ami6nize amis4e am3ito a5miu am3iz am3l amme4d5 4ammel ammis4 amn4 a4moi am5ois -4amon a4mora a4mort a4mosit a4moy ampa4nu amp3ing ampi4r am4por ampu3t 2ams am3sh -am4sp 4a4mue a4mul 4amun amy4a amz4 a4n. a5naa an4abase ana4basi an4abr a3na4ch -ana4din an4adr an3aer an3age an4al. anal6ise anal6ize. an4all an4ame4 an4amm anan4i 4anao -an3arc ana4ri. anari4a anar4ia. an2as 4anasy a4nat. anath3 anat3ic ana4tr anau4 4anca -anchan4 an3chi 3anchol 4ancht anci6nate 4anco. 2and and3ag andes5cen andev4 an4dies. an2dis -andoo4 an4dow andown6e an4drac 4andre. 4androp and4t 4andu andu4a an4dun a3nea. a3nean. -a3nemi ane4mon a3nen4t ane4p 4aner an4erg a4n3est an3eta ane4to 2ang 4ang. angar4 -anger4y ang2l an4glan an3gli angol4 an4gou angov4 an4gres an4gru an3gu an1i 4ani. -an4ia 4aniac 4a3nial a4niar an3ic a4nic. an5ica an5icis an5ics a3nicul an4id a3nide -an3iel ani4er an3if ani4la an4ilit ani4lo 4an4imi 4animo a3nine a4n3ing 4anio a3nip -a4nipl an4ir an3is an5ish anis4tr an3ito ani4toi an3ity 4a3niu ani4y an3iz 4anke -ank5ly an3l ann4 4ann. annar4 an4nema an4nia a3noc4 an3od anogam6 a3nol. 4anoli -a1no4m a3noma anon4g an3onis a4nopo anora4 a3nosi anoti4 a4nour a3nov a4nove ano4vi -4anow4 an5ox an3q 4anr an2s ans5co anse4l an5sen an4sil ans3l ans3m an4soc -anso6phi ans3pa anspar3 ans3po an4sure an4sut an4tac antal2 an4talk antar4 4ante. an4ters -anth4 an4thor anti3e 3antio 4antis 4anto. anto4c an4try an2ty a4nu. an4ub an3uf -an3ul an5umi a3nut anx3 an3ya 2anz 2ao a4o. ao4a a4ob a4oh ao3li -aos4 ao2t aout4 a2p 2ap. 4apa. 4apab a3pac apach4 a3pal a4pala a3pann -apar2 a4parin ap3aro a3part ap4bl 4ape. 4aped apel4e ape4r 4aper. a6peries ap3ero -a4pete apeu3 a3pezi aph3ano a4phare a3phe a4phes a4phot 4aphrasi aph3ro3 a4pid a3pien -ap3illo ap3ing a4pio ap4is ap3ita a4plana ap4len ap3li apo4c a4poe ap3oge ap3ola -a3polo a4p3o3m4 apon4a a4ponin a4pon4ism a4ponit apop4 apo4ph ap3opl apo4re apor3iz apor4t -a4posp apos3t a4posto a4potac a3pou app4 appe3ti 4appi appi4a. ap4ri a4pric aprog4 -aproge4 a4prop apro4pi apro4s 3aptat 4apte. ap5ule a5pun 4a3pur a4puri a1py a3py. -apyret4 a4qa aquil4 aquin4 aqui4v aq4w 2ar. a1ra. ar2ab ar3abe arac4i a3rade -ara3di a3raj 4arak ar1al ar4alo ar4amb ara3me aramel3 ara4mu ar4an. a3rang aran4gu -ar4ani aran4tis a4rap ar1as4 ar2at ara3th a3ratio ara3v4 arawa4 arb4 arbal4 arbalis4 -arbe2 arbel3i ar3bi arbor5i arbu3r arbut4 4arca ar4cant arce3n ar4chang archiv4 arcion4 -ar4cla 2ar2d ard3er. ard3is ard3ous a4rea ar3ean 2ared a1ree areli4 4aren ar3ent. -ar5eou 4a4r3er ar4ero a3res4s a4rev ar3gic ar4gre ar3gu 4arguar ar1h a2r1i a3ri. -4ar3ia aria4d ar4iali a4ric ar3ic. ar4i4cl ari4dea ar3ies a3rieta ari6ette arif4f a4rify -arimi4 a3rina. ar3inat a3rine. 4a4r3ing a3rini ar4int ar3io 4arion ari4oso ar2is ar3ish. -ariso4 ar3ison a5riss ar4it. ari3ta ar5ity ar3iu a3riv 2ariz ar3ko a4rl arla3t -ar4lite armag4 4arman arm3er. ar4mes. armis4 armon6g armo5n4is armo5niz 4armos 2ar1n ar3na -arnel4 arnet4t ar2o a3ro. ar4ob 4aroc a3roch aro4i aro4mat. ar5omatic arone4 a5roo -a3rop a3ros4e a1rou aroun4 2arov ar3ox ar4per arp5ing arpos4 arr4 arri4an. arris4t -4arrit arrol4 ar4sl ar4sn ar4sph arta4n ar3ter 4arth. 4arthe ar4thor ar4tom a1ru -4aru. a4ruc a3rus 4arv arv4e arves4 ar3w ar1y arya2 a4rz 2asa asag4 -4asal asal4a a6salt. asan4i a4sanit as3an4t a4sav as3c4 ascar4 asca4t as4ch as4cop -as4di a4sec as3ect aseli4 a4sent 4as4h ashal4 ash3er. ashi2 ashim4 ash4o a4shout -ash3y asi2 as4ia asi5al a3sib 4asic asi4d a4sidim a4sif a4sila a4silin a4silis -a4sin4 as4ings asionar4 a4sir 1a3sit ask4e asl4 as4ly as4met as1mi 2aso a2soc -aso3li a4som a4sore a4sori as4pas as3pe as3ph as5pira aspi4ran as4pr 4asse. assem4i -as2sh assis4 associ5 asso4l assor4 as4sp 4astad astat6in astau4 as4tele as3tell as3ten. -as3teri as6teria. as3tero astho4 2as3ti astil4 as4tin as4titic astna4 as3t4o aston3 as3tr -as4tray as3tu a2s1u 4asu. asu3a asu3i as4un 4asur a3sus 4asv a4syli 2at. -a2ta a4tab at3abi atag4 ata5la atalo4 atani4a ata3ra atas4 a3tax at4ch atchet4t -a2te a4teb at3e3ch at3ego at3en. at3ena atenar4y aten4te a3teri at4erin ater3n a3terna -a5ternit at5erniz a6tersi a4terso ate4v a2th 4ath. ath3em a4th3e3te a4this. 3athist 4athoc -athomor4 a3thy. 3a3thyr 4ati. at1ic atica4 at3icis 4atico a4ticoe a4ticu a4tida at3ies -a2t1if 4atig at3iga ati2l 6a6tilise. 6a6tilize. a4tilo 4atim a4time at3inat 4at3inis 6atinize -4atino atir2 a4tire at3iris a3tise a5t4iti at3itu ati4us ativ3i a3tiz at4ja 2atl -at5li atmos5 a3to 4ato. 4atoa a4tob a4tocu a4togl a4togr a4toh a4tois a4tola -a4toli a4tom a5toma a5tome at3omis at3omiz a5tona ato3nis 4atook a4topa a6tophi atopho4r -4atora a4tosp a4totr at2r a4tra atra4p a3t4re 4atri. a4triate atri4cle 4a4trico at5rics -a3tricu 4a4trid a2t3ril a2t3rim 3atriz a4troc atro4n3 a5tron. 4a4t5rophy 3atrou a3try 4ats -ats4k at4sp at4su a4tt at3te atter6ie at4tho a2tu atu4an atuar4 at3uat at3ue -a4t1ul a3tum atu3ra at3ural a4turat 4aturis 6aturiza aturn4 4atus. at4wai a2ty 4au. -aub2 auc4e a4ud au4dato aud2i au3dib audo4 audu3 aud3ul au3e au4fi aug4h -augi4 3augur 4auh au3i aul2 auld4 auli4e aulo4m aume4 aumer4 aumi4 aumon4 -4aun4 au5na aun5de aun5dr auni4 au4nit aupa4 au1r auran4 auri4o auriti4 au4rous -4aus. au4sa aus5ativ 4ause au3sib auspi4ce auta4m 3au1th au3ti auti4e au4to. autu4 -4av1ag ava3li a4vanc avar4y a2vas av5asa a3vat a4vata a2ve 4ave. av4ed ave2l -a4velo ave4no av3erag aver4an aver5i ave4sa 4avi a3v4ia av4ic a4vidl avi4et a4v3ig -avi2o avi4o4le av3ish a4viz av4o2 a4voir 4avon avo4r avor3i a3vou a3vu 4aw. -a3wa 3away 4awb a2wh aw3i aw4k awl4e awl5er aw4ly awm4 a3wo aw4pe -a2w3r aws4e aw3ye 4axa axa4l ax4eni ax3i axi4b 4axim axim4e 4axo ax4yl -2ay 4ay. ayac4 ay2an4 a4yar4 a3yat ay3l aymo4 ayn4 ays4t ay3t2 ayth4 -ayy4 az2 a2za az3ard aze4d a5zell a1zi 4azi. a4zic a3zie a3zine az3ing -azi4r4 a4zl. azo4la a4zom a3zy. a4zygou 1b 2b' 2b. 3b4a. baa4 ba3ali -4bab babel3 b3abil ba6bilis ba4br bach3e ba4cla bac3t bac4ulif b4a3da badar4 b4adic -ba4din baen4 ba4gat 3b4ah bah4n 3bai bair4 ba4je bak4erl bak4l bal3an4 ba4lata -bali4o balis4te ba4lit ballas4 balle4 bal4lo. bal4lup bal4mi 4balu ba4m b2an 3ban. -ba3na4 banda4n ban4der. ban4e ban3i ba4ni4a ban4il 4bano banse4 bant4 ba4p bapos4 -bapt4 3bar. 4baran bar4bl bar4cl bar2i bar3ic ba4rin ba3riz bar4my bar3og barris4 -bart4 bas4a ba4sani bas4es. b4asi ba4sil. 2basis bas3k bas4king bas4sis bas4tron 3b2a2t -4batae 4batean bate4s bathe4 bati4e 4batis. bat4s batter6i 4batto 4bauchee baw2d baxillar6 -ba3ya ba4za 4bazit 2bb2 4b4b. b3b4a b5bab bbal2 bba4si 4bbe b4bed bbe4s -b4besi b4bev 4b3bi bbi4a bbi4na bbi4t bbit4i 4b3b4l b5bli b3bo 4bbu 4b3by -2b1c bc4i4 bcres4 bcun4 2b1d2 4b4d. bde4m b3di bdisti4 b3do b3du b2e -2b4e. bea4h be2a4n3 3bear beas4 beat4r bea3v be4bi be4br be4ce 2b4ed 4bed. -be3da bede4v 3bedie be2dr 3bee bee4f 4befa befal4 bego4 begoni4 be3gu be4han -3bei2 be3ia 4belai be4lan 4belar 4beld4 4beles 4belet be4lia. 3bell belle4 bellig3 -4bellik 4beme bemi4 4benda ben3ed be3nev3 ben4i be3nig be4nis 4beno 3benz b1era -b3eral 3berc 4bercr berdi4 b4ere be3rea b4erg4 bergi4 3b4eri be4ri. ber4im beri4o -be4rios berlin4 3bern b4ero 4berose b4ert. ber3ti ber4tie ber4tit b4eru 4b4es. 4besias -be4sier 4bes4n be3sp be3stow bet1i be4ton be3tr be3tw be4v bev3e bev4er. 4bevi -bevil4 be3w 3bey beyo4 be4ze 2bf bfoun4 2b1g bga4 bgen4 2bh bhav4 -b3hor bhyoi4 b2i 3b4i. 3bia 4biae bial6iza bia4n bi4anc bi4arr bia4t bi4b3 -bib4a bib4it 2bic b4ich b4ici 3bicu 3bidd 3bidi bid3in 3b4ie biel4 4bieta -2bif biform4 bi2g bi4gl bigo4 big5ot 4biki 3bil1 4bila bi4land 4bilen bi4les -bil2g bi3lin bi3lise bi5lisi 4bi3l2o 4bilyn 4bin' 3bina bin2d bin4e bine4t bin4ga -bini4e 4binog 4bino4s bi4nou 4binso 3bi1o bio5e bioe4c bi4ol. 4biotr bi3p bipa4ro -3biq bir2 bir4c bir4l birm4 b4is bis4e bish3 bis4p b3ited 3b4itio b3itiv -b3itor bi4tram b4i2tu 3bitua 3biu biv4i biz4e 2bj b4j. b3je b3ju 2bk2 -bk4c b2l2 4b4l. blag4 4blah 3b4las blas5p blas4ty 4blat 4blau b4laz 3b4l4e1 -ble2a ble2m 4blem. blem3at ble4nit ble4p bler4 ble4sk 4bless 4blet 4bl4i b3lic -b4lica. b3liga bli4gato 2blik 5blin6d b4ling bli4ni b3lish 4bloi 4blol blou4 blri4 -blu4e 4blum blun3d 4blut 3b4ly 2b1m bman4i b3m4e bmer2 bmi2 2b1n 4b3ne -4bni b3no bnubi4 bo2 3bo. 3boa4 boan4e 4boant boas4 bo4b 3bod1 bod2h -bod3ie b4oed boeti4 b4oh 3boi bois4 bok4s bo3la bol4e bo4ley 3bolic 3bolish -b3o3lit bo3lo bo3lu bomba4b bom4be bombo4 3bomi b2on2 2bonai bonar4 bonav4 bon3i -bonse4 bo3nu 3boo 3bor. bo3ra bo4rab borac4 bor4al b3orat borbi4 bor3d bor4ic -4borin. bor3o. bor3ou borre4 3bort bor4ty 3bo4s bos3ci bos4si bos3w b4ot botan4 -bot4e bo4th 3bou boun4c boun5t bou4r bous4t bo3va boxyl3 3boy 2b1p bpe4t -bpro4b 2bq b2r2 4br. 3bra 4braca bra4ce bra4che 4brado brail4 bra4ne brank4 -brant4i bra4r bras2 bras4e bras4si brau4n 4brawo 4brd bre2 brea4t bre4co brel4o -bre4m 4brep bres4c bre4th bre4to 4brev. bri4b b4rici 3bri2d brid3i 3brie 4brifa -4brifuge bri4g brig3an 6brillato brind4 4brio. 4brioi 4brion 4brios 4briu bro4a bro3g -b3roga broge4 broid4 broi5de brok4 brom2 bro3mi bromi4n 4bron. bro4ph bro4sy broth3 -4brua brun4 bru3ne bry1 4bryc 2b1s2 4b4s. b4sb b3s4c bscon4 b3se 4bsh -bsi4 bsin4t bsis4 bsist3 4b3so bsor4 bsorb5en 4b3s4t bstra4 bsur4 2b1t2 bt3ab -bte2 bter4m bti4 b4u. bu4be bubin4 bub4o bu4chi buco4l budge4 bu4gou buil4 -build3 4buj 2bul bullar4y 3bullie bul4lo bulu4 bume3ni b4un4a 3b2und bun4dis bu4ne -bun4ger bun4t4i 3bur bu5ra bu1re bu4ria buri4at bur5ie bu3ris bu3riz bur1y 3b4us -busi4e buss4 bust2 bus5tib bus3y 4butan b3utar3 3butio b3utiv b3uto but4s 2b1v -2b1w 3b4y. by4b b4ye 2byl4o 2byr by3ri b5yrin byr4l bys4e bys4sa bys4t -byte4 by4z 2bz bze4 bz4i 4c'd 4c't 2c. 1c2a 3ca. 4caa. 4caaa -cab3a3li ca4bell cab3ine cab4o 3cac cacci4 cach3i ca3ci ca4cit ca4co cac3o5d caco4p -2cad4a cadav5 2cade cade4mi ca3den cael4 caeni4 c4ag cagel4 cag2o ca3gon. 2cah4 -cahi4 3cai 3cal. ca3lad calde4 cal4di ca4len ca4li. cali4fa cali4g calit4 cal4lin -cal4mi 4calog cal3or cal4pi 4calus ca4ly. ca4mak 4cambo 4camen cam3er 4cam4et cami4e -ca3nar canari4 can3c4 can3d4 4canedi can3k4 ca5noe can5ol cano5p4 4canor cant4a can4tic. -can4u 4caoni ca3pa cap1i 4capl 4capod. capon3i cap4r car1 car4an carbor4 ca3ree -car5eo ca3res4 care4u cari4sa carmi4n car3oli car4ter. car4tes car4tho carti4e car4v ca2s -cas4a cas4ser casso4 cas3tel cas5tle 4c3asty casty6la casu3 2c3asy cat1a catani4 cat3e3c -cat3eg cat4er. cat4eri ca4th cath3e3t ca4ti. 5catio 4catiz cat4j ca5to 4caton 4catoo -4cator' catri4cl catter4y ca3t4u4 ca4turas cau4s cav5er2 ca4v3i c2b2 c4bi 2c1c2 c4c. -ccar4y c3ce ccel3 ccentric6 ccep2 ccept5an ccessar4 ccha1 ccha3r4 c3ci cc4l c4cles. -cco3l ccompa3 ccoun4 ccout4 ccun4 ccu4san 2cd cdi4 cdo2 cdo4t cdou4 cd4y4 -2ce2 4ce. ce5a. 4ceab ce3ani 4ceanog. 3ceas ce3b ce4bri cec4i cecili4 4ced. -ce3da 5ceded 3cedi 3cee ce3f ceg4 ceger4 3cei2 ceip4 3cel. 4celain. 4celeu -3cell ce3lo celo4n 4celot 5cels cel4t 4cely ce3m 4cem. ce4met 3cen 5cenc -4cend cend3a cend5ent. cen4e 4cened 4cenes 4cenet cen4ic 4cenin4 ce3no cenogra6 ce4not -censur5a cen4tag 4centar 4centati cen3ter cen6tic 3ceo 4ceor 3cep 4cepe ceph1 cephe4 -4cepl 4cepo 4cepr 4c1er. cera4m 4ceran c2erb 3c4erc 3cere cer3em 4ceri4n 3c4er2n -cer4ne cer2o 4ceros cer3ou 3c4ert 4certil certin4 4certina c4e3r4u cervi4e 3ces 4cesa -ces4co 4cesh 4ceso 4cessa 4cesse ces3si 4cest. 4cesu 3cet. ce5tab c3etal ce3tat -cet2i 3cetic ce4til ce4toi cetu4 3cety 4cetylc 4cetylic 4cetylmo 4cetyls 3ceu ce3w -4cfo cge4 cgre4 ch'4 4c4h. c4ha 3cha. 4chab ch3abi 4chaelm 6chaeoc 4chago -3chai cha3l4a 4chalo 3cha4m chan4cy chan3d 3chan4g 4changel 3chanic 4chanici cha4p char4g -char4ti 3chas 3chat chatu4 chaza4 4ch3b4 chcom4 2ch3d4 2c4he2 4che. 3chea4 cheap5 -3check. 4ched ched4di chei4on che5k che3li 4che3ma 4cheme 3chemi che4m3o ch4en. 4chenbe -4chener 3che4no che4p cher4t 4ches. che4te 5chette chet4tin 4chf 2chg 2chh ch2i -3chi. 3chial 3chiat chi4cle chi2co 3chid 4chies 3chiev chi4ge chi4l5i chilo4m 4chilu -4ch3ily 3chim 4chin 5chin. chind4 ch5iness 2ch3ing 3chio chi4oe chirp3 chir4ri 3chise -3chit 4chitar chiv3a chiz5oph 4chk 2chl chle3a 3chlo 4chloi 4chly 4chm chma4 -4chn4 c4ho 4chocy 4choe 4chogy 3chon 4chono 4chony 3ch4or chor4i cho4ry 4chosph -cho2t chot3o 3chow 2chp chr4 4chroi. chro5niz ch3rym 2chs 4cht4 ch3te ch3th -chu4l chur4 chura4 chu4t 4chw4 ch4we. ch3y3d 3chym chy4pr chys4 4chyto 1ci2 -3cia cia4l ci4alite cia4n ci3ar 4ciare ci6ata 4ciatic 2ci3b cic3at ci4chl 4cici -3cicu cic4ulus 4cidas cid3i 3cidif 3cidit 3cidu 4cidur 3cies. ci3est cie3t cif1e -ci4fic 3cific. 5cifical c3ifis ciform4i cig4e4 4cigr cil3ab 4cilat cil3i 5cili3a cil4ie -4cil4is 3cilit4 4cilite 4cilitr 3cilli 5cillu ci3ly 2cim 2cin 3cin. ci3na ci4nac -c4inatio 3cine. 4c3ing 3cini cin4quef 5cinth cioe4 4ci4ol. 3ciou 2cipa 4cipe ci3ph -ci4p3i 4cipic cip4is cip4it. c4i3p4l cip3ro 4ciproci cip4ule 4cish ciss4 4cista 4ciste -2cit cit3ab 3c4ite. ci4thi cit3i3z citur4 3ciz 2c2k1 4ck. c4k3a ckas4 4cke2 -ckel3i ck3er cke4t ck3i ck3l ck5le 4ckm ck3o cko4h 1c2l2 4cl. cla2 -4cla. 4cla4do4 claf4 4clame clam3o 4clamp clan4g 4clarati clari4no cla4ro 4clasi clas4p -class4es clat4u4 c4l4e 3cle. cle2an 2c5lect cle4la cle4m cler4a cli4f4 4clik clim4 -5clinab clin4g5 cli4ni clin4is 3clip cliq2 cli4to 4clium 4cliva 3cl4o 4clogi 4clogu -4clomy 4clonu clo3p clos4t clothi4 clow4 cl4u2 clu4b 4cluc clu4d 4cluse 2cl4y2 -c4lym cly4p 2cm2 4cma 4cme 2cn c1ne cn4i 1co2 3co. co3ad coar4 -c3obit coble4 3coc co4cal cocar4p 4coche 4cocot cod4ic coenter4 co3er coffi4 4cog. -cog1i 2cogn co3h cohol5 3coi co3la co4lan colas4 col4e co4ley co3lit col3l4 -colli4e col2n 5cology co5lom 5colon col5oniz 5color color3i 2colou 3com co3ma co4mato -com4be comed4 com3er com3pi com4t 3con. 2cona con2al 2conci concy4 con4dr conger4 -3conic 4co5nid con4if con4is co5nit co3niu conni2 6connoitr 3cono conspir5a cont2 contan4 -con5ti coo3e cooker4y coo4la coo4p cooper4y co3or3 co3pa co4pag 4cope co4phy cop1i -4copic cop3ie 3co3pis co3po copol5y co4pr 3copy3 co3py. co3q 2cora co4rac cor3al -co4ram co3rat 4corb c4ord cord3an cordel4 cordi4 4cordl core4la cor4ic 4corism cork4e -corn4e 2coro coro3n4 4corp. 3corr cor4rel. 3cor3t cos3tal costi4 costu4 co4su co3ta -cot4er c4otia co4tic co3tin cotin3i co4tit 4coto4s c4ott cotti4e cotu4l cou5ple cour3a -6coustici cous4y cout4e couter4 3cov1 co3v2a co4ve4 cov4el cov3er covet5 covi4 co4w -4cowa cow5h 4coz2 cp4s c1q 4cq. c4quem c4queri c4ques. cr2 cra2 c4rack -2c3racy crad4 cra3n4i 4crano cra4p crap3i crap5u cras5t 3crat. 4c3ra3tes 3cratic 4c3ratiz -crawl3 3cre. cread4 2crean cre3at 3cred1 credit5ab cre6mas cre4natu crene4 cre4p crep3i -2creta cre4te creti4 cre4vi cre4w 4crg cri2 c4ria cri4b 4criba crib3er c4ridi -cri5f c4rin 5crine 4crinou 4crip 4cris cris4e c4risi crit1 5c2rit3i cro3ch cro4dr -cro4f cro4mar c4ron. c4ropi cro4pl cro4po c4rops cros4ci cro4set crosi4 crot4e c4ru -4cru. cru3c4 cruf4 crum4b 5crus crus4e cru3t cruti3 4cry. cry4ly 5crys 2cs2 -4cs. 4csi c4so. 4csp c3sta 2c1t 4c4t. 4cta c4tab ctag4i cta4r3 ctar4y -4c2te c4ted c3t4er c4ter. cter3is cter4o ctex4 c4tg cth4 4cti c4t1ib c3tic -cti2m3i c3tina c4ting cti4nos c4tish cti4vi ctiv6ise. ctiv6isti ct3l 4c3to cto1m ctomor6 -ctori4e ctro3c ctrop4 ctro3t c4ts 4ctu ctur3a ctyl3i cty4log cty4loi ct4z c2u -cuad4 cub2 cu4be cu3b4i 3cue 4cuee c4uf 3c4ui cui4a cuis4 cu1l c1ula -c3ula. cu4lam c4ulare c4ule 3culia cullo4 1cult cul5ta cul4te 4cultie cul4tis 3cultu -1c4um. cu2ma 4cuman c4umb 4cume cumen4is 3cumi cu4mo cum1s 5cumu c4un cun2a -cund4 4cundin cu5n4e cun4h 3cu3n4i cun5n cu4ph 4cupl 1cur curan4 cura3r cu3rat -cu4rativ cur4bl cur4d cur5dl cur4f c4u1ri cu2rin cu2ris cur4l 6currency cu4rum cur4vey -3c4us 4cusan 4cusec cuss4 cus5sin cus3t cu4tanc cutch4 cu2te 3cuted cuti4e 3cutin -cutin3i 3cutio c3utri cuy4 c2v 4cx. 1cy 3cy. cy1a cy4bel 4cyc. cy4cla -4cyl. 4cyla cyme4 cyn3i cy3o cy2p cy3ph 4cypod. cy4s cy4tiu c2z cz4e -4d's 2d. 1da 3d2a. 2d1ab d4aba d2abe d4a4br 3dac da4ces 4dach4 da4di -3dae dae4ni 2dag d3age. da4gr da3gu da4hi dah4l 4dahy 3dai daik4 dail4 -4dair dak4e da3la da4le 4dalid dal4if 4dalir4 da2m2 dam3ag dama4n d5amant damas4 -da4mo damp4ie 3dan. 3dana dand4 3dane d4ang dan3ge 4dani. dani4l 5dantic dan4tr -d2ap 4dapho 3daptab 5dap4te 3dapti dar2 3dar. 2dard d3ardi da4ri 4daris dar4i4us -darmer4 d4art das4tu 3dat dat4c da4til datilo4 da4tol da3tu dau4b daugh3 dau4r -dau4t 3dav dav5er da3vi daw4t 4daxi 3day 2db dblind4 2d1c dcas4 4dcl -dcol4 2d1d2 d4d. d3de d3dh d3di d4dib 4d3d4l ddys4 4d4e. 1dea deaf5e -deal4a de3am de1an de4as dea4th 4deaw de2bit de4bra. de4but 1de1c 3deca deca3d -de2cag de4cane 4decar de4cast de4cat de3ce 4deces de4cile de4cili de4clo decol3 dec3o3ro -de3c4r 2decto 2d1ed deda4 3ded3i 4dedl de3du 1dee deem3 def4 4def. de4fini -def5lag de4flor deform6i de1g4 3de1i 4deico de4im de4k dek4e de5ki 3del. de4la. -de4lano del4at del3eg dele4m de4li. de4lia deli4a. delib3 deli4e de2l3iq de4lis. de4lit -de3lo 3delp del4st de2lug 1d4em dem3ago deman4 dema3t de4mero 3demic dem3ic. 4demicia -de4mo. demon3o demor5 1den d4ena 3dena. de4nar4 d4enb 2denc d4enci 4denes deni4e -de6nol d4ens. den3sa d2ent 2dent. 3dental dentar4 d4ente4 3d4ent4i d4env de3o d3eog -3deol 1d4ep de3pa depau6 de3pic dep6ilato de2pu d3eq 3d4ere der5el de3rid 4derie -der3k4 derle4 3derm der2n d4erne dern3i der4ob de5rogato d4er4ol d4erop der4or. d6er3os -3derou derpi4e d5ership der3t derto4 4dertot 1des d4es. desal4 desar4 des4c descar4 -3desce des3ec de4sei des3ic des5igna 4desn de2so des3o3l de3spon des3ti 3destri 4desy -3de1t de3ta de3te de4ti de4to det2r de4tri 3deu de1v dev3as3 de3ve devel3 -de4ver dev3il de3v4o de4war. 3dex 4deye dez4i 2df dfas4 4dfo4 2d2g dg5ab -d4ge dgel4i dge2o dg1er dge4ta dg5ete dg1i d3gr d3gu dg3y 2dh 4dh. -d1h4e d4hel d4hen 3dhi 4dhib d3hou dhun4 dhy2 3d4i. 1di3a 3d4iac 4diad -3diae di4aln d4iam di4am. dia4n dias6tro 3diat dia4z 2di3b di4bat d1ibl d4ibo -di4bu 3dib3ul di4cam d3icap di3cea dic4ed di1ch d4icin4 d5icine 3dicit 4dic4l diclin4 -3d4ict dic4te 3diculo 3did didy4n d4ie. 2died 3di1en di4erl die3v 5d4iff d3ifi -d4ific. d4ifu 3dig 4digal di2g1i 4digr 4digy di3i di4lem dil3et di4l3i 3d4ill4 -4dillu d2im2 di3mi4 dimin3 di4mis di4mou 2din 3d4in. 3dina 4dinag dina4l di4narc -din4e 3dine. din4gu 3dini 4dinite di3niz din4kl di3o 3dioa di4oc. 4dioco dio4de -3diog dion4 di4or. diora4 d4iori dio3te diother6m d4iph dipha4 di4pla dipo4c dir4 -di4ral 3di1re di4ref di4rel di4ren di4res 4d5iro dirt3 d2i4s1 3d4is. dis4ant dises3 -dish4a dis3i dis5pi diss4 dis3t2 dis5ti d1ita 4ditat dith4 3diti 6ditione. d3ity -3d4ius 1di1v2 dives4 div4i 4divid. 4diviv di3vo 2d2iz 4dja djective6 d4jes d4ji -d4jt 2dk d1l2 2dla d4lac 3dle. 3dled dle4i 3dlel dles4 d4lest 3dlew -2dli 2dlo dlor4 2dlu4 2dly 2d1m 4dm4e dmin3 dmin4d dmis3 4dmo dm4s -2d1n 4d4n. 4d3na dney4l dni4 4dno 1d2o 3do. do1a doac4e 3do3b dob4l -dochei4 do3cr doc3um do3de doder6mi 4dody 3doe dof4 4doff 3dog 4dogr 3doi -4doit do2la dol4g dol3ic do4l3is 2dolit dol4li do3lom dol2or do4lou dol4u 3d4om -domen4 do3mes do4mete dom1i dom3ic 4domid do4mine do4miz 4domou 3don 4donai 4donij -do5nisti 4donit do2nou do4ny 3d4oo doo3d4 3dop dop4a dope4 dophi4 4dopto 3d4or2 -4dorat 4dor4ic dor3if dork4 dor3m dor3ou dort4e 3dos do4sag doss4e doster4 3do2t -do3th 4dotr 4dotu do4u dou4c dous4t 4dout do2v 4dovary 4dowa dow4ie 4downe -4downi 3do4x doz3i 2d1p2 4dqu 1dr2 4dr. dra2 dra4b 4drace. drag3on dra4gr -4drail drala4 drav4 dra4w dr4e 5dre. dre4i dres4se 4drh 3dria dria4n dri4b -4drik d4ril driple6 4droe. droga4 drogen3 3droi 4droll d4rom 4dromal drom3ic 4d5romou -4dromy 4droot drophobi6 drop4o dross3 4drow dru3i dru3p 4drut dry4e 2ds2 ds3b -d3sc 4d1s4e d1sh d4shu 4dsl ds3m ds4mi dso4 dsor4 d3s4p d4spo d1s4t4 -dsth4 d4stin dswor4 d4sy 2dt d4ta. d4tes d2th d4tia d4tis 1du 2d1u1a -dual6iza d4uar du3bi dubi5t du2c d3uca 3duce 3duci 3duct duct4in 3due du3el -du4gou duik4 2dul d3ulen d4ul4i 3dult 5dum. dum4bl d3umbr dume4 3dun dunc4 -4dung du4ni dun4nite 4dup. du4pa du4pe du4pin dup4l 3dupli 3dur 4durol 3d4us -dus4i dus4ta d1v dvan4c dvert3e 4dvi dvi4se 2d1w dwar4 dw4e d2wel4 dwif4 -dwifer4 1dy dya4n 4dydi dy4fe 4dyl. 4dyll. d4ym 3dy1n 4dyno dyo4 d4ys1 -dys5e dy4su dyte4 dz2 4dza4 dzo4 2e. 2ea2 e1a. 4e3a3b e4aba e4abe -eabi4 each1 each3i ea3co4 eac4s e3act e2ad1 eadapt6 eaddress6 4eade ea4d3i e3adj -e3admi e3ador e3ae eaf5en ea4fi ea4fy e4a3ger ea3gl ea4hi eak1 e4ak. eal3a -eal4at eal3er eal3i ea4lis eal3l e2alm eal3o ealth3 ea3ma e4am3er eam3i eam3y -e1an. e3and e4ane ean3er ean3i e3anic eano4r e4ans ean4tr e4apa e4ape e4api -ea4py ear3a ear4at ear4c e2are e4argua ear3in e4armo ear2n ear2t eart3e e4ar4th3 -e2arw ear2y easan4 eas4l eas4t e4ast. east3e easty4la e3ate. eat3en ea4t3er e4ath -eathe4n eath3er ea4thy eat3ie e3at4if e3atio e3ativ ea4to. eator4i e1atr eat3ri e4a3t4u4 -4e2au eaus4 eav3en 4ea4vi ea3w eb2 4eb. e3ba2 e4bara ebarba4r ebe4c e4bel. -e4bend 4eber 4ebic e4bis4 eb4l e4boan ebok4 e2bona eb4or e3br e4braw e4brio -e4bris. ebro4p e4bry ebs4 eb4t3 eb3uli eb3ulo 4ebu4r ebusi4 e4buta 4eby 2ec -e4c' 4e4c. 4eca e2cad eca3de ecag2 e2cago ec5agon. ec3ah e4cali e4calo ecami4 -e4can. e4cana e4cano e4care ecas4t ec3c4 eccar4 ec4clu ecd4 3ecdo4 e1ce e3cel3 -ecenar4 e3cer ecer4n e4chea 3e3chis 3e3chiz ech5ua ech4we eci3a. eci6able e4ciar eci6ativ -ec5ificat ec3ifie ec3ify e4cig e4cilit ec1i3m4 e3cio ecip3r e4cist e3cite ec3i3th ec4k -e4clam e2c5lec e4clina ecli6nato6 e4clino e4clog e4clom e3clu e4co. e2cog e4col. e4colle -ecolor3 e4comme ec5ommend ecom3p ecompari6 e6compen ecompos6e ec3onci e4condi e3co4p e2cor ec3o3ra -e4cord. ec3o3rou e2cosp ecos4s e4cow e1cr e3cra e2crea e4crem ecrep3 e4creto ec4ri -ecrit3 ec3rom ec3st ec4te ecti4no ectiv4iz ecu4bitu ec1ul e4c3ula e3culi e4cuma e3cun -e3cur e4cury e4cus4a e3cute 2eda ed1ag e4dah e4dalu edan4te e3dar eda4tis e4dativ -e4dato 2edd4 ed4dite 4e1de edek4 ede4mo ed2en edentar5 ede3p e2der ed3era e3des. -ed3es3ta e3destr ede2v e4dey 4edgel4 e4dh 2edi e3dia ed1ib e2dica ed3icab ed3icat -edi1e ed6ifica e4dik ed3ime e3dio ed1it e4dita e4ditiv e3diu ediv4 ed4j e2dl -1edn e2dole edo2no e4dony edriv6en e4droe 4edt ed4ta 2e2du ed4ua 5educa 2ee2 -4e4e. e4e3b4 ee4b. ee4c eech3i e2ed1 eed3in e3ed3u ee3h ee3i4 eek1 e4el. -eel3ab ee3lan e4eler e4em. eemo4 e4en. e6encoun e4ened e4ener. een3in ee4no eensbor4 -ee3nu eep1 eep3i ee3ru eesep4 e2est. eet3i ee3va e4eve e4evin ee3w e1ex3 -eexa4 eex4plos 2ef 4e4f. e3fa efal4l e4fana efe4 e2fer ef5ere efic4 3eficia -efici6ary e4fila e4finit ef5i5nite ef4l e4flag ef5lagr e4flora e4flu e3fo eform6it e2frag -efrig3 ef4t eft4il 4e3fu efu3e e2fug efun4 efu4t e4futa 4ega. egaci4 4egal -egam4e eg4an 3eganc e4gano eg4are e4garin egas4 e3gel egel4e ege4m egen1 e2g3end3 -eger4a eger4e eg4gr e3ghe e2gi2 e3gi4a eg3ib eg2ic e3gil eg3ime e3gio eg1is -e3git1 e2gle eg3li eg4my 4egn eg4no e3go e4god ego3i e4goli e4gor. ego3ris -4egos ego3ti e4grad6a egrammat6 4egraphy e4gri. e2gu e3gui e4guin. eg1ul e5gur 2e1h2 -e3ha e3he ehear4s e4hen. e3hi eh3me e3ho ehr4 e3hu ehy2 ehydro3 2ei -4ei. ei4a e3ian e4ib e1ic. ei4ce ei3d4e 4eif eig2 eil4e eil3ing e3illu -e4imar e4imo e4ims e1i2n e4inag ein3au ein4e e5ing. e4inho ein3i e4ins. 4e4i1o -ei4ov eip4i eip4t eir4d eire4 eir4i e4isa e4is4e eiss4l ei3su e3itera eiterat6 -ei2v eiv3er e3ja e4jel e2jud ej3udi ejun4 ejus3 2ek ekam4 eka3t ekate5 -e5kater e3kee ek4n ek4s e4l. e1la el3abl e4lacy e4lae e4laf e3lagi e4lagin -e3lai 4elan el3anch e3land elan4i el3ano e4lano. e4lapi elas3 5elast e4lates elat3in -e4latin. elati5niz e4l3ativ e4latori e4latory e4latur elav4 e4layl el4di el4do. e4le. elea4 -e4lean e3leas e4leat el3eb e3lec ele4ch e4lee e4l1eg el3ega e4lem el3eme 3elemen -e4lena e3lene elen4i e4leno elen4t e4leo eleono4 e4l3ep ele3ph elepi4 el3e4r 4eles -e4lese e3let. el3eto eleton5 e4leu e4lev el3eva e5le4v3e el2f 4elf. elgi4 e1li -e3l4ia e4liar elib3e e4libi e4libl el3ic. el3ica eli4ca. el4iced e3lici el4id. e4lidl -el3igib e4lign 4elik e4lim. e2lin e3lina e3l4ine el3ing eli3on e3lios e4liph elir3 -e4lira e2lis eli4sa eli4sor e4list e3liu eliv3 e2liz el3ki el4labl 4ellan. elle4r -el3li 4ellif ellif5l 4ellit 4ello 4ellu ellu5r 4elm. elme4 e1lon elo4na e3loni -e4lono el3op. el3opa el3oq e3lor 4elot 4e3lov e3low elr4 el3si el4tan el4ti -el4try e1lu elui4 elus6te e3ly 4ely. ely4a 3elyt el3ytr e4lyu e4m. em3ab -emagnet5 e2mago em4al e3man. e2mana em3anat e3manc eman4d e4mane eman4n e3m4ar e4mare -emargi5 e3mas e2mat e3mate em3atoc em5atolo em3atop e6matoriu e6mature. e4mav e4m3b4 ember6m -em5bl emblem3 3embo embod3 5embry e1m2e e4meab e4medie eme4diev e4mel emeli4 eme4lia -e3m4en ementa5r e4m3era e4mere e4merol e4mery e4m4es 4emet4 e4mete emica4 em3id4 4emie -e4mige e4migran emili4a em3im emin4 em3inen e4mingt e2mini e3miou e4mi3p emi5pa emi1r -emi4rus emis4c em3i3s4p em4ite. em4iti emi3v emm4 5emniz emod3u e3mog e4molit emon3e3 -e4mone. emon3is e4moniti emon3ol emo4nu e3moph em4or. emor4a e4mora. e4m3orr e4mory e4mosc -e4moso e4mosp e4mosu emo4te emp2 em3pe emper4e em3p4i 3empir empo3 e4ms e4mu. -em3ula em3ulo emun4 e4mv em4y emy3tho 4en't e4n. e1na 4ena. e2nac e3nade -enadi4 e3nae en5age. e3n4a4l ena2m e2nan en5ancy e4nar4d e3nari 4enary 4enbu 4enc. -en4ces 4encia 4encio 2en2d end6aged en3dam en4d3an en4dato en4ded en3dic 4endre en4dry -end4wr endwrit4 e5neal e3nei 4e4nel enem4 4enen e3n4eo enep4 en3er enervat6i 4enes -e3nes. e4nese en3esi e4nesse enes3t en4eti e3neu eneu4ri e3nev3o ene4w en4fer eng2 -en4gel en5gine en4gle eng3li 4en3gu engue4 4enhairt 4enia e3nia. en4ian e4nias. en3ic -eni4dea e3nign e3nil en3ing enion4 e4nip eni4r en3ish e4niso en3ita en3itu en6izer -4en3k enka4 4enna ennar4 enne3a 4en4nim 4enny e1no 4eno. eno4e 4enol e4nold4 -e4nom. enom4ic enon4i 4enopho 4enor en3orr e4nors 4e4nosin eno3th e3n4ou e2nov 4e3now -enro4 en2se ens4es ensi4n 4ensk ensur4a en4swe e4nt en4tage en5tar 4ente. 4entem -enth4 3enthet en4thou enti6pe enti4r ent4is 4entiv en2tiz en4tox entre3p 4ents en3twi -4e4nu. en4ual en4uf 4enul e3num e1nun en3ure e3nus e4nuses en3v e3ny. enyl3 -enz4 4enzi 2e1o 4e3o. eob3l eob4u e3oc eo3ch eoder4 eo3e eoe6c eo4es -e3og e3ol eo3la 4eo1li eo4logus e5ome e4omin eon4as e4opar eo3po eora4 eor3d -eo3re e2org eo6rient. eo4r1o e3os eo3sin eo3sta eostra4 e3ot eo4t4e eo3th eoti4 -eo2to eox3 eoxi3 4e2pa e3pac4 e3pai e4pana epar2 e4para ep3a3rab epari4 e3pas -epa4tia epa4tic epau6p e3pav e3pay e4pech e4peh epend3 epen4t e4pert e4pet e2pha -epha3la ephe2d ephem3 ephe4u e3phi e4phia 4e4phonis 4ephony e4phos 4ephr ephtha4 ephy4 -ep1i ep3ica ep3ice ep3id ep3igl ep3i3l 4epin. e4pion ep3iso epi3t ep3ita ep3ith -epix4 ep3iz eple4r e2pli ep3lic e4plorat e2poch epol4 ep3o3nem e4positi e4potis e4pp -ep4pes eppli4 epra4n e2pre ep3reca ep3red ep3reh epre4m ep3rese e2prob ep5robat epro4c -epro4l eprom5i ep3si ept2 ep5tib epub3 ep3urat e2put ep3uta e1q e4q. e4qq -eq3uab e4quer e4quie e6quine. e2q3ui3s e2quit er1 e4r' er3ab era3ch e5racio er4ah -e3rai e4raim erak4 1eral 3erali 4eralia era4lo era4ma e5raphica era3pi er4aps e4ra4r -era4s 4er4asi er4ass. er6ators 4eraz er4bage. er4bie er4bly 2er3c 4erca 4erd. er5da -erder4o erdi4e erdit4 erdo4s 2ere 4ere. 4erea e3real er3eb ere2c ere4co eredi4a -er5edu er5ee. ere6forma 3er3eig er3emb e4remou er3en 4erene 6erens eren4tis 4ereo er3er -eres4i er3ess er3est. er3este er3ett er3ex ere4z 4er3ges 4ergia erg3in er4git ergi4u -erha4 2eri 4eri. 4e1ria e3ria. 4er4ib eribe4 4eric er5icu 4erides erid4l eri4dop -e3rien 4erii er4imet 4erinc er3ine 4e1rio er4ion e4rioni e3rior erio3ra erior3i e4rioso -eriot4i e3riou 4erip e5ript eripter6a eri4q er3ir 4erity 4eriu e5rivativ e3rive 4erk -er3l erlan4d erm2 4er3me er4mel. ermen4 er4mer. er3m4i ermon3i er4mor. erna4c ernali4a -ernau4 4erne. er4nen er2ni ern4ie 4ernit 4e1r4o. e3roci erodacty6 e4rogeni e3roic eroid4 -4eroli 4eroll e4rometr e4ron. eronau4 eron4is 4e3room ero4tol 4eroun 4erow 4e4rox erp4 -er3pe 4erpen erpi4er erport4 2err4 erre4fo error3 2e4rs ersar4ia 4erse erse4h erse4p -ers4es ersh4i ers3ib ersi4e ersi4u er3sk 4ersl 4ertas erte4n ert3er. ert4h er4thag -4ertic er3tin er4tish er4tite 4ertiu er4tl er3tw 4eru. e3ruc eru4e 4er3ul 4erum -4erun e3rup er3ur 2erv 4erva. 4erve erv3er. 4ervo 4erwal 4e4rx er4yo erys4 -2es. es4a2 e2sag esalin4 e4salp esal4t e4sam esar4g esar4io 4esb4 es3ca es4cara -es5chea es4cia e4scienc 4esco e4scot e1s4cr e2scu ese2 es3ecr e4seil e3sel esel4l -es3enc e4sentee e4sentl e1s4er e4serva es4es 4es4h e1shi esi2 4esia 4e2sic es3icc -esiccat6e esic3u e2sid eside4 es3iden e3sidi esido4 e2sig es5ignat esil3i esil4l e2sim -es3ima e4sin e4sip esir3o esis4 esis5tiv 4esit es3ita esk4 e4skil e3skin 4esm -e4sn esocepha6 e2sol eso3la eso3li es3olut es4ome e4son4 e5son. es3o3na es4oph e4sorc -e4so3te esp4 es3per e4spi es5pirat espond3 espon4ti ess4abi es2sib ess3ing es4sis es4site -4essn es4sy 1est. 4esta. es5tabli es5tame e4stant e4stau e4sted 4estep e4ster. es3th4 -6esthetiz es4tho e2sti estic4i es3tif es3tig es5tina es4tip es3tit4 2estl 4esto es4tor -e5strai e5strict e4stur es2u 4esuc e4sue esui4 e4sult e2surr es3urre es4us e1sy -esychas4 e4syl. esystem4 e4sz e2t e4t. 4eta. e4tab e5tabul etae3 e3tai e3tak -et5allis et3alo 4et1am etan4 e4tant et1ap eta3ph e3t4ar3 etas4 e4tat. e3tate4 eta4th -e3tati etax4 etch3er e3tea e3tec e4ted etel2 e3tele e3tel4l e4tely e4tem e3tenc -e4tene e3tent 2eteo 4eterm e3tern eter3ni eterog6e e3tes e3tet ethar3 e4thea 4e4thel -e3there e3theri e4thic 5ethn ethod3 e4th1y 4etia e4tiam eti4b et1ic et4ica. et3icis -e4ticl e4ticule et4id e3tim etin4 et3ina eti4p e3tir et2is et5ish e5titio et3itiv -4etiu eti4us e4tm etma4 e4tn e3to 4eto. e4toes e4toid e4toma et3ona 4etone -e4tony e4tope e4topl etor4ic. e4towa e5tox et4raci et4rag e3tran e3trat e3tre4 etrea4 -e4trel et1ri et4rid et4ril e4t3rim et4rio etro5f4 et3rog e3tropi etro3s e3tru e4trum -1e3try et4st et4su e4tt 3ettan et4ten et4tes et4tine. 4ettl 4etto et3ua e3tud -etu4i et3ula e3tum e3tun e3tu4r 4etv ety3le 4e4tz 2eu1 4eu. e4u3b2 euc4e -eu3ci euck4 eu4cy eu2i eul2 e1um. eu4mi eun4e eun4o eunu4 eupa4 euphe3 -4eur eu3ra eu4ram eu4ras eury4l 4eus e1us. eus4p eut4e2 eu3ti e3util eu3to -euton3 eu4zl e2vale ev3ane e2vans evap3 evardi4 e4vart evass4 evas3t 3evate 3evati -e4vatt e3vea e4vei e4v3el. ev5elatio ev3ell evel3o e4vena even4i even6ta e2ven4u ev3er. -e4verat e2vere ev3eren ev4ers ever4to e4ver2y e3vet e4vev eviar4y e3viat e4v3i3d evi4e4 -e2vil evil3i e3vinc 4eving evit4er e3viti eviv3a e2vo e3vo. ev3ocab evol3a ev3ole -e4v3olu e4von e5voro e3vot ev3r evv4 ev3y 2ew e4w. ewal4l e3wee e4wic -ew3ie e3wif ew3ish e4witt ew3l e3wo e2x 4ex. ex3a exac5e exae4 exas4t -3ex3c exci4t ex3e 3exh ex3i exi4an. 4exm exon4a 3ex3p ex5ul e2y e4y. -ey4al 4e5yar ey3de 3eye. eye3l eyk4 eyl4 ey4na eyne4 eyo4g ey3or eyp4 -eys4 ey3se ey3t 2ez 4ez. e4zant. ez2e e4zel ezi4a e3zo e3zu e4z3z -1f 4f's 2f. f2a 3fa. fa2b fab4b fa4b3u 3fa2c fa4ce fa3cie fac5u -4fael faer4 4fage fain4 3fal fald4e falder4 fal4l3e fal4lin falo4n fal4sie falut4 -4famati fa4min 3fan 4fana. fanat3i fa4ne 4fanie fani4l 4fans 3far far3ad far4m3 -fa4ro. 4fars far4sp farth4 fash3 fas3ten fa2t fa3ta fatar4 fa3the fa4tiz 4fato -fat3ua fault3 faus4 fau4v faw4 faya4 fay4al. 2fb 2fc fca4 fcas4 2fd -fe2 2fe. feath3 f4eb fe4bris 2feca fec4ta fed1 feel3 4fees 3fei2 fek4 -fe3li fem3 fem4a fe4mer femi3 femor4 fen2 4fen. 3fend 3fens feof4 f2er. -2fera fer3e fer4es feri4a. feri4as 4f1ero fer4ob fe3roc fer4op 3ferr 4ferse 3fert -4fertit fer3v 3f2es 4fesa fes4sa fessing4 fes3ta 3feu fe3v4 2ff2 4f4f. 4ffa -4ffe f4fed f3fen ffer3 ffer4o f3fet 4ffi f3fic ffich4 f4fie ff4ie. ff3ily -ff5iness ffin3i f4fis 4ffl f3fle f5fle. 4ff3n ffor3e f4fr. f4frai 4ffs f3fu -ffus3ib f4fy 2fg fgh4 fgha4 2fh f4i. 3fi3a 5fiab 4fian. fi3b fi4bo -fi4bu 4f1ic. 4fica. fi4cab 4f3ical 4f3ican 4ficate 6ficatin 5ficatio 6ficato 2fic4e 4f3icen -4fices fich4e 3fici fi3cie 4ficis 4ficit fi4cl 4fics 4ficulti 3fid fi3de4l fid3i -4fido fi4do. fi3du 3fie 5fied 5fiel4 fier4 4fies fi4f fift4 3fi2g fight3 -fig1u2 4fik fi5lad fil3am fi4latu fil3i fil2l fil4m fin2 3fin. fin3ab fi3nal -find5 4fing. fin3ge fin4ial. fin3is fin4nic. fin4nis fi4noi fi2r2 fires4 fish3 3fishe -f3ishly f3ishn 5fist fis4tia fi2t f3it3a fi4te f4iter fi4u 3fix fiz2 f2j -2fk f2l2 4fl. 3fla fla4c fla4ge flag3el 4flagr fla4my fla4s fla3v f4le -3fle. fle4a flen4 fler4 4f5less fleu4ro 4fley 4fli. flich4 4flik flin4 flint3 -4flis 3flo flo1r 4florat flor4i flo4rie flo4rin. floy4 flu1e flu3i flu4t 2fm -2fn f3ne f2o 3foc fo4e 3fol 4fola fo1l4i foli5a fomo4 fon4a foni4 -fonni4 fonta4 fon4tal 3foo fool3 3for2 fo6rab fo4ram. fo4ras fo3rat6 for3b for4c -fo4rea for3ei fore5r 4for4mate for3n4 fos3 fote4 3fou fough4 foun4dr fouri6er. fout4 -fow4 2fp fper4 fr2 4fraf frag4o f4rai fran3c fras4 fra4tr fre2d frel4 -f4re2s fri2 4frid fri4ga frig3e fri3k fril4 fro2 4frob fro4ni4 frost3 frot4ta -f4ru 4fru. frui4 frus3 2fs2 fsa4 fsur4 2f2t 4f4t. 4fte 4ft1i ft3l -f4tm 4fto f5ton 3f4u. fuck4i fue4gi fu3el 4fug fu4gu 3ful 4fulo fu2m -fu3ma 3fun fun4a fu3n4e 3fur fu4rat fu3ri fu6riate. 3fus fu3sil fus2s3 fuss5i -fu4s3t fu2ta 4futab fuz2 fuzz3 4fve 2fw fy1 3fy. 3fyi 5fyin fy4k -f4yo 2g. 1g2a 3g4a. gaar4 ga4ba 4gabo ga3br gac4 4gach gadge4 gad4h -gadi4 gadis6 ga4dit ga5dr 3g4ae 4gaeu gagam4 gag4n gah4 3ga4i gais4 3gal. -gal4a 4galay ga3len g4ali gal4ib 3galis gal5ler gal4lin galma4 ga1lo galo4pi 2gam -gam4a 5gamb gambi4e games4 3gamic g3a3mis g3amou g3amy 3gan 4ganap gan3gl gan4gla -gan3i gan4im 4ganip gan6iser ganis4ti 4gann 4ganof ga4nome gans4e 2gapo ga4py gar3den -g4area gar4it garn4 ga3r4o 5gart gar4ti ga2s ga4se gask4 gas4me ga4so gas4per. -g4ass4 3gas3t 3gate 4gatee 4gatew 4gatha gath5er. gath4i 3gatin 3gatio g3ativi gativ6ism -ga4tiz 4gatorin 4gatu 3gau2 gaus4 gauta4 gauti4 gav4a ga4ve gav3el gay4a 2gaz -2gb 2gc g4c. g3ch g4cy 2g1d gd4i g4dy gdyni4 4g4e. ge1a 4geab -ge2ar 2ged 4ged. ge4dal 3gee 4gef geiss4 gel4a 2gele gel3ic ge4lid ge4lig -4gelik gel4in g4ella. g3ellat g4elli 2gem 4gema gemar4 gem3i ge4mo gemon4is 1ge2n -ge3nar 4genary 3genc 3gene gen3es 4genes. 4genet. gen4fe gen1i geni4al 3genic geni2o -2genis gen3it gen4ite geni4u 2geno g4enoi genom4i 4g3enou gens4e 3gent gen3tle ge4nuse -4g1e3ny 1geo ge4of 3geog ge4ol. 4geole ge2on ge2or ge2ou ger4an 2gere 3geria -germa4no 2gero 1g2es 4gese 3gesi gesi5a ges3tat get4a ge4tar get1i 4geto ge4try -3gett 3geu geus4t 2gf g4fet 2g1g2 ggar4t gge4d g5gedl g3gedn g3ger g3gi -ggi4a gg4l gglom3 gglu3 g3go ggo4t ggran3 gg4s 2gh2 4gh. g3ham ghar4i -ghas4 gh3b gh4es. gh3f gh3h ghi4b gh3in ghi4o gh3l 4ghn g5horn g3hous -ght1 gh4ten 3g4i. 1gi2a gi3al giale4 gian4t giar2 giar4c gia3ri gi4at gia4to -1gib gib4e g1ic g4ici gi4cl 3gid3i 3gien gi1es gi4et gi3ga gigam4 g4ih -gi4le g4iliu 2gim g3imen g4in. gin2a gi3nab g4ind 3ginee giner4 g4ines. 2ging -gin5ger gin4h gini4a g4int 1gio 3gion 4giose giotro4 3giou gipa4 gip5s 3gir2 -4giriti girl3 1gis g1isl giss4 g2ist. gis3te g1i3ta gita4l gi4tall g4ite 1giti -4gitim. gitin4 gius4 3giv 1giz 4gji 2gk g3k4i4 4gko gl2 4gl. gla2 -gla3b gla4d glam3 4gland. g4lar glas2 glas4s glazi4 1g4le 3gle. gle4an 3gled -4glen. 2gles g5less 4gley 2glia gli4b 4glie. g3lig 4glik g4lim 3glion g3lish -4glisti 1glo globi4 4glod 4glog glom3e glo4n glo1r glou4 glo4v 4glu. glu4me -glu3ti 3glyc g4lym 3gly4p 4glyphy glyr4 2g1m 4gma2 gma6tos gmentar5 2g1n2 g4n. -g4na. g4nac. g4nace gna4g g3nal gnar4 g4nard gna4s gnat4a g3nati gnet5ise gnet4t -3gneu gneu4ry g4ni. gni4an gn4ic g3ni4f gnif4ic. gn3ing g4nio4 gnis4 g3nit gniz2a -gn3l gn3m gn4o g4no. g2nol gnom4 gnon4 gno3ra g2ny 1go 3go. 3goa -go4bl go3c g3ocy 4godo goer4 3g2o2g 4gogr 4goh 3goi goid4 go3is gold4i -4golit gol4le go4mer 2gon2 g2on. go1na4 g4onar g4one. 3gonia 3g4onic go3nid 4goni4e -gon4io gon4ism 4gonist 3goniu go3nom gon3op 4gonu 4gony g3ony. 3goo4 gor3at gor3ou -4gosa 4goso goster4 g4ot 3go4th 3gou 4gour. 4gout. go2v1 gover4 govin4 gowa4 -2gp gper4 gpi2 1gr2 g2ra2 gra4ces 4grag. 3gram grama4 3gran granar4 grang5er -grani4t gran4te 2grap gra4pe 3graph. 4grapha. 6grapher 3graphi 5graphic 4graphis gra4py grar3 -gras4 gra4tin gra4tis 4gray. 3g4ree greg4 gre2l 4gressis gre4t 4greth gre4v g2ri -2gric g3ricu 4grif grif5fi grigen4 gri2m grin4d 4grity gri4z g4ro2 groc4 grol4l -4grope groph4 4gropo gro3sc grue4l gruf4 gruff3 grum4p gry4p 2gs2 g4s. g3sp -g4spo 4g1s4t gstar4 2g1t2 4gta g4th gth3e gting4 1gu 3gu. 3gu2a gua3d2 -guar3a guay4 gu4dr 2g4u4e 4gue. 4gued 4guef guel4 gue4la 4gues. gu4gl guia4 -3guid 3guil gu2in gui3na 2gu1l gul4lab gul4p gum4a gu4mis 3gu4n 2guo 4g3uou -4gup 2gur gurd4 g1ure gu4rio 3gus gu4si gus3tat 4guta gu4th gut4s guya4 -4gva 4gve 2g1w 4gwa g4wane 4gwe 1gy 2gy2a gy4bi 4gygu 4gyh 2gyn -gy4ne gyn4i g3yny 4gypt. gy1r gy5ra gy4t 4gze gz4w h'4i 2h. h2a. -4habad ha4baz habi2 ha4bile h3abl ha4col h2ad ha4dar ha4deu had4j had3ow haf4 -ha4gan hag2i hag3ic hag3io h3agis hag4on h3a3gy h4ah ha4han haid4 hail4i 4hairt -ha4it ha4ki hal2 ha3lami hal4an ha6lange. ha4lar h2ali hal3ic h3aliti hal5mi ha1lo -ha4loc ha4lone ham3it hamma4 hamme4 ha4my h2an2 ha2nat handel4 han3er h4ang han4ge -hang3er han4i han5ic hani4l h3anis ha5nisti h3a3niz han4k han3n ha4no. h5anol h3anous -ha4nov 4hansh hao2 ha3ot hap3er ha4p3l h4apu h2ar har3ac ha3ran hard5en. har3d4i -haren4 hare4u har4f har2g har4ga har4ia har4iz har2mi ha3ros4 har2p har4tis hary4 -hass4 h4a2t ha4tiz hauc4 4haud haugh3 hau4l hauli4 5hause ha2v ha4ve havel4 -havi2 hay4a ha2z haz3ar 2hb h3bo h3b4r 2h1c hcas4 4hcl hcom4b 4hcr -2h1d hdra4 hd4u 4h4e. 3h4e4a4d he3an hea4p hear2 3heart heart3i h3east3 heathe4 -hea5then heat3r hea4v heav3i hebe4i hebra3 he4bri he4bro he4cl hec3t hectom4 h4ed -4hed. hed4dit hede4 he4done hed3u hee4n hega4 he4gi he4ia he4il he2in hein4i -he1is he4j 4hek hekho4 hel4ep hel3ico heli4e he3li3o hel4lie hel4lis hel4ly h3elor -helo4t hel2p hely4 he2m he4matei h4eme hemer3 hemilu6 hem4ise. hem4ith he4mon he4m3or -hem4pe he2na he3nes he4nias heni4e hen3is hen3iz henom3 4henu he4o. heo3ri he4os. -h3eosi heos4t he2p hepta4v he2q her4ali her4bal her4by her4d here3a he3redi heres4 -heret6ic. here4v he4ri. h3eries herit3a he3riti herlan4 her4mel hermi4t hernali4 her4ome her2op -her4or hers4e hert4e h3erw h3es. hesan4 he4sho hes3it hess4e hes4sit h2est hes3ter -hes3tr het1 hete4 het4ed h3e3teri he3tis he3t2iz het4tine heun4 4h4ev he3vis hex3 -hexas4 h4ey. 2hf2 4hfu 4hg. h3go h1h h4i. hia4n hian4g hi4ap hi4au -h5iave hi3be hi4bo hibra4 h1ic hi4c4e h3ici h4id hid4it hi4do hi4dy hien4 -h3ier. h3i1es hi3est hif4fer hi4fl h3ify hi4get high3 hight4 h4ih hi4lad hi4li. -hilib4 hi4lin h3iliz hillar4 hiller4y hil4loc hilo4mo h3i3lou h1ily hi4lyr h2im hima4 -hi4mac4 hi4mar him4it h4ina hin4der. hin4derm hin4ea hine4al h3ing. 4hi3no h4inom 4hinu -4hi1o hio4d hi4oed h4iol hi2on hiop4i hi4or hi2p hiprot4 hiq4 hir2 hirel4 -hirst3 his4a hi4se his3el hi4sor hist2 his5tam his3tl his4try h4i2t 4hite hith3er -hi4top hi2v hiv3al hiv4al. hi2z h1k 4hki 4hko 2hl hlag4 h3lan h3lea -hle4b hlep4 hle4t h1li hl4o1 hlor2i hlo5ric hloro3 hlo6rou hls4 2h1m2 4hm. -hma2 hma4g h3man hmel4 h3men hmet6ic. hmi4r 2h1n2 4hn. h4nen hn4i hni4e -h4nol. hnos4 h2o ho4ag4 ho3bo ho4co h4oda h3od3is h5odiz hoen4 ho2g hog3o -h4oi4c h4ois ho4it ho4lam ho4lap ho4l3ar holat6 3hold1 hol5er holes3t ho4ley hol3ic -ho4mas ho4me3s ho4mogr ho4mopho homo3t homy4 ho4my. hon4a ho4nas ho4nau hond4 ho4ne. -ho3nem ho5net hon2g hon4is ho3niz hon4oc ho3nog ho4o 3hood hoo4k hoo4p hooper4 -hoot3e hopes4 hophyl6lo ho4pl h3opoe hor2 h4or. 4ho3ra ho4rab horar4 ho3reo ho3reti -horian4 hor3ic horo4b hor4on hor4s 3horse hors3i ho4ry. hos4e hosi4 hos3p ho4tho -hoto1 h4oug 3houn houp4 3house ho2v hove4 hov3el ho4via 2h1p 4hpa hpem4 -hpri4 2h2r 4hra hral4li hra4sa h4r4e hrea4 hreat3 hren4e hre4no hre4s hr4i -h5rig hri4s hroat3 hro3mic hro4mis hro3n4is h4rops hr4y h3ryma hs2 4hs. hs4e -h3sh h4shu h4si2 hsin4 hsink4 h1s4t h4stag hsti4 h2t h4t. htab4 ht1e4 -h4ten. hten3i h4t3eo ht2er h1th4 htha4la hthe4us h4this h3thy hthyla4 ht3l 4hto -h4ts htu4 h2u hu2a huat4 huf4 huff3i hu4ge hugh4e hu4mis hum4p hump4y -hun4e hunk4e hun4t hur2 hura4d hu4rat hur4c hu1ri hu6rise. hur4l hurs4i hur4t -hus4s hus3t4 hu2t huta4 huz4i h1v 4hv. h4vh 2h1w2 4h2wa hwar4 h3ward4 -hwat4 h3way hw4e hy1 hy4' hya4c hya5li hy4bi hyen4 hy3gi hygro3 hyi4 -h4yli hy2m 4hymi hy3o hy4pre h4yr hy4rh hyr4i hyro4g hyroi2 hy4s2 hys3e3a -hys3i3a 4hyt hyx3 2i. 2i1a i3ab i4abel iabi4 i3ac. 4iacal iace3t ia4chi -iaci4 4iaco iac5o5na i3ad ia3de i3ae iaf4 4iag i2age i4agu 4ia4h i4ai -i4ald iale4u i3alit i2am ia4mi i4amo ia2n ia4na 4ianao ian3at ianchi4 i4anco -4ianh ian3i 4ianii ian4ite 4ianne. 4iano i4ano. ia4nop ianop4s 4iansk iaon4 ia3pa -ia4pl i4app i3arc i2ari i3arie i4arri iar3t 4i3as ias4c ia4sic iass4 ias6trop -i3at ia4tic ia4toa i4atoo ia2tr iat5ric. i4att ia3tu i4aus ia3v iave4 5iavel -i4avo 4iax3 ia3zi i2b i4b. 2iba ib4as i4bata i4bb 4ibbo 4ibe. i3b4el -ibe4la 2iber ib3era iber4o i4b4es ibe4t i4bia i3bi4d ibid3i ib3ila i4b3in i4bio -ib3it. ibi4ta i4b3ite ib3ito i4bium i3bl 4ibla i4bn i3bo 4ibo. 4i4bol 4ibon -i3br ibra4c 4ibran 2ibri i4bria i4b5rio i4bs i4bul ib3ula ib2un ib1ut ib3uta -ib3ute i4by i4c. 2ica. i4caa ica4ba ica3c 4icaca i3cal i4cald 4icam icap3i -3icapp i3cari i4cast. 4icata. 4icch iccu4 2ice 4icef ice3ro i4cest i3cet 2ich -4ich. i1cha i4chag ichar4 4iche. i3che4n ichen4e ichen5o 4ichi ich3ic ich4i4e ichi4l -ich3in ichi3ni i1ch4o2 i4choi i2chom i4chore. 4ichot i1chr 4icht i3chy 2ici 6icia -4icic i4cic. i3cid4 i3cien i2cif i4cila icillar4 4ic3in4a i2c3ip i5cipi i3cis 4ic1it -2ic4k 4ick. 4icke icker4y ick5l 4i4clo. ic3ly ic1n4 4ico. 4icog. i2cola 4i2c5oli -ico4mat icom4e i6como 4icope ico3ti 4icou 4icoy 2i1cr icre3 4i4cri icro1 2ict -4icta icti2m3 ic4tis ictu2 ic2t3ua 2icu1 4ic1ul ic3ula i3cule ic3ulo i3cult ic3ulu -ic4um ic3uo 3icur 4icylat 2id i4d. idacty4 ida4d i4dai i4dak ida4l i4dam -i4dani i4danu 4idasa idau4 4id3d4 ide1a i2dect id5ed idel4e ideli4a i3den ide4na -id3er. id3era 3idesc ide4se i4detr idewa4 id4g idgel4 idger4 i2d2i i4d3ia i4d3ic. -id3ica idi4d id4ie id3if id4il i3d4im i3dine i4d3ing id3ino i4d3io i4d3i3t id3iu -4id4j id3n 4idni i4dobl ido2l idol3a i4domi i4dones i4dophy id3ow i4dri i4drop -4ids i2du1 i3duc id1ul idu4n id3uo idur4 idw4 i4dyd i2dym ie2 2i4e. -4iebr 4iec. 4ie4ce i3ect 2i2ed i4ed. ie4fe ief5l iego3 4ieh 2iel ie3la -iel4d ieman4 iemp4 2ien ie4na 3ien3at ien4e ien4g ie4n3i i4eno 4ieno. ient4a -ien4tag i3enti4 4iento ien4tr 4ie3p iepra4 i1er i4er' ier4c i4erd 4ier4e i4erg -2ieri i4erly i4ern 4iero 4i4ers 4iert i4erwa i4es. ie3se i1est i3est. 4i3et -ie3ta i4eter i4eton i4et4r ie3ty i4eu 4iev iev3er 2iew ie4we i1ex i3ezo -i2f 2i4f. i3fa ifar3 4ifat 2ife i4f1er ifer4e 4iferi i4ff 4iffe if2fi -if4fia 4iffu i3fi i4fi. i5fia 4ificant i4fice i5fie 4ifil i4fing 4i3fl i3fo -i4fo. iform3i i4ft1 i3fu 4ifug if3uga i5fuge i3fy i5fy. i5fyi 2ig 4ig. -igam4e i4gand 3igarc i4gatori i1ge2 igens4 igen4to i2ger ig3era ig3eren ig3ero i4geta -ig4fe igg4 igh4t ight3e ight3i i2gia ig3i5at i3gib ig1il ig1in 4i3gio i4gios -i2g3it4 ig2l igli4 i3gly ig2n 4ign. ign3ab ig3nat 3ignate ign3er ign3in 4ignm -igno3m i5gnon. i2go2 3i3g4oc i5gon. igon4is ig3o3no i3gop igor3a i4gri i2gu i4guat -i3gu2i ig3ul igulat4 i4g3uo ig1u2r 4i2gy i1h iha4n iheret6 ihil3i 4ihin i4hk -i4hs ihun4 i4hz 2i1i2 4ii. i3id i4is. 2ij ijan4 4ije i4jl i4jn -2ik 4ik. i1ka i3kan 4ike ikin4 i5kin. ik4l ik4w 2il. i1la i4labe -i4lade i4laf i4lana ila4ne ila4p3 ilar2 ila3ra i4laru ila4s i3lat i4lato i5lator. -i4latur 4ild il4du i4le. ilea4 il4ed i3leg 4il3er iles4 i4lest ile4tr i5lev -ilev4er ilh4 il1i i4l3ia ili4al. ili2an il5ias ili6ativ il2ib ili6ca. 4ilie il3ien -i4liki i3line il3ing. il3io ilion4 iliora4 il2is 4il4is. 2ilit il3ita il4ite ili4tur -il3iu 4ilius iliv4 i1l2iz iliza6bi i4l4k 4ilk. 2ill ill3abl ill4e 4ille. ille4i -illi2a illin4 4illio illo4c il4lock il4lust. il4mi 4ilni 4ilob ilo4e 4i4logi ilo4mon -4ilop il1oq il5oquis i3lo2t 4ilp ilpa4 il2ti i1l4u il1v il4y ily4a i4lyra -4im. i4maa i4mace 4imach i4machu i2mag 4imak iman4i i4mano 4imaru i5mat 4imato. -4imav 2ime 4ime. ime3l i2mene imenta5ry i5meriz im5etry imf4 imfer4 4imh im1i -4imi. i4m3ia im4ico im3ida imi5la 4im5ing imin3i imi4nu i6minutio imi5ny imi4on im4i4p -i1mis i4miss im3iti 4i4mity imma3t 4imn4 i3mo imo4ch i4moi i4mola imo4nian imo3no -i4moth i4mout impa4l im4pers. im4pha imput3a 4imr imro4 i2mu 4imu. im3ul 4imw -2in. i1na 4i5na. inab4 4inace i4nach 4inad ina4la inal4iz i4nam ina4mo in4ann -i4narch i4nare i3nari inar4is inar4it in3aug inav4 ina4z 2inb incar3 incel4 4inche -in4cor. in4cr. 4inda 4indee inde4fi inde4l in4dermo 4indl indor4se 6indromis ind4wi 2ine -4ine. in1e3a in3eal in3ear in3eat in5ega i4neit 4inel i3nell ine3m in3emat i1nen -i2n3eo inep4 3ine4ph3 in3er iner4s in4er4v i4nese i2nest i4nesw i1n4et i4neth ine4tr -in3ew in3f4 infat3 in4fin. 1i4ng 4inga ing5ab 4ingbu 4ingd 4inge ing3en. 5ingenu -4inghe 2ingi in2gl 4ingla 2ingle 4ingo ingov4 4ingr 4ingtow 4ingu 4ingy inhag4 -4inhar 4inhol 2i2n1i 4ini. in3ia 4inian 4in3ic i4nic. in4ica. in3in i4ning in3io -iniona4 5i5nisc in3ish in3ist in3itiv i3nitr 4i4n3ity i4n3iu 2ink in4ke4 in4ki in3kl -in4ky 2in3l inlan4 in4nag inname4 4inni inno4m i1no 4ino. i2no4c i4nodon i2nog -i4noge in5ogen. inoi4s inoki4 i4noli i3nom 4inomi i3non4 inos4t 4inot. i4nota i4noti -4inour inow4e in4ri. insati4 in4siani insis4 insist3 4insk 3insu insur5a i4n1t 4int. -4inta in4tee in4ter. inter3e intes3 intest4 4inthi in4thod in4tin in4tres intro3 2inu -inud4 4i3nus 3inv4 4invil i2ny 4i3ny4l3 2io 4io. i1o1a i1o3b i1oc io4cel -4iocu i1od io4dep 4iodic io5dize io5do i1oe ioe4co i1og ioga4 6iographa i3oi -i3ol 4iol. io5late. iol5a5ter io4list 4iolou i1om iomer4y io2n ion3a ion4ac ion4ag -ionar4i4a i2o4ne 4ione. ion3i 4ioni. i3onic ion4it iono4my i3op ior2a iora4bi iora4m -io3rati i4ord io4rit i4orm i3os 4iosi 4ioso io3sp io3sta 4ios4to i1o2t io3ta4 -io3tel iotempo4 io3th iother6my iot1i iot4s io3v iovi4 i3ox3 2ip i3pa 4ipala -ipa4n i4par ip3aro i5part ipat4edn i4pathi i2pe ipe3da iper4ac i4phat 4i1phe 5ipheno -i3pho i4phora iphori4 i4phyo 4ip1i i4pia ip4ic ip3i1e ip3ing i5pinn ipis4t ip3it -i2pl i4plac i3ply ipo4ce 4ipoda i4pode i4pody ipol4i i4pom ipor4 i4poro 4ipot -ipow4 4i4pp ip4pel ipri4e i4pry ip4sal 4i4pt2 ip4tal i2pu ip1ul i3pur 2iq -i1qua iq3uef iquet4 iq1ui 2ir 4ir. i1ra 4i3ra. i4rado i3ral 4iram i4rama -iran4 irassi4 ir4at i3ratio ir4cal ir4ch ir2d ir4do ir3dr ire5ar i1rec irec6trix -i4refu irel4i i4rely i3ren iren4t i4r3er iret4 i4rety irev3o ir3g irhi4 ir1i -i3ri. i4ria ir3ica ir3i3de ir3ido iriga4 iri6gibl ir4im i4rin ir3ing i1r2is ir4ites -ir4ius 5i5r2iz ir2l ir2m ir4mand irm5ativ irm6it ir4ni i1ro i5rog i3ron iro4ny -iro4po irov4 irre4f irre4la irri3 5irrit irru4 4ir2s ir4ty. ir3u3l i5run i3rus -i4russi irv4 ir1y i2s 2i4s. 4isa. 2isab isa4me 4isan i4sandr i3s4ant. isar4i -isas3 4isau isa3ve 2is3c iscar4y is4ces is4cio 4iscot iscov3 4isd i3sec 4i3sep -is3er iser3a is3es ises3t iset4i i3seu 4isex ish1 4ishan 4ishc ish4ch i5shev3 -2ishi isho4 ishon3 is1i 4isib 4is2ic is5ido 4isi4e is3il is4iny 4isio i4sior -isi4p i4sise i4sisi is3i4ta is3itiv is3ito 2isiv 2isk is1l is4la. 4islao 4ismi -ismis6sib 4ismo ismolo6 ismome4 4ismu i3so iso5b is4od is4ol iso3la 4isor is3p -ispan4 is4pec ispen6si is4ph i3sphe is4pi i4spie 4is1q isro4 2is1s 4issa2 4isse -is3s4i 4ista is3tak is4tat 4iste. istem4i is3ten. ist3enc ist4er is3teri i4sth is3t2i -ist3ib 4isticha is4tide. is4tif 4istiu is4tiv ist2l 4is3to istoi4 is4tolic is4tom is3tr -3istry is3tu istur2 is1u3a isub4u i5sul is3w is4we is4y i5syl i3sym i4t. -it2a 4ita. itab4 4itac i4taco i4tag 4itai 4i4tak4 ita4l3i i4taly 4i4tam ita3mi4 -ita4ne i3tani ita4n3o 4itante ita4p i3tar ita3riz itar3y 4i3t4as4 i4tasi i3tat 4itato. -i2te i3tell i3tem3i i3tent i4t3eo 2it1er 4itero it4erp 2i2th 3ithel i4them 4ithi -i4this 4ithmi ith3r 2i2ti 4itia. i4t1ic it3ica it3ici 4itics it3ig i4t3il it1im -iti4n it3ish it3ism i4tisti i4tiu i4tiva itiv3i i4tly i3to 4ito. i4tok 4itol -i6tology i4tom i4toph i4tora itori4d 4itoso itpro4 itr4 i4trame 4itre i4trem i4trep -i2tri 4itric it4ro. 4itropy i4try 4its it4sa it4sy i4t3t4 4itt. 4itta. 4ittae -4itte4 ittel4e 4it5ti itti4t ittu4 i4t1ua i3tud itu4de itu3di3 4itu3e 4itui i4tui. -it1ul itulat6 i3tum i4t3uo 2itur itur4a i4turgy itur4n 1i3ty it4y. 4itya i4tyi -i4tyl i4tyo i4tyr i4tys 4itz it4za 2i1u iuli4e i3um iu2mi 4iu4o 4iupo -4iur i4ush i4usi ius4t 2iv 4iv. 4iva. 4ive i4vel iv3ell iv3en. i4ver. -iv3eri ives4t ivest4i iv1i iv3ia i4vid. ivil2 iv5il. ivi3lis i3vine iv3io ivi4p -4i4vis. iv3ise i2vo 4ivor i4v3oro i4vot iv3v i1w iwan4 i4ww 2ix 4ixe -ix3i ixiv3 ixo2 ixo4d ix4yl 2i1y i4y. i3y4a i4ys i4yy 4iza. 2izab -4izal 4izar iz3ard i4zato 4izd i4zel izi2 iz4ie iz4it izz4 iz5zin 1j -2j. ja2 3ja. jac3u ja4de ja4g jama4 ja4mas jame4s 4jang ja4pon4is jargo4 -jargon3 jas4c jas3p 4jav javar4 jazz3 2jd je2 jeal3 je4b 3jec jectiv6is -jeel4 je3j 4jel. jella4 jel4lo 4jem. jemap4 jem4i jen4u jeop3 2jep jepu4 -4jer. jer4a jer4om jeron4 j4es 4jes. 4jesty jes3u3 jeun4 jew1 jh4v jh4w -j2i2 jil4 jim4p jing4 jink4 4jis 2jj j4je 2jk 4jl. 4jli 2jm -4jn. 4jna jo2 jo4b jo4ce jo4i2 4joie join4tr joli4o jon4a jo4p jo3ra -jos4e jo5sep joss4 jo4su jour4no jo3vi jow4 2jp 2jr 2js 4jt. 4jta -ju4a ju5bi 3judg jui4 4jul ju4le ju4lia. ju4lian. ju4li4u jum2 jump3 3jun -jun4a jun4e jun4gi juni4u jun3k4e jun4u 3jur jurid3 jus4r jus4ter ju4ti juve3 -4jvo 2k. 3k4a. 4k1ab kaha4 3kai kain4i kais4 kak4 3kal2 k4ala 4kalah -ka4ley ka1li ka4lia kal4id ka4lis. kalit4 4kalm kam4e kam4i 1kan ka4na kana4k -kang4w kanon4 kan4ti ka3o kao3l kap4i ka4pl kar4s ka4sa kash4e 5kateri 4kath -ka4thy 4kati ka4tol ka4trine ka5za 4kbe kbin4 4kbl k1c k4cy k1d ke4an -keat4 4k4ed keda4 ke4dr 1kee keel4e 3keep3 ke4ge kegel4 keiz4 kem4i kena4 -ke4n3i ken4ia ken4ti k4enz keper4 ke4pl keple4 k1er. k4era 4kerm ker4ni k4err -ker4si k4esi 3keta keth4 k4e4tr k4evi ke5ya 2kf 4kfe 2kh 4kh. kha4k -khani4 khano4 khay4 khe4da khet4 5khov kho4vi khuz4 k4i. kian4g ki4ato ki4bl -ki4d k1ie 4kier 4ki1es k4iew kil1o3 kilom4 4kil4s k1ily k4im k4in' k4ind -3kind. kin3ema 3kinesi k3iness kin2g king5l king4s k4ingt 3kin4i kin4kly k4inl k4inn -kir2 kis2 k1ish k4ist ki2t ki4ty k4iz k1k2 k1l2 klavi4 kld4 4kles4 -kle4sh 4kley k4lid 4klik k4lio k1m kn2 kne2 kn4i kni4fi 3k2no4 knol4 -knot3 k4nu knur4 1ko ko4cytot ko4hi 3koi4 ko4it kold4i 4kolos kone4 koon4 -koop4 ko3pe kopen4 kop4f ko3ra kor4t kov3s k1p2 kpa4t 4kpo kram4 kre4 -kroo4 krot4 2ks2 k4s. k4sar ks3b k4sco k4shea ksnif4 ks4p 4ks4t ks3v -k1t kt4a kth4 ktom4 1ku ku4a kuch4 ku4dr k4um kun4z ku4o k3up -2k1w kwar4 4kwe 4kwo 2ky kydi4 ky4e 3kylo ky4mi kyp4 ky4pr ky4ri -2l' 4l'o l4's 2l. l2a 3l4a. 5laam la3be 4labeg lab3ic 2labl la5bore -la3bori l4a4bri lab5yr la2c lac3ab 3laceo la4ces 5la3chia lach3r l4aci lack4y 4laco -laco4p lac3t 6lacteric 3lactic 4ladel ladi4e ladi4o. lad5is lado4c 4ladol la4dop la3dy -4laeo laes3 lae4v laf4f la4fi 4lafo 4laga4 la4gan lage4 lag3ell la4geo 4lagine -la4gist 4lag4o 3lah. la4hab laim3a lai4n 4laira laish4 4lakl la3lia 4lalist 4lalt -la3mar lam4bi lam4d la5melli la4met 4lamid lam3or3 4lampu l4an. lan4a 4lanac la5natio -l3ancho l4an2d 3land. 4landin 4laned lanel4 4lanes lan4ic lani4e lan4if lanne4 lano4ch -lans4i lanta4g lan4tr lao4 la5od5 lap3ar 4lapax 4laph la4pl lapla4 laps3i lapu4r -1la2r1 3lar. lar2a la4ran 4larbor 4larct lard4er lar2g lar4ge lar3i la4rine 3l4arm -la4ro. 4larop 4larsh 4larus larynge6 la4sa las4e lask4 lasmi4 las5ph las3ty 1la2t -l3a3tal 4latat la4te 4lateli 4lateriu 4latesh lath3er 4lathi 4latho la4thy 3latia l3atil -latil3i la4tim 4latine l5atine. lat3ini 4latinit 2latis lat3it 4l3ativi 4latom laton4 4l1a3tr -l3atro l3atry lat4ur la4ty 4latya lau4dat laudi4o laun5d launt5 lau3si 2lava la4vato -laver4a lavi4e lav3is 4lawa lax3 4layl. lays4 la2z 4laze lazi4e la4zin 2lb -l4b. l3ba lban4 l3be lber4d lbin4 l3bo l6bous lbun4 2l1c2 l4c. l3ca -lcali4 lcas4 l3ce l3ci lcia4 lci4nab lci6nato lci3tr l4cm l3co lco4n lco3ra -l4cose l4cou l4cs l4ct lcu1 2ld l4d. 4lde lde4ba ldens4 lder4al ld4er4o -l4des ldh4 ldi2 l4dia l4d3ish l4dist l4dit ldiv4 ldivi4 ld3l l4dox l4dry -ld4ti l4dup l2dy 2l2e l4e. le4a4d lea4f 4leano4 le2ar lea4s leas3a least4e -leath3 4leati leator4 4leaz le4bel le4bit l4e4bri 1lec 4lec. 4leca le4che lech3er -l4e4chi lech4w 3lecit 3lect lect4a 3lecula 6lecy 4l4ed. l3edi 4ledl 4ledn le2e -lee4l 4leem l5efact le4gad 3le3gal l3egan le1gi 3lego lego5rize 4legy le4ia lei4c -le4ina leit4e lek4 3lel. 4lelik lem3ati l4emb 4lem4e le4met 3lemic lem5ish 3lemma -lemper4 4lenar. 3lenc lend4w len4e le3nec 4leneu 3leng 4lenga len4gl le5ni5e len3is -le4nite 4lenor 3lent 4lentin 4lentu lentur4 4lenty le3o leob4 leo4ch 3leog le4omi -4leph le3pha le4pi. l4epin lepi4p le4pus l3era l4era. ler4al ler4e leres4 lern4 -l4e1r2o 3lero. le4ron 3lert 3l4es 4les. 4lesb 5lescenc 4lesd les4e 4lesee 4lesg -4lesh 4lesk 4lesl 4lesm 4leso le4sow 4lesr lessar4 les4sen less4i 5lessn 4lestin -4lestit 4lesto 4lesv le3ta 4letag le3thal leth4i 3letic 4leti4e le4tom le4try 3lets -4leui 4lev. 4leva l3evat lev3el le4ven 4levil levis4 levit4e lex3 lexi4an 4lexini -3ley l4ey. 4leyar 4leye. 4leys 4leyvo 2lf l4f' 4lf. l3fa lfani4 l4fant -lfa4ti lf4fia lf3h lfi4e lfilar4 l2fl l4fla l3fo l3fu lfu4ra l4fy 2l1g2 -l4g. l3ga2 lga4m lgan4 lgar3 lgar4y l4gea l4gev lgi2 l2gin lg3ing lgi4u -l3go lg4r l3gu 2l1h 4lha 4l3he lh4j l3ho l4ho. 1l4i. l4iae lian4it -li2ar liar3is libe4l lib3er lib4er. 5libera li3b4i4 2lic l4icata 3licen l4i3che l3icho -li4cic li4cid. 3licio liclin4 l4icn lico4t li4cr lic4te 5lic3ul l3idar l4idas l3idat -li4dea lid3i lidi4as 3lidif 4li4dil 3lidit li4dom l2ie l4ie4b lief4 4liem lie4n -l3ien3a li4ery li1e4s 4lifat li4fe lif4f li4fl lig3am 3ligat lig3ere 2ligib 5ligino -3l4ign lig3nan li3g4o l3igop lig2u l4ihi 4lihu 3l4ik 4likar 4liko lil4i lilit4 -3liloq 4limani. lim4bl limenta5r l4i4mi lim4ic limit5ab limo4g lim4pl l4imu l2in 1lin. -3lina. li4nall li4nant 4linar 4linas li4natu 5lindric 3lin3ea li4nen 4linet 2ling 3lingt -lingto4 3lingu li4nid lin3iz linoi4 linos4 li4nosi 3linq l4ins lin4sh lin4t lint3i -linu4 3liog 4liog. li4ol. lio3r liorat6i 3liot l4ip lipa4t li3pe li4pec lipe4d -4lipha 4lip4i li4pol liptic4 lipu4r li4pus 4liqua 6lique. lir4a 4lirat li5re lir3i -lir4ty 3lis. li4sa. li3sad lisar4 li4sato l4isb 4liseu 2lish 4lishl 4lishn 4lis4p -l4iste lis3ten lis3ter 4listine l4it. 2lita l3itan l3itar lit4c 1l2ite l4iter li3t4er. -4lites 1li4th lith3op 1liti 3litica 3litio 4litiv li4tor l4itr l4itu li4tui 5liturg -2l3ity li4ve liv4ed live3l liv3er lix3 l4iy 4lizin. l2j 2l2k 4lk. l3ka -lka1l l4kat l4kaw lk1er lket4 4lki lk3y. 2l1l2 l4l. llab3o llad4er lladi4o -llai4 lland4 llan4i l4lao llap2 llapu4 llas4ti l3lat ll3b ll3d 4lle l4le. -l3lea l3lec l4led l3l4eg lle4in l3le2l llel3i l3l4em l3len lle4no ll4er lle5r4a -l3lero l5lers. l4lese l3let l4leta l4lete. lle3v ll3f ll3g l4l3h l3lia l3lic -llif5lu l3lig llil4 llim4e llinar4 llin4iz lli4nos l5lio lli4ol llipe4 ll4it l4l3m -l4l3n l3lo l4locke llogu4 llo4mi llopa4 llo4pe l4lov llow4el ll3p l4lr4 l4ls -ll3t l3lu llu3c llumin4 l4lv ll3w l3ly. lly4e lly3g 2l1m l4m. l3ma -l4mab lma4to lm4e l4mes lme4t lm3ie l4mil l4ming l2mis 4l3mo lmo5na lmon4dy -lm4sh lmu2d l2my 2l1n2 4ln4e lnia4 1l4o. lo2a 3load l2ob lo4bal lobi4n -l6o3bl 4lock' 4locky 4locya l2od 3lodic 5lodio l3odr l4odro l2oe loes4 loeti4 -l2of log3a3r l4ogen l3oget 3logia 3logic log4n 1logu logu3i 4logy lohu4 loid4 -loi4t loiz4 4lole lol4i l2om 3lom. 5lomatic lom3er lo3mi lo4mic 3lomit 4lomitic -lo4mona lomul4 lomulti4 l2on2 1lon. lon4e 4loned 1long longer4 lo1n4i 3lonia loni4e -l3onis 4lonoi 2lonyl lood3 4lool looz4 l2op lop3in lopi4t l3opm 4loq. l4ora. -lora4d 4lorc l4orch lor4dy l4orem lore4t lo2rh l4oric lori4e lori4n l3oriz loro3e -loro3m lorometh5 loro3p l2o2s lo4se los3in l5osophic 5losophis 5losophiz l4osophy l2ot lo5tha -loth4i4e lotin4 lo2to 4loton lo2tr l4ott l2ou lou4c lou4de lou3i 4lour. lour4d -4lout lout4r lo2v lo4ve l2ow1 loy4d 4loys 2l1p2 lpa4c l4per l4pes. lpe4t -lp3f 4lph4 l4phant l4phar lpha4t lphe4n lphi6a. lphi4d l4phor lp4i l4pier l4pies -lpi4tal l2ple l3po lp3t lpu4 l4py. 2l1r lre4 lr4i lri4o 4lro 2ls2 -4l4s. lsa4my ls3b l4s4c 4lse lsem4 l4sew lshe3v l4sib lsin4 ls4it ls4k -4l3s4p 4l1s4t4 lstaf4 lsth4 l4stin l4stu 2lt l4t. lta4na l4tans l4tant l4tativ -l4taz l3ten lte4riu l2th l3tha lthy4 lth3y. lti3a lt4ib l3tic l2tie l4tily -ltin4 lti3p ltira6c ltis4 l4tish l4tism l4tist. ltiv2 lt4j lt3l ltm4 l3to -l4tol l4tose ltra4bl ltra3m ltu2r ltur3a ltur3i l2u l4u. 4l3u1a lual4 l4uar -2lub lubi4 l3ubl lub4li lu3br 3luce 4lucel lu3ci luct4 luc5ta 4lucti lu2d -lud5ib 2lue lu1en luf4 lu1i lui4a 4lu3id3 lu1l4 l4um. lum4bl 2lume 4luminic -4luminiu 3lumni 4lumnis 4lumny lun2 lu3na lun3de lun4e 2lunt l3unta luntar3 lu3n4u -lu4ny 4luo luop4 4lur. lu3ra lur4b lur4c 3lu3ro lu3so luss4 lus3t lus4t5i -lutan4 5lutato luth4 lu2tis 4lutiz lutton3 4lu4va 4luyl 2lv 4lva l4vable. l3vag -lvan4 lvar4y l3ven l3vet l5vin. l5vini lv4v 2l1w lw4e lx4a 1l2y l4y. -4lya 3lybd 4ly4bi 2lyc ly4cid ly4cina ly4cra l4y4dom 4lyer l4y2g lygen4 l4yk -4lylo 4lyme ly4mene 4lymo 3lymp4 4lymu lyn2 lyn4c l4yny 4lyo 2lyp l3ypl -ly4poi ly3ra 4lyra. lyr3i lyr4m 2lys 3lyse 4lysi lys4t 3ly4ti ly4tom 4lytr -4lyus l4yx 4lyzate 4lyzato 3lyze l4zg l4zi lzin4 lzo4 l4zy 2m. 1m2a -3m4a. maar4 4maas 4maaz 2m1ab mabe4 ma4bin m4abo mace4t 2mach mache4 ma5chine -mack4ere ma2cl mac1r mac3t mac3u m4ad mad3ic mado4 3mae mae4n 4maep ma2g -magal4 mag3a3z mager4 3magg maggluti6 2mag3i 3magn ma3go 2magog magor4 4magr 4maha -maho4 3m4ai4 maid3e maini4 main4ie 3mak 4maka. 4maku 3mal m6al. mal3ap ma4lat -m4ale 4malek 4malge mal4ib ma3l4in mal5le 4malley malli4 4malloy mal4m ma5lou 4mal2t -mal4ti 4malu 4maly 2mamb man3ag manar4 mana4s 2manat manc2 3manci 3mand ma3nen -ma4nes 4manes. m4ang 5mania man5if mani4l man3it 3mann ma4noi mans4l m4ant ma2nu -maph3r map4pe ma3q m4ar2a marbel3 4mares mar3g mar2i mar4ial 4marif mari4o. mar3it -ma3riz ma5rol mar4shi mar4st 4marty. 4maru mar3v4 mar2y. m4as 3mas. masal4 mas4ce -mas4e ma4sic ma4sor 3mas3t mas4th mas4tl ma3s2u 4masv 3mata 4matal mat4c 3ma3ter -ma4ter. m3ateu ma4this 4mathy ma4tik. m4atin mati4nal 3matio ma3tis 4matod ma2tog 4matogl -ma6toph matopho4 4matopod ma6tosc 4matose 2matou ma2tri ma3tu ma4tura maug4 maus4e max3 -maz4a ma4zl 2mb m4b. 4mba mbal4i mbardi4 mba4t 4mbe mbel3 m4bem m3ber -m4berie mbia4 mbian4 m3bic mbid4 mbi3en m4bies m4bik m2b3ing mbis4 mbi4v mb4l -mblem3a m5blin mbm4 mb4o mbo5na 4mbr mb4sa m3bu mbus3t mbus5ti mbut4 mbys4 -2mc mca4 mc4h mck4 mcma4 mc4o m4da. 4mdo me2 4m4e. meal3 meam4 -me3b 3mech me3co 2m4ed 4med. me4dan 3me3des 3medi med3ic me3die 4medied 4medil -4medis med4it. m5e5dy me3f meg2 3meg3a1 megam4 3m4ei meion4 mel3anc 3melia me5lie -4melik 3melio melli4a mel2t memo3 memor4 1m2en men2a me3nab men3ac men4ag 3mend -me3ne 4meness mene4u 4menh me3nia meno4m menos4 mens4 men4sc mensu3 mensur4 men4sw -3m4ent 4mentac men4tate men2te ment3er me3nu4 me3o meog4 4meon 3meop 4meosi 3meo3t -me3p me4pa meral4i 3merc merd4 merdi4 3mere 4merel mer3et 5mer2g 3meria 3meric -4merici m4eride mer4im mer6ob meroblas6 mer4og mer4ol 4merol. m3eron 4merso m4eru mer5us -4meryv 4mesb 4mesd mes3en 4mes4i mes4n me4s1o mest4 4mesta 4meste 3mesti mestic4 -4mestr 1met1a me4ta. met4abl me4tai 3metal 4me4tar met2as m1e3te me4ten 3meteo m4etes -1meth 4meth. 3metha meth4i 4methr meth6ylam 3metic 4metin me4toe me4ton 4metop 5metre -3m4etri 4metrid 4metris me4tru mew4 me3wa mex4ia 2mf mf4e mfer4o mfoun4 2mg -2mh 3m4i. 1mi3a 3m4ia. mia4m mian3a mianop4 4miar 3mias mi3b m1ica mi5chel -mi4chen4 m3icil micker4 3micro m4id. mid2a m5idal m3idat mi4da4z mid3ic m4idn mi4done -2mie4 m4ient mi1es mif4f 3miga 4miges m4igra mi4la. mi4lag mil4de mi3let mil4h -1m2ili mili4a. mili4al 4milie 4milis m4ilk 3mill milli4e mil4te 4mily mimed4 mime4t -mi4mi. mim5ic mi4mis min2 m4in. mi3na m4ind 3mine. mi3nee m3ineer mi4nei mines4t -m4inia mini4fo 3minio m4i4nit mi3niz min5ne5 mi3no mi1o m4iolo mi4ona 4mioni m4iot -m3ip mi3pe 4mipl miq4 3mir. mir2a mi4ram mi4ri mi4russ m2is 3m4is. mis1a -m4isco mi4see 4mises 2mish mi4si mis5le 1miss 4misso mis4sy. mis3ta mister4 mi4tal -mit2er 4mith m4itic m3itiv mit3iz 1m4itt m4iu m3iv4 mivi4 m2iz 4mko mk4s -2m1l2 m3le m3li mli4n 2m1m2 m3ma mma4da mman2 m4mandab mmar2 mmari4o mma6top -m4md mmeas6ure mme4da mmeli4 mmem3 mme4n m3m4i mmi6nato mmi4p mmis4a mmi4t m4mk -mmoni4a. mmu1 m3mul mmu4ne 2m1n2 m4n' m3na mnar4y mnav3 4mne m4nem m4ner -m2n3in mnon4 mnosper4 1m2o m4o. mo2a 4mochore mo4choro mo4chron mocli4 mo4coo 2mocr -5mocrac 5mocratis 5mocratiz 3mocy 3mo2d mo4d1e mod3ul 4mody. moer4 mo4gena mo4g5e5niz mo4ges -mo4i 4moi. moid4 moig4 moi4t 4moix 4moki mok4s mo3la 4mola. mo3lin4 4moliti -4mollit 4molog. 4mologue mol4or 5moly moma6 4momi 3mon mon2a 4monas. 4monb mon4dia -mo4ne 4monen mon3e3t 4monf mo4niani 4monid. 4monl monni4 mo3noe mon3olo mon3os 4monto -mon4tro mon3u mo4num 4monym moo4s 4moot 4mopau 3mophi 4mophoni mo2r m3or4ab mor3al -mo4rar 4morat mo4rax morbi4 4moredn moreli4 m4org 3moria mo4ries 4morin morit4 m3oriz -mor6iza 4moro mo3ros 5morp m3orrh 3mos 4mosac mos4e mosil4 4mosite 4mosm 4mosor -mo4sp m4ost mo3sta 4mosur mo4sy mo4tes 4mothy 4motis moto2 motor5i m6otropis mot4tle. -moun4te 3mou4s 4mousine mous4se 4mout. mo2v mo4vi movi4e 4moyi 2m1p m4pab mpac4 -mpa4i mpais4 mpan4d mpan3e mpani4a mpa5no m4pantl mpa4nus mpa3rab mpar4te mpa4s m5pa1t -mpa4tr m4paz mpe4dit mpe4l m4pen. m2per. mper6ato mpe3ria m3pet mpe3tr mph2 mphan3g -mphi4m mph4y m4phy. m2pi mp4ic mp3ie mp4ie. m3pig m3pile mpin4 m3pir mp3is -mp4le mp5les m4plev mpoti4 m4pous m4pout mpo4v5 mpress6in mpri4 mprin4 mpro3m mps4es -4mpt mp4ter mp5tu mptuar4 mp4u2 mpug4 mput3ab m4pv m2py mp3y. 2m1r mro4d -2ms2 4m4s. 4m1s4e m4spo m1s4t4 2mt2 m4tis mt4s 1mu muci4 mu4cl 4muel -2mul m4ulc mul4i mul4ler mul4lit 3muls m2ul3t4 multi3 3m4um 3mu1n2 mund4 mun4e -mun4ga. muni4f mun4is m4ur mu3ra mur3d4 mu4re murmu4 3m4us mus3t 3muta muth4i -mu4tis 2m1v 4mvi 2mw mwal4 mw4e 1my 2mya4 myal4i my4an my3c my4cer -my4col. 4myda my4do my1e my4log my4los 4myne my1o 4myr my4ri myr3ia myro4 -mys3 mys4i mys4u my4ti mz4e m4zl n2' 2n. 3n4a. 2nab naba4l n4abase. -4nacen 3n4aceo 4nachl na4cho 4nacif nac4r 2nact 2nad nad4de 3nadia nadi4e nadi4r -3nado. 3naea 4naec 3naem n3aero naes3 naeth4 2nag n4agena n3ager na5go n4agu -n4ah 5nah. 4naigret na4is n4aj nak2 nal4go na3li3a nal3ie 4nalin na4lit 4nalla -4n2alo n5alogic 5nal5ogis 4nalt 3nalysi 4nalysin name4ne na4met n5a3mid n4amme4 4namy. n2an -nanach5 3nance na4nes nan4ic nank4 n4anna nanom4 nan4tes 4nantiz nan4tr 2nap n4aph5o -napi4 na4pos n4apu nar1 3nar. n4ara n2ar3c n5archis nar4di 4naret nar3i 3naria -nar4iac na4rin nar4ite nar4l 4narou narri4 nar3t n4as. n4asa na4sa. nas5c na4sh -3nasi na4sit n4asm. nass4 n2as3t 4nasth 3nastic 1n2at na3ta nat4ab nata4k nata4l -nat4en 3nathem na3tho nat3ici 4natina nati4u n5atomic 5nato5mis 5natomiz na4tro 3natu natu4a -nat3ura nat4u4ran nat4u4rat 2nau 3nau. naud2 nau3di n3augu 3nauti 4nauto nautog4 nav3i -naxa4 3nay n4az 2nb2 n4b. nbak4 n4bd n3be nbeat4 nbes4 nb4l n5bli -n3bu 2n1c n4c. nca2 ncaco4 ncampho4 ncapsul4 ncar3c n4caria n5ceri nc4es n4ces. -n4cev n2ch n4chate nch3er n3chia nchi4ni n3chi4s nchis4es n3cho ncho5a ncho4lin n3chu -n3cib n3cil ncil3a n3cina n3cip n4ck nc4l nclat4 ncle4 n3c4o ncorre4 ncov4er. -nc4r ncre4v n4cs n4ct nc4tad nct4e ncu4ra n4cx n3cy ncy4cli 2n1d n4d. -nd3age n3dal ndama4 nda4na. n2da4r nd3ard n3dea n3d4ec n4ded. ndeform6 n3del ndel4li -nd3enc nd4ens n3denta nde4ra n3deri n3dero ndes5cent nde3t nd3g nd3ho n3d4ia nd5ibi -ndi6cativ n3dice ndic4to n3dic3u ndi4en n3d4if nd3ing n3dio nd4it nd4iu n4div. n4d4j -n2dl n3dler n3do n4dof ndo4mi ndoo4r ndop4 n4dov n4dq n3d4r n4drat n4ds -nd4sia nd4te n3d4u ndu4a. n4duc. nduct4i n4dunc ndus3 n4d4v nd4win nd4y n4dz -n2e 4ne' 2n4e. ne1a n4ea. 5nealo ne4ap n3eari ne4art ne2as ne4ata n3eate -n3eati 3neau ne2b n4ebe nebro4 ne2c n3ecd 4ne2d n4ed. n3edic ned3u neep4 -5neficen n3efici n3efit ne4fr 4negade neg5ativ ne3gr n3egy 1n4ei neid4 3neir4 neis4 -nek4e 4nela ne4let neli4a ne2lig nel3in n4elv n4ely 4nemari nem4ati nemer4y nem4i -n4emia 3ne3mic ne4mog ne4mone n4en. ne2na 4nene 3nent. ne4ny 1ne3o neo4t4 3neph -4nephore ne5phriti n3eq n3era ne3rad nera4l3 nera4m ne3r4as n4erd n4ere ne3rea n3er3g -n4eri 3neric ner5ist ne3rod n4er4ol ner4ri n4ersv ner4vi n4erw 1nes 2n4es. 4nesbo -nes4co 4nesd 4nesec 4nesen 4nesh 4nesm ne4sot 4nesp 2nesq 3n4ess nes3ti nes3tr -4nesv 4neswe n4et. ne3ta ne4tag n3etar netel4 4neter 3net1i ne4ti. 5n4etic 4netie -n4etn ne4toi ne4top n3e3t4r ne4try net4s n4ett 1neu 4neui 3neuro neu4ry. 3neut4 -neva4d nev4e nevers4 4nevi nevo4c nev3ol newal4 news3 n3ex3 nexami4 nexci4 n4ey. -n4eya n4ez 2nf2 n4f. nfalo4 n4fers nfi2 nf4l n5fla nfu3sib 2ng 4n4g. -n2gab ngal4le ngal4li ng4am ngan4i ngar4i ngar4y n3gat ng3c ngd4 nge4a. n3geal -n4ged n1gel n2gen. ngen4er. ngeni2 n3gere n3gero nger4on nge4t ngh4 n3gi3a n3gib4 -ng2ic ngin4 ng3ing n3giu n2g3iz n4gj n4gk4 4ngl n3gli3o n4gm ngn4 ngna4 -ngol4l n4gour n3gr n4gr. n4gs n4gt ngth3 n4gue n3gul ngu4s n4gv n4gw -n2gy n4gz 2n1h2 nha2 n4ha. nhab3 nhabit5an nhag4i nhas4 nhil4l 4nho nhs4 -nhu4me nhun4 n2i 3n4i. 1nia n4iac nia4g 4ni4ap 4niard ni3ba 2nibl n3ic. -n4icc n4ice n4ici n5icil n4ick n4i2co nic3o3t n3ics n4icu nic3ul 3nicula n4icy -nid2 3n4id. n4ida 3nide. n5idine n4idir n4i4do. ni4dob n4idu nie4l ni4et n4ifer -nif2f nif4fi ni4fin n4ifo nift2 ni5fy nig4i n5igmatic n4ii n4ij nik4 ni3ki -n4ila ni4la. ni4lam 3nile n4i3lin n4ility n4illi n4ilo 4nilw n3im nim3ag nima4te -n4ime 4nimea nimi3ta ni3miz n4in. n4ina nindi6c n4ine 3nine. nin4erti ni4net ninvo4c -nio2 n4ioc 2n3ion niona4t nio4r n4ios 1ni1ou n4i2p nipa4 4niple nipo3 nipre4 -3nipu 3nique. n1is n4is. 2n3ism nis4ma. n4ison nis4p nissari4 n3is3te n3istr n4it. -nit3ab n3ital nitar3 nitar4iu nith4 4nith. ni4thi ni3tho 3nition 3nitis. 1ni3tr 3nitro -nit4s ni4ur ni4use 1n4iv 4niv. 3niva ni2van nive4l 4njo nju4ra 2n2k n4k. -4nka n4kal nka4v n4kb 4nke n3kee nk3en. nker4s nk4e4t n4kf n4kh nk3ho -n4kie nk3ines nk2l n4kp n4ks n3ku n4kw n4ky. n3kyl 2n1l2 nlan2 nlan4d5 -nlar2 nlas4t nlau4 4n3le2 nlev3 n3li2 nlis4 nlo2 n3lu nlu3b n3ly 2n1m2 -n3ma n3me nmemo4ra nmensu4 nmercu4 nmin3i nmo2 nmon2 n3mu 2n1n2 n4n. n3na2 -nnaba4 nnait4 nname4n nnan4 nnar4y nna4ti. nnat3u3 nn4e2 n4ne. nne4ce n4necy n5nel -nne4le nnel3i nner4o n3net n4nh n3n4i n4nian nni4cu n4nime n5nin n4nism nnit4 -nni4v nni2va n4niza n4nk n4nl n3no nno4mi n4ns nn4u n3ny nnym4 n2o -3n4o. 4noam no3be nobli4 n4oce no4cer noco4 n4ocon n4ocr 2noc3u n3ocul no2dal -4nodrome 4noem n4oet 2nof no1g 4noga nogam6e nogen4is no2gr 1noi 5noint noi4sa -4nok no2la 4nold4e 4nolei no1li 4no3lit n3olith 4nomac no5mad3 3nom3al 6nomal. 4noman -no5mer. 4nometa no5me6tri 3nomic nom5ina no3mis n3omou 4n3omy. nomyce4 6nomycet no2n3 non4age -non4ar4y noncu4 no4ne 5none. none4a none4s non5f nong4 3nonic 4nonicit non4it n3oniz -4nono nonos5 nonta4 noo4t 2nop n4op. n5ophob n5opht no4phys nop4l 3nop3o3l 4nopolyl -no4pre nopter4o no4rab no4rary norator4 norm4 nor3ma nor4thin no4ry 4nosce nos4ed no4sic -no1sp 4nosph nos3t n6ostr 4notap 4notau 5no4te n3othei no3tice noti4e no3tis noti4t -no3tiz no2to 1nou 4nouk 4nouro 4nout 3nov. nove2 nov3e4l nov4ell 4nover no4vis -2now nowl3 now3s nox3 2n1p2 np4a2 n5par npat3en npa3tr n3pe npecu4li npe4d -nper3 nphenol4 nphi2 nphil3 npi2 npi4t np4l npo3e npoin4 npom4 npre4fa npri4 -npross4 n3ps npub3 n1q n3qu nquil4la n1r2 n4r. n3ra nra4k nras4 nrav3e -n3re1 nre2co nre4com nre3d nre2e nre4lat nres4t nret2 nri4de 4nro nro4b n4r4r -n4r4w 2n1s2 n4s. nsa4na nsan3i 4n4s3b ns4c nsco4 n3scr n4scry ns3d nsde4 -nsec5utiv ns4ed nse2l nse4la ns3elem n5sen. nse4na nsep3 n4ses ns3f n4s3g n3sh -n3si nsi4d nsist3e ns4i3t n4sit. nsi4ta n4site ns4k n2sl ns4lau ns4lay ns1mi -nsmo4 n3so n4soce nso2l nsol3ab nso3na n4sorb nspar3e n3spi2 nspir5ato nspiri4 nspor6ti -nspro4 ns3r ns3s4 ns4t2 n3ste n3su nsu3a nsubmers6 nsu5li nsur4ab n4suret n4sute -ns3v nsw4 n3swe n4sz 2nt 4n4t. 4nta nta4be nt3abi n4tage. nta4gen ntagi4u -ntag4n ntago3 n3tai nta4le n4talka ntam3i nta4na nt3anc ntan4go ntan4i4 n3tar n4tarc -ntar4ia. ntar4is n3tas n5tatio n6tativ n1te n3tee nte4l3 nt4en n4ten. n3tena nt2er -n2ter. n3tera nter5c nter3es nt4eri n6terman. ntero2 n3tes. nte4t ntev4 nt3g n1th2 -nth4e n4thel n3thi n4this n4thodo n3ti ntiar4 nti3di n4tie. nti3eg nti2er n4ties -nti4fi n4tily nti4nal n4tines n4ting nt4ingt nti4nou ntin3ue nti3p nti4pate ntip4s nti1r -ntiradi6c ntis4 nti3sc n4tish n4tism nt4ist n4tite nti4us n4tiz n3to nto4cr ntomi4 -n5ton ntoni4e nton4is nt4r ntrac5ta ntrar2 ntrion4 n4tro. ntro3s n4trose ntro3v n3try -nt4sa nt4si nt4so n3twin nt4z n1u1a n4ua. nuan4 nu6atel 1n4uc nu4ch nuch3i -3nucl nu4de nud4i nudo4 nu4e nuf4 n3ufa nu1i n3uin nu4is4 3nuk null4 -nul4lar n3ulo 1num n4um. num4bly 4numen 2numi nu4mis. nu4na 1nunc nuniform6 n1uo -nu4p 4nup. nur2 n4ura nur3b nur4l 4nury 1n2us 3nus. nus4c nu4th nuti4l -2n1v2 n4v. n3va nva5les nvar3 n3ve2 nvel6ope. nvermi4 nvers6a 4n3v4i nvict4u nvig1 -n4vt 2n1w nwi4 n3wo n2x nx3i n2y 1ny. 4ny2a n4y2c 3nych3i nyer4 -3nyin n4yl nyla6m ny4li 3nym. 3nym3ic 4n3ymo nyth4 2n1z2 nza4l nzi4a n4zit -n4zk o'2 o'4c o'4d o'4f 2o. oa2 2o1a. o3ab oab6o o3ace oac4er -oach5er o5act oa4d1 o3ag1 oag4r o4ag4y o3a5les oalesc6 o1an oan4er oan3i 4oann -oa6no oa4p o3app oar2 oar3i oar4s oas4t oat5er o2b 4o4b. o3ba 4oba. -ob3abi o4bad. obadil4 obah4 oba4li o4balo 4o4bb4 ob4bes o3bedi obe3li4 obe4lia o3ben -o3bi obil2 obiliar4 o4bing 3obin3i obi4no o4bins obit4e 3o4bj objectiv6 o3b4la o4blem -oble4s obor4 o3bot o3br o4bre o4bs4 ob1ul o4c. o3ca o4cag oca4la o4calu -o4cat. o4catel o4cativ oc4cas. occul4 o3ce 4ocer o4c3er3o o4cess. o1ch ochei4o o3chem3 -o3chet o4chic ochine4 ochle3 o4chop o4chorou o4chosp o4chrois o5chrom ochron4 och3t oci4ab -ocid4 oci4l o4cile 2ocio ocio4n o4cip oc4k oc4l4 o4clam o3co o4cod ocoe4 -ocolor4 ocommuni6 4oconi oco4py oco4q o4cosa oc3rac ocran4 oc5ratis ocre3a oc4ter oc4tu. -oc3ul oc3ume oc3uo o2cura oc3urat ocurato6 ocus5si o3cuti o3cy 4o4d. od2a oda4g -o2dal. o4dam o3dan o4dant ode2 o3dec odec3a odel4s ode4mat o4den. o4depe oder4i -odern3 o4des. o4desis o3dia o4d1ic od4ico odidac4 odi6dacti od1if odif4i o4diga o3dio -odio4d o3d4iu od4j o4doa odoac4 o4doe o2doli o4dolo o4dom odom4it o2domo o4dop -odor3i odor3o o4dosc 3o3dou o4dov o4droma o4dromo odro4s odu2 o4duct. od3ula odwil4 -o4e1a oe1i2 oe5ia oe1l o1e3le o3emp o3enc oendo6c oe3n4i o4eno o3enz oeod4 -o1er o4er4a o3erc oer4i oe5rotic o4er4u o4eso o4ess4 o2et oetas5 o3ethy 4o3et1i -oeti4u oet4s o1ev oev3e o3ex o2f2 o3fa ofa4n o3fe of3fe of3fi offi4e -of4fl of4fo 4offr off3s ofi2 o3fic ofi3ci of3it3 o4fle o3fo o3fu o4ful -o4fy 4og. o2ga o4gal o3game og3amo o4gano og3a3ri o3gas 2ogat og5atori og5atory -o1ge 4oge. oge4l o5gen. ogen4ism o4g3eno o3geo2 o4ges. 3ogeti o4gh 4ogic 1ogis -o3gist og1it 1ogiz o1g2l o2gli o4glie o3glo ogn4 og4nac og4ne o2go o4go. -o4gog o5gol o3goni o4gonis og3ony 3ogous 4ographa o6gress. ogrol4 2o2gu 1ogy 4ogyn4 -2ogyr4 o1h2 o3ha2 o4ham. o3he ohe4a o4hg o4hin o4hm o4hn o3ho oho4l1 -4ohs 4o3hu o4hv ohypo4 2oi o4i. oian4 o1ic. oi4ce oi3ch o4ico o4id. -oi2d3ec oid4er 4oides oid4h oid4ic oid5is oi4do oi3es oig2 oi3ki oi4l oil3a -oile4t oil3i oi2n o3inc oino4 oin4ti o4ir o4is. ois4c o4isi 4o5ism o5isti -oit2 o4it. o3ite. oit4i o4i4ty o4jv 2ok ok4am o1ken ok4es ok4ev o3kina -o3kine o4kn 5okous ok4sa ok4u okur4ti oky4m ol4' o4l. o1la ola4be ola4br -o4lac olack4 o4lalis o4lami o3lan olan4i o4lapa o3lar3 o4larc olar6is. o4larl o4lars -o3las3 olas4c olass4 o2lat ol3atr 4olc olch4 o4l2d ol4d1e o4le. ole1a oleast4 -ole4cr ol4e4d o3lei o3len ole4nod o4l3er o3let o3leu o4l2f ol4fe olf4fi 4olg -o3li. o1lia o5liat ol1ic o3lice ol3ich ol5ick ol3ida o3lidi oli4e oli4f ol3igo -4olik o3lilo o3line ol3ing. o3lini o3li3o o4lip 4ol3ish o3lism o1lit ol3ita o3lit4e -o3lith oliv4e 4olk o4ll olla4gi 4oll4e ol3l4i ol4lingl ollin4i olli4no 4olln ollo4p -ol3ly olmak4e o3lo. ol6ob o5log. ol3oge o4logist ol4ogr o2l3ogy o5lomb o3lomi o3lon. -ol1or olor3is olo4ry 1o3lou o4lour o4lp o4l1s4 ol2t olt5is o1lu ol3ub o4luce -4o2lun3 ol3unt o3lus o4luy 4ol2v ol4va olv3er. olx4 o2ly ol3y3a ol3yc ol3yh -4o4lyl ol3yp o4l1ys4 2om. o4mab o2mac om3achy o2mada o4madh omad3i o4mah o2mal -omal2i o4malle o4malo o4many o3mas oma4tik o4maton ombil4 om4bl ombo4r om4bry ombus3 -o2me 4omely om3ena omene4 omen4i ome3no o3meo om3er5a ome4ri om3eriz o3mest o3me4ta -o4meter o3meth om5e5tris o4metry ome3w o3mia om1ic 4omic. 4omica om3ici o3mid o3mim -omi4mi om1in om5ina. om3inen omin4ic. 4omins omi4se o4mish o4missa o3mist o3miu o1miz -om3l omma6to ommut5ab omni4f omno3 o3m4o omo4geni o6mometr o4monas o4monid o4mony o4mopa -o4mophon omo3th o4mp om3pil omp4l ompress6 ompro3 omp4te om4sh om4ti omy3 omy4t -omz4 on1 o4n. 4ona. 2onab onabor4 on4ace onactu6 on3age on4agen ona4nat o3nanc -ona4ne ona4po onapos6 o5narchia o5narchic 4onard onar4i4a. on4asm o3nasti on3b onback4 on3c -oncer4 on4chat 3oncila 3oncile oncu4m ond4d ond3ent ondur4 ondu5ra o2ne one4ag 4o3neal -one4at one4gat o3nei o3nemat o3neo on3er 2o4nes o5nesi on3est o4net2 o3neur o4nev -on3ey on5fl 4o4ng onge2 onger4o on2gi ong5is onglu3 on3go ongrel3 on3h o3n4ia -o4n3ic on3if on4ife on3ing o1ni1o o3nis. o4n3ish on4iti on3ito o1n4iu on3key onk4i -onk4s 2on3l on3m onma4tu onmerce4 onn4 onni4e onni2v on3oc ono4ce on4ocu on5oh -ono2l o4nol. onoma4t on3omis on3omo o4n3omy ono4phy onopo3 onopter4 6onora onos4 o4nost -onosti4 on3osy on3p4 onpart4i onpre4p onpre6se on3r 4onra onre4fo onre4la ons4 onsa4n -onsel4 onse4n onsid3 4onsk onspir5at ontag4 onta4l ontam3 ontana4 ont2i onti2e onti4f -4ontin onti4s ontortu4 on4tros on2u o3nuc o4nume onuti4 o4n3v onva5le on3w 4onymu -o3nys onza4 2oo2 ood1 ood3i ooe4l oo3ey oo4f oof3i oo3i oo4id ook1 -ook3ie o4ol. oo4lan ooly4 4oom1 oo4n3i oo4pe oop4er' oop3i oo4pl oo4ri. oos3in -oo3sp oos4t o4ot. ootch4i oot3er oo4ti ooz3i o2p o4p. o3pac 4opaeu op3ag4 -o4pagu o3pai o3pa4l o3pan opar6o o3patho op3athy 3o3pau o3pe3a o3pec o4pec. ope3di -opeg4 o3pei o3pell o3pen o4peng op1er 3opera oper4a. 4operag opes4t o3pex op4fr -o1ph oph4e ophe4l opheli4 o3phen o5pher. o5phia o3phie oph5ilis oph3ora o3phori oph3oro -o3phr 4ophrasi o4piab op3ic o4p3ing o3pini o1pis o3pit opi4th o3piu o3piz o3pl -3oplec o4p3li4 o4plo 3opme op3oda o4pody opog4e o3poi 4opol. opo4lat op3olis opoly3 -opo5ly. opol5ym o3pom o3pon o4ponis o4pophy o4popy o3por o4posit o4posp o3pou oppe4 -oppress4 o3pr opra4 opress4 4opresso oprin4 o4prod opro6f op3rol ops4 4ops. opse4 -op3si opsi3a o4pt opter4oi opu1 op1ul o1py. o4pylo o1q oqu4 o5qua 4oque. -o4r. o2ra o3ra. 4orac o4race. o4raceo o3rad ora4da orade4 o4rades o3rae o4rag -or3age 4orah 4o3rai 4o4rak4 ora4l o4ralist o5r4alit o3ram ora4ma or3ange o3rar o4rar. -orargy4 o3ras o4rasm orati4 ora4tis ora4tivi o5rav oravi4a. o3rax orber4 or3bi orbi4e -orbi4f o4rc orca4 or3ches orch4i4er 4orchu 4ord. or4dal ordeli4 or4dit o3re1a orea4l -o4rear o3rece or3eig ore4lat oreli4a o3rell oremon6 o4rence o3rens o4rent o4renz o3resc -o3resis or3est. ores4tr o4reth o3retic ore4v 4org. orga6na. 2orge 2orgi or4gis o3rhi -o2rhy o3ri. 4o3r4ia orib2 o2r1ic 4oric. or4ico o4rid. o4rida ori4dea ori3di o4ridl -o3rie ori4en. ori4f 4orifa ori5ge o3rigi o1ril o5rill o4r3ing o4rinu o1rio o4rip -o2ris or3ish orit4e o4rity o3riu or2m or4mater orm4e ormen4 ormini4 orm3is 4ormu -or4ner orni4a or4n3ie 4orno oro3a o3roc oro3et o4rogl o3roi or4on. oro3n4e oro3ph -2or4os o3rota orote4 oro4ti or3oug orphy5 orpo3 or3r4 orrob3 o4r2s orsa4l or4sen. -ors3in orst4 or3ste o4rt ort5ab or5tent ort3er. orth3e or3th4i or4tier. or4tres or4ty. -o1ru o4r3ul o4rv or3w o1ry ory4da ory4o os2 o4s. os3ab o4sace. os4ad -os4a4g o4salin o4sam os4an o4sanne os4ar osari4o o3sau2 os4ce o4schi os4ciat o1sco -o2s3cul os3e3cu 4osek osem4 os4f osgen4 4os4h o5sho os4ie osi4g osil4l o3sim -osina4 o4si4nan o3sine os3ing os3itiv os3ito o4situ osk4e os3m o3so. o4sod os5om. -o2sop4 os5ophis os3ophy os4or o4soth 4osov os4pa o3spec osper4mi os3pero ospirit4 os3sa -os4sai oss3c ossem4 ossil3 ossin4 oss3t osta4l os3tate os4tax oste3a o5stem os4tend. -os4thy osti4b os3tile ost3m os4tome 4ostomo os4tor os3tu ostu4m os4tut o1s4ty o3su -o4sue osulphu6 osur3g os3we o1sy4 osymme4 o2syne osyphi4 o4t. ot2a 4otag otam4e -o2tan otan4i ot5any ota4p o3tar ota5riz o4tasi o3tat o3tax ote2 o1tei o3teli2 -o3ten o4tenl o4tenoi otepa4 o2ter ot3er. ot4er4a oter6ism o3tes o4testa otes4tiv otet4 -oth5ecat oth3er. o4theral o4thou o2ti ot1ic o4tic. ot5icis o3tif o3til o4ting. o3tini -oti4po o3tist o3tit otiv4is o4t3l oto5f4 o4tog. otoion4 o2tom o5toma ot3o3mis ot3omiz -ot3omy o5tone otone4g oton4g o4tonus o4topy otor5is o4toro o3tra o2tri o6trophe o5trophi -o4trophy o4tropo otru4d o4try ot5ryo o4tt 4otte ottra4 otu3b oturi4e o4ty. ot1yl -oty3le o2u2 4oua oua4c oub4 ou5bl oubt3 ou4ce ouch3i ou4den ou3ette 4ouga -4ouge oug4h ough4t o4ui ou3is 4oul oul4d ou3m 4oun2 oun4cy ound3i oun4dry -ounk4 oun4sl oun3ter oun4ti ou4p oupha4 2our our3ag our4an our4c our4de our1i -our3is our4no. our3ous our4th o4us1 ou4se 4ousia ou4so ous4p ous4to ous4tr out1 -outch2 outre4 4ou3v ou4verne ouw4 o4ux o1v o5vak o3var ovar4y ov4ella o2ven -ove5na oven4i over3e overgent6 over3i o3v4ers ove4t ovet5o o2vi o3via o3vip o3vis -o4visa ov4it 3o3viz o4vk 4o3v4o ovol4t o4vr ov4y o2w 2ow. ow5al owa4n -o3way ow3di owd3l o4w1e owe4l ow3er owg4 ow3i o5witz. 4owo owof4 o4w3r -ows4a ow3sh ow3y ox3a oxam4 oxa4n oxcom4 oxe3m ox3i 4oxidas ox5im ox4on. -o4xu ox3y oxyl3a 3oxys 2oy oy1a oy4ad oycot4 oy4da oysi4 o4zal o4zene -oz2i o3zo1 oz4s 4p's 2p. 1p2a pa4ba pabys4 pa2c pa4ca pa4ce pach4e -pac3it 4pac4r pac4u 3pae pagan3 page4 pag4n p4agulu pagy4 pah4 p4ai pain2 -pain4t pais4t paja4 pa4k4i pa2l pal4ab pal5an pald4 p4ale palet4 3p4ali pa4lik -pal3in p4all p4aloi pan4a panc4 pan3el pan4gler pa4nia pa4nic pan5ici pani4o pan4is -panni4e pan5op pan4ti paot4 3pap pa3pa pa3pe papel4 pa4pet 4paph papi4 3paq -p4ar. par1a 5paras p5aratio par4chi par4din p4a4re 3paren pa3riet 4parina par3iso 5parit -4paro pa3roc pa3rol parot4ic p3arou 5parox 4partee 4pasm pas3qui 3pass pat4edl pa5tel -pa4ters 3path. 3pathic 3pathol 4pa3thy 3p4atic pa4tin pa4toi 3patr patro4 pau2l4 3paus -3pave pavi4e pa4z 2pb pbel4 p4ble 4pbr 2p1c pcervi4 2pd 1pe2 4pe. -4peab pe4ak 3pean pear4l pe4ba. pecas4 4pecha pe3cie pecti4v 2p4ed 4ped. pe4dar -peda4t pe4date 3pede pedes3t 3pedi pe4di. ped3im pe4dio pe5dol 4pee4d pe3f pefoun4 -peg4i 4peha pei2 3pe3ia peip4 peir4 pek4 peki4 4pelet 4pelik pelli4e pe4lo -pel4ter pel4tr pe4nan p4enc4 pend3e pe3nia pen4ic pen4is pen3i3t p4enn 5penny pens4e -pentar4i pen4tho pe3nu 3pep pe4pe pep4i pep3s per3a p4era. 4peralta 4perand percen4 -pere4t 3per3g 3peri pe3rial per3ic per3ig per3il per3im per3in per3ios3 periot4 per3it -4perl. perlu6n p4erm per4mism per3n pernatu6 6persedel 4persedy persh4 pe3rus per3v p2es -pes4car pes4th pes3ti p4et 3pet. 3petalo pe3ten pe4tio pet5iola pe3tra pe4tra. pet4rog. -pe4try 3peu peu3t 4pev 2pex3 3pexi 2pf pfal4 2pg 4ph. pha4co phag2 -3phage. 3phagia phak4 5phalia 3phalic ph5a5lis ph3alit 3phant 4phantu 3p4har phare4 4phareu -phar3is 4phars 3phas 3phat 4phe. 4phea 4phed phe2dr 4phelio 3phem 4phep 4pheret -ph4es 4phese 3phet 4phet. 4phets 4phial phi4b phi4di 3phil. 3phile 3philia 3philic -phi3lis ph5ilism phi4ma phi4n ph5iol 3phis 4phiste 4phistop 4phis4tr phi3th 3phiz phl4 -phle4 4phli 5phloe 1pho 3phob 4phobl 4phobo ph5omo 3phon phon4e 4pho3n4o ph5onou -4phony 3phore 4phori. 3phoria 3phoric 4phorn 3phos 3pho3t4 4photy 4phox 3phra 4phrasi -3phre ph3ro3d phros4 ph3t phu3i phu4ra 3phus 3phy phyc4 4phyg phy3la phy3le -phyl4i 4phyly phyr4 phyro4 4physa 4physis 4physit 4phytou p2i 3pia pi4ag pia4n4 -4pianu p3ic. pi6cable pi4ceo p4ici pi4cie pi4cil p4icl picom4 p4i4cr pi4cy. pid4e -pi3der pid4io 3p4idit p4idol p4idur 3p4ie4c pi1en p4ieno pier4 pi1es pi4ete pi3ez -pif4 pi4fl pi3geo p3iglo pigni4 p3igram p4ii p4ik p6ilatory pi4lik pil3ing pi3lo -pi4mol pinc4 pin3d4 pin4ery pi4net p4ini 3pinio pink3 pi3no p4int pin4ta. 3pi3o -4pioc pi4ole pion4a 4piony p4i2p pi4pag pi4ped pi4perin pi4phor pip3i pipis4 piq4 -p4ir pir3ic piri4ti pir4y 2pis p4is. 3pisc piscar4 4piscope pis4p pistem4 pis3th -3pisti pis4tr pi5tab pi4te pit2er pi4tl pi4to 3pit3om 4pitomic pi5tou pit4r pittel4 -3pitu pi4ty pi4v piza4 p3izo 2p1k 1p2l2 2pl. 3p4la2 pla4co 4placu 4plad -pla3g planc4e 4pland plan5et 4planos 4plap plar4 plas3te 4plasy plat3in plau3s 5ple. -ple4a4 p4lec 3pled plee4 3ple3g2 ple4ga plenar4 4plene. p4lent pleo4c ple4ri 4ples -plev3 4plev. 3plex 4pley pl4i 3p4lie 4plig 4plik pli4nan 4plit 4plj plo4d -4plof 4plog 4plol plum4be plu2mi plur4 ply4m 2p1m 2p1n p2neu 4pno 1p2o2 -poad4 po4cer pochlo6 4pocri pocrin6 3pod. 4po3da4 4podes po3di 3podia 5podic. 4podis -5podiu 4po3do po4dos po4dot po3dy 4pody. podyna4 3poe po4e. poe5i poendo6 po3e4t1 -poet3i po3ge poge4ny po3gl pohepa4 po4i 3poi3e poi3k poin4t po3ken pok4ier 3p4ol. -po3la po4lam pold4 po4les 4poli. polian4 4polis polit2 3polita p3o3liz poll4 pollin4 -4pologa 4pologia 4pologu po5loni po4l3y poly3s 3pomo po6mology pomp4 pon4ac pond3en po3nen -pon4g 3poni po4nia 4ponin. 4ponist 4ponite ponti4 4pony 4popau po4phy 4pophyt pop4l -popla4 p3ople poploi4 pop1u 4popy. po3r4a po4rade 4poree po4res p4ori po4rif po5rife -por4it por5ten 4porto por4tre por4ty 3pos pos3a pos5edl 4posem pos3in 4posio pos3it. -4posph pos3s pos5tasi pos4th 4postol pos4tom 3pota 4potace po3te poth5eca 3potheo poth5es -3poti poti4e po4tor potri4 poun4d3 3pour 4pousl po4ve po4w3 pow4n pox4a 2p1p2 -p4p' 4pp. 4ppa ppal4 ppara4t ppar3e 4ppe ppeli4 ppend6ant ppe4s p4pes. ppet4i -ppe4tr pph4 4p3p4i 4pp4l 4ppo ppo4d ppoin4 ppo5site 4ppr ppro4c 4ppu p4pv -1pr2 4pr. prac3 4prae prai4 pral4 pra4ni praorbi4 4prava 4praw pre1 prec4 -3preci pre5co pre3e pre4ene pre4fac pre3i pre2j prel4 pre3le prem4 pre4mis pre3p -pre4p. preph4 pre4posi pres4o 3press pres4sag pres4u pre3t4r p4ri. pri4b 4prico pri4cy -pri2e pri4es 2prima p5riman4 prin2 pring3 prin6kler pri4nod prin4t pri4s pris3o pri4val -4privat pri4z pro4bl prob5le p4roci 4prodo pro3e pro3i pro6ject. pro3l2 prola4m prolo2 -prolo6gis promp4 pro5no pro4pis propu4 pro1r pro4sp pross4i pro5sto pro1t prov5al provo4 -4prox. pru4c prun4 pru3r 2ps2 4p4s. ps3ab p4sal4m psa4m p4sb ps4est p1sh -p2sib psi4f psi2n ps3ing 4p1s4t p4sv p1sy. 3psyc 2p1t 4p4t. p2tan pt3ana -ptas4 p5tat 4pte pt3ed pt2er pter6ol pter4om p3t2i p4tib pti4f pti1m p4tin -pt3ing pt4is ptiv6ism 4ptl pto4mato p4tru p3tu ptur3a pt4y 1pu pu3a 4pub. -pu3be pub3l pue4 pueri4 puf4 pu4ga pu3gi pugli4 pu4gr pu5iss p1ula pulei4 -p4uli. 4puma pu2mi p4un2 pu4n3is 5punt pupi4 puppe4 pu1ra pu4ram puran4 pur3c -pu1ri 4purim pu4rism pu4rist. puri3t puru3 3pus pu4sio pus3t 3put. 3pute pu4to -4pv. p4va 4pvi 2p1w py4al p4ye py3el pyg4ia. 4pyla 4pylit py1o py4s -pyxi4 2q. 4qaz qi4b 4qq. q4to qu2 2quab q3uabl 4qua3m 3quatic qua3v -4que. 4quelic 3queo 4querr 3ques ques4s 3quet. qu4h qui3a 4quiem q4uin 3quin. -quinar4y 3quip 4quipa 6quipoise q2uis qui4sa 2quita quiv1 4quiv. qui4ve 4quivok 4quivoq -qu4o2 q4wa r2' 4r's 2r. r2a. 2rab raba4r rab4be. ra4bidi ra4bis ra3b4u -r4ac. ra4ca. r4acar racas4s ra3che ra3chi ra4chide 5rachn r3achu r2aci rac3ic 4racid. -4racki 4racl 4racot r4act2a rac4tan r2ad ra4dan radapt4 ra1di r4adic ra3dis r4adism -rado4 4rador ra3dr ra4dul 4rady3 r4afe r4agino 4ragon ra3g4r ragra4 ra4gro r4ah. -4rainer ra3ist r4ak ra5k4l 2ral rala4z r4alite r4allu ra4l3og r4a4lu r4alys r4amba -ram5bl ramel3i 4ramer r4a4mete r2ami 5ramida r3a3midi 6ramini 4ramis ra4mit r4amu ran2 -ranar4y r4and ran3di ran4dies ran4din 4randr ran4ge rang5eri ran4gui r4a3nia ran4if ra3nio -rani4to ran3n 3rannic r3anniz ranopi4 rans3el ran4sur ran4tr ran3ty ran1u ra4ny 3raphe -4raphed 4raphes 3raphis 3r4aphy ra3pis r4apn 4rapody r4apsi 4rapte rap5ul ra3py r4ara -4rarc rar4chat rar6chist rar3ef 4rarg r4ariu 4rarl r2as. ra4sal rasca4 r4ased ra3seo -ra3si 4r4asia r4asin ras4p ras4sie 6rasticho r4as4u rat4en4 rat4er3 ra4tid ra4tin. ra4tivis -r4atiz 2rato ra4toc 4ratom r4atus raud3u raugh4 raus4 raux4 rave2 rav3el ra4ver -rav3er. raver4sa ravet4 rav3it rawa4k rax3 r4axe 4raxel ra5zen razi4 4razzi rb2 -r4b. 4rba rb3ab rbal3a rbar2 rbarba4 r3bea rbe2l rbel3iz rbe4ri rber4n r4bes -r3bic r4bicide rbicy4 r3bid rbi4fi r2bing rb3ing. rbit4 r4bles rb5less. r4bn r3b4o -rbo3liz rbo4rac rbo6real. rbrat4 r3bu rbulk4y rbu3ri rbut4t 2r1c2 r4c. r3ca rca4b -r4canto rcap3i rcat6as r4cea r4ced r5cede r3cel rcelo4 r4cem rcen4ta rcep4te r3cera -rcer4eb r4cest r4cf 4r2ch2 r3cha r4ch3er. rcher4o r3chest r3chet r3chi rchid4 rch3m -r3cho r3ci r5cib rci3c r4ck rcle4 r3co r4cog rcollec4 rcor4i rcor4n4 r4cq -r4c3t rc4te r3c4u rcu2m3 rcuti4 2rd r4d' r4d. r4dal. r5dam r2dan rd4ancy -rde2c rdec3o3 r3del rdeli4a rdel4li rde4mo rdeni4 rdepend6a rder4or r1di r3dia rdi4en -r3dio rd3ise r4d3ism rdit4e r3diu rdi4vo rd3iz rdli4l r4dm r4dn r4dop rdo4sa -rdo5ta r4ds r3d4u r2e 4r4e. 4reab re3add re3adm 4re3ado re1al 3reali re1an -4rearm reas3i re4ast reast6e reas5y rea4th r6ebrate. rebu4 recas4 re3co4r re2cre rec3rea -1rect 4rectc 4rectd 4rectex rec6trix. re4cus4 2r4ed 4red. red2e rede4m re2dic red3ica -re4dit 4reditam re2dol redoni4 4reem re5endo ref4 ref5era ref5f refu2 4reful re6fuse. -4reg. reg3im regis3 reg4l reg5n 4rego re4gol re4gory re1i2 rein3a re4ist. reit4er. -rek4 re4lati re5latin re5latio re3lea re5len4 4relet re4ley rel3ish re4lude re6ludin 4rely. -re3mat r4emen re4mene re5mic remi4e re4mif 4remous r3emp r4en' r4en. 3rena. 4renai -re4nar renatu4 r4enb r4ene ren4eq 4renes r4eni re4nif r4eno ren3ov r4ens ren4tag -r4ente 5renthes ren4tist 4renz. re3o reo4e 2reot reo4tr re3pa repen4 rep3it 4replei -reposito6 rep5roba re3quit rer4 r3er. rerat4e r4ere rerec3o reren4 r4eri r4eroo res5ag -resar4i resel4 4resh r4esi res4im resin3 res4m re3spe res4see ress3ib res6so. res4to -re5stric res4try re3ta re5tabu retal3 3retar re3tel re5tem re3ten ret4er re4ters re3th -4rethy re4tic. ret2r re3tra ret3ro re3tu re3u2p 4reux re1v rev3ale revic2 rex3 -rexa4 r3exe r3ext re4za 2rf2 r4fab r4fer. rfer4ent r4fie rfi4l rflo4 rfo3ra -rfu2 r4fv r4fy 2r1g2 4r4g. r3g4a r4gab r4gag4 rgans4 rga4s r2ge2 rge3m -r3gen r3geo r3gery r3get rgia4 rg4im r2gi2n rg3ing rgini4 rg4l rglar4y r4gn -r3go rgon3is rgon3iz rgo4p rgo4t r4gret r4gual rguer4 r4gues rgu4mi 4rh. rha4b -rhag4o rhas4 rhe3al rhel4 rhen4ic rhepa4 rhe4um rh4i 4rhil rhi3z rhod4 rhon4i -rhu4me rhumer4 rhy2 rhya4 2rhyd r4hym 3rhyt 1r4i. ria4ch 4riad ria4da ri2ag -3riah 1rial 4rialisa 4rialla ri3a4m riam4e ri3an rian4it ri4ano ri3ar 4riat. 2riatr -2riba rib3al ri4bel ribe4r rib3er. rib3i r4ibo ribra4 r4ib2u r4icc ricente4 ri4chi -2rici ricid5a 4riciu ri4cles r4i4clo ri4coc r4icol ri6com r4icop ri4cy. 4ridae ri4dean -r3i3dec 3r4idic 3ridio 4r4idiu ri4doph r4i4ed rie4f ri3e4l ri1en 3rienc 4rientee ri3er -ries4 3rietal 2riev 4r4ifican ri5fy ri2g rigen4t rig3er righ4 rigi4l 3rigin 4rigin. -ri3gl ri3i ri3ka ril2 4ril. ri1li riliter6 riliza6b ril6let. rilli2 rilli4u 4rillo4 -ri4lo. ri3loc r4ima. 5riman4d r4imin rim6inan rim3it r4in. r2i3na r4inch rindi4a rin4dy -rin4e rine4p ring5hal 4ringn rin4io rin6kler. r4in4o ri4nodo ri4nou rint5er. r4inth rin4ti -4rinus ri3o2 r4iola. r6iolate r4ion. rion4is r4ior 4riosov rio4t1 riot3o ri2p ripho4 -riphthon6 ri4phy riple6g rip5lica ri4pol4 ri4pur ri3q r3irr ris4c 4risch ris5en 4risiu -5risiv riso4p ris4p 4risp. r4iss r2ist rister4 risti4ch ri2t ri3tal 4rites ri5tes. -ri4tiat rit3ic ri4tist r4itos rit4s ritur4 rit4y r4iup r4i2v ri3val riv5ativ riv3el -riven4 riv3er riv3et 4rix riy4 2riz ri4ze riz4zer r4ji 2r2k r4k. rka4n -r5kat rk1er r3ket r3kin. r5kins 4rkp rkpa4 rkt4 r1l2 r4l. rlan4di rlan4t -rlas4 r3le r4le. r4led r4ler r4lest r4leto rle4tr r4levi r4leye r4leyv 4rli -rl4in rl3ish r4lk4 r3lo rlunar4 rlus4 r1m r4m. 4rma rm3ab rmace4 r4maes -r4mald r4malg r4mallo r3man. rmaph3 r4mater. rma6toc rma4tog rma4top rm5atory rme3at rme3c -rm5edl rme4la r3men r2mer r4mest rm4et rm3h 4rmi r3m2ic r3mid2 r2ming rmini4f -rm4i4p rmi4tia rm4iv rm3l r4mn r3mo rmoe3 rmo4rie r3mu r5myl rmyster4 r4n. -r1na r4nabl rna4cl r3nad rna4l rnal4li r4nanc r4naro rnar6y rnat3u r3nau rnau4t -4rne r3nea r4nee rne4g r3ne4l rneli4 r2ner r4n3er. r4nesb r3nest r3net r3ney -4rnh r1ni r3nia r2n3ib r3nic r4nies r2n3in rn4io rn3ist r3n4it r4nite r2niz -r4nk r1no rnop4 r4nove rnt4 r1n2u r4nup r2ny ro3al r4oan ro4an. roar3 -rob3ab r4obe. ro4bile ro4bin rob5ing 4robiu ro3bl r6obla ro4ble rob5lem rob3or roc4e -roce4l ro4chos r2oci rocio4 4rocit ro1cr rocre3 r2od ro4dan roden4 r4odi ro3dis -3rodit r4oed roe3l 6roetha rof3f ro5fil roge4a rogen3i ro4geu rog4i 4roglu r4ogy -ro1ic 3roica roi5der rok4e rola4mi 4roles ro1li4 rollo4 ro3ly ro5lyse r4ome. ro3mel -rom2i romi4na romonta4 rom4p romp4t ron2 ro1na ro3nau rone4g ron4em ron4er r3one4t3 -ron4gr r2oni ron4ism ro3nist ron4ten r4onti r4oo rook4s 3room. roo4n ro4o4t 3root. -r4opae ro5par roparie4 ro3pel 4ropia ro5pin. ro3pis ro3po 5ropolita ro4posi 3ropous r4ops -4ropsi ro3py 4roq ro5rati rorato4 rore4b 4rors r2os ros4a ro4san. ro4sann ro1sc -ro5shi rosi4e ro4sot ros3per ros4s ros4te ro4sy. r2ot rotam4 rote4l rot4er4 5roteri -4rotesi rotes4ti ro4thes ro4tic rot3id4 4ro3tis ro4tonu rot3s rot4tag roturi4 rot4y4 ro4u -rou3b r4ousi 4rouss. rous4t ro2v rov5al. rov3el rov5en. ro4vo4c rox3 4rox. 4roxi -ro3zen rozi4 2r1p2 rpass6i r3pedo rpentar4 r4per. rpe4ti rph2 rphy5r rp4i r2p3ing -rp5ing. rp4l rpla4s rploi4 r4pn rpo3r rpo4ri rpo4s rpos4t rpre4te rpri4e rpro4m -rp4s rp3t2 rpu4ris r1q rqu4 rques4 2r1r2 r4r. 4r3r4a rr4e rre4co rrec4tit -rre4for rre4fu rre4i r2reo rre4pa rresol6 rre4th rrev3o rrh4 rrhe3a rrhon4 r3ri -rri2a rric3u rri4en rri4o rris4te rritual6 r4rk r4rm r4rn r3ro rrob3o rro4da -rrog4 rro4l rrol4l rron4 rror3i r4rs r3ru rrubi4 rru3g rru4p rrur4 r4rv -r3ry 2r1s2 4r4s. rs3ab r3sal rsal4t rsar4ia. rs3b rs4c rs3d 4r2se r3se4a -r3sec rse3cu rsel4 r3sen rse4no rse4ph rser4 rs4es. rse4u r3se3v rsever6at r3sey -r3sh r4shie r4shy r2sib r3sic4 rsica4 rs3ing rsis4 rsist6 r3s4it r2sk4 r3ski -r3so2 rson3at rspo4 4rs4t rst4e r3su rsu5an rsuggest6 rs3v 2rt 4r4t. 4rta -rt5abi r4tage. r3tar rtar3i r4tase r3tat 4r2te r3tea r3teb r4ted r3tee r3te4l -r3tem r3tere r3teri r3terl r3tes4 r3tex r4tf rth4a r4thei rth4erly rth2i rth4in -rtho4gr r4thors rthusi4 r3thy rt1ib r3tic r4ties r3tif rtil2 rti3lis r4tily r4tines -r4ting r3tins rt4inv r4tipl r4tism r4tist. r4tistr r3tit rt4j r4tles rt5let r4tly -r3to r4toma r4topl rto4te r4tress r4troo r4ts rt4sho r3tu rtub4 rtur4be r3twi -rtyr3i r2u2 ru3a ruant4 ru3be ru3bi ru3ci ru4cin ru3e2l rue4li ru3en r4ug -ru3ga ru3id ru3in r4uk ru3ma rum4bi rum5bl ru3me ru3mi 4rumn rum4ph run4e -run4s run4t ru3o rup2 3rupt ru3ra rur4e ru3ri r4us rushing6 3rusp ru3ta -rut5is 4rutz r1v r4v. rva4d rvato6ry r2ve rv4el r3ven rven3i r3ves rves4t -r3vet r3vey 4rvi r5vi5en r4vies r3vil r3vin. rvi4p rvisi4 rviso6 rvis4u 4rvy -r1w r4w. 4r3wa rwet4l r3wi 4rxe rx4y ry1a rya4t ry4di r2y2g ryg3i3 -4rylas ryllo4 ry4loc ry4lyl ry3ne 5rynga r5ynx ry1o ryopi4 ry2p ry4ph rys4e -rys4i ry4so rys4ton ry3te rytel4 ryth4i 3rythro 6rythrops rz2 4rzu 2s. 1sa -3s4a. 2s1ab sa4bat sab4o 3sacch sa3che sa4cl s4acr sa3cral sacros4 s3act s4adda -s4ade sa4dom s4age sa4gi sag4n s4ah4 saha4 3sai sail3 4saim 4saise 3s4ak -sa4lar sal4at s4a4le sal4id sa3lie s4alin sali6na. 4salind sa3line sal4ish sal3iv 4salmi -sal4mon. salti4e sal4ty sa3ma sambi4 sa4men 4sami sa4mo sa4my. 3s4an. sa4n5at 3sanc -3sand san4di san4dy san4gl sanhe4 san4in san3it 4sanite san4tr sa4n4u s4aph sap4i -sa3pie s4ar. sar4a sar4ce sard4 4sarea. sar4gu sar4iae sar4ios 4sarm sa3ro sar2y -s4as. sa4si sas3s4 3s2at sa3ta sa4tir 4satl satra4 sat1u 3saur s4ava sava4g -sa4vel sa4v3i2 sa3vo4 4savu 5saw. 3say 2sb s4b. s3ba sban4 s3be sbehav6 -sb4j s3bo sbor3o 4s3bu s2c2 4s4c. sc4a scag4 scal4d sca5len sca4lig sca4ly -scan4t sca4p sca4r scar3ab scar4c scar2f scar4p scas4 s1ce s4ced s4cene s4cen4i -scerp5 scet3 sch4 2sch. s4cha. s4chal s4che s5cheat schil5l s4cho s3cib scic4ulu -s4cio. s4cion sci3p4 s4cist scit4 sci5ta sc4l4 scle1 scler4 4scly 4sco. scocar4 -scof4 4scog sco4le 4scom 1scop 3scope 3scopic 4scopis 4sco3py s4coto scov3e sc4r -scra4 scro4 4scrys 2scue 2s3cula scu4riu scu4ru 2s1d s4d. s3da s2e 4s4e. -3se2a. sead4 seak4 sear4 seas4 3seat se4aw secer4 se3cl se2co secon4 3sect -sec3ul se4cy 2se2d s4ed. sed3en sedil4 sed3im s3edn 3s4ee 2sef4 sefa4 s4e2g -seg4n seg1r 2seh se4hen 3se2i 4seik sek4 3sel. se4la. se4lagi s3eleme selemen4 -selen4 4seles se5leu 3s4elf sel4ic seli4e 4seline 1sell 5selle. 4sellen 2sely4 sem1 -sem4a semain4 semal4 se3man se5mati sem5b 3sembl 2sem4e semer4 3sem3i3 se4mi. sem4ic. -semi5p semis4 semit4is sem4m sem4or 1sen senar4y sen5ato 2senc 4sene sen4h 3s4eni -sen6ic. 3seno se4nol se4nou 3sens sen4ti. 4sentis 4sents s4e2p sep3a se4pal sepa3r -se4pho sepho4r 3sept se3q ser2 2s3er. sera4d se5raphic ser3at s4erd ser4e 3s4eri -se3ria ser3ic 3s4erl sermon3 se3rol 4ser3p s4err s4ers 1sert 4sertico 4sertl s4e5ru4 -3serv 5servab 4servas serv3er 4ser3vo 2ses s3es. se4sa s4esc ses4o ses3q 3sess -s3est. 3s4et 4seta 4seterr seti4 4seto 4se4tr s4eu 3seur 3seus 3seve sev3en -sevi4 4sewo 3s4ex 4sexp 1sey 2sf2 s4f. s3fe sfea3 s3fi2 s3fo 2s1g2 -4sga s3gr s2h 4s4h. 3sha. 3shad shad5i shad3o sha4ga sha4ko sh4al sha4man. -sh4ap shar2 sharmo3 sharmon4 sharp3 4s4hc sh4chi shdraw4 shea4 4shead s4hed sheel4 -4shei s5heim shel4li 4shen shep3 4sh3es 5shev3e she3vi s4hf s4hi 3shi. shic4 -4sh3ier 4shies shim4i 4sh3ines sh4ino sh3i2o 3shi4p 1shir s4hk 4sh1l 4shm shmi4 -4shn4 1sho2 sh4od 4shol shoot3 sho4p sho4r 4shors short3 s4hp s4hr shriv4er -4s4ht 5shug 4shum 4s4hw s4hy shys4 1si 3s4i. 3si2a2 s4ia. si3ac s4iaca -4siad s4ian3 si3b siber4 si3ca 2sicc s4ich sicil4 sicili4 sico4t 3sicu sic3ul -si2d 3side. si4dec sidel4 side4n s3ident sid3er 3sidera s2ie 3s4ie. sie4n si4era -2si1es 4sies. siev4e si3f si4fe siga4 3sigh 3signal 3signed 5signi 4sih siha4 -s4ij sik4i 3sil. si3la 4silar sil4e siler4 si2li sili4ciu sil3i4e 4silin. 4silisc -4silisk sil3iz 4sillu sil4ty 2si5ly s2im 2sima s4imac s3imal sime4ne sim4ply 2s1in -3sin. 3sina sina4m 4si4nant 4sind 3sine. 4sing s3ing. sin4gan s4ingh sin3gle s3ingly -s4ingst s4ingt s4ini sin3if sin4in sini4s sin4it s4inn si4nou 4sins sin4th 4sinth. -sinus5 3sio2 4siod sio3p 4sioru 3sip si4pr s4i2r sir3ou 3s2is sis4a 4sish -sis4p sist3en 4sisth 2s2it si3ta si4tab 3sitio si5to sito4rie sit4s 3situ 4situs -s3ity s4iu 3siv siv3i si2z s2k 2s4k. s4kala ske4 4skee skel3e sk4et -s4kil ski4r s4kl sk4v sky3 sl2 s4l. sla2 slam3 slan4t s4laug 3s2lav -slav3er s4le. slen3 4s3let 2sley sli2 3s4lip sli4v sl4l 2slo 2slu2 s3luc -2sm2 s4m. sma2 sma3g s3man sman4n smel4 s3mer s1mic smi4no s3mis smis4sa -3smith smo2 s4mo4k smold4 s4moo smo4t smoth3 smu2 4smui smut3ab s4my sn2 -4s4n. s4nal sna4p snatu4 s1ne sne4f snel4 s4ni4 snif2 snive4 snoo4 s4nou -1so soa4 so3b 4soc. 4socc 4socea 2so2ch so3ci soci3o 4soct 4socu sodro4 -3soe soer4 soev3 s2o2g sogam6e 3soh 4sois sol2 3sol. solal4 so3lat 6solativ -so4lato sold4 sol3di so2li so3lic sol5id. so3lil 4soliti s4olo s4o6lutiv so4luto 3solv -3so2m so3ma s4ome. s4omel s4omen som4i 3s2on so3nan son4ar sond4a soni4e 4sonm -sonni4 so2no soo4t so4ped soper4 s4opha so4phe 3sophic so6phism s3ophiz sop4i so4po -sor2 sor4b sor3c 4sorci sor3d 4soret 4sorg4 so1ri so6riate 4sorit s4orn sor4ta -3sory 3sos sosi4 4sosm 4so3t4er soter6is soth4i sot4s sout4 south3 4sova so2ve -sover3 so3vi 4sowen 2sox s2p 4s4p. spal4l spa4ni span5ic spar3en spato4 spavi4 -s4pb sp4e 3s4pea s4pec3u s4ped s4pee4 spel4l spen4g spens4 4speo spe4r 3s4perm -sper4min sper4mis sper4on s3perou s6persede sph4 s3pha s4phe s5phem sphen4 spher4o s3pho -s4pi. sp4ic spi4cy 4spid spi3de s4pie 4spiec 4spier spi4f s4pil2 spi4li s4pin -sp3ing spion4 s4pis s4pli s4ply s4pn s4pom s4pon spon2d 3spons spoo4 1spor -4spora. 3spori 4sporou spor4tie spro4c spu2 s4pum sput3ab s4py s4q. s3quer squin4 -squit4 2s1r s4r. s4ram sre4c sre2p s4ri. s4ro. sro4b sr4t 2ss s4s. -s4sab s4sais s3sal s3san ssap4 ssar4t s4sc ssect4ib s1sel s3semb ssem4ic s1ses -ssev3 s2s3ho ss2i ssi4a s2sibl ssico4 ss4id s2sie ss3ies ssil3i ss3ingl s3sini -ssir4 s3s4it ssi4us s4s3l ssman4 s4s1n s3so sso4le sso3na ssoni4 sso6riat ssor4t -sso4ru s4sov sspel4 s4spo4 s3su ss3w ssy4r5 s2t 4s4t. st2a2 s4tad staffi4 -4stag. stame4 s4tan. stand3 s4tani sta4p staph3 s4tas. s4tasia 3s4tat. 3s4tatic stat3u -4staur s4taxi st3c ste2 4ste. 3stead ste3at s4ted s4te4g s4teles stel4ic st3ence -sten3i s3tenin ste4no sten3ot ster4na. stern5iz s4terr 4stet. stew5a 2s4tf 2s1th s4tha -s3the s4thea s4them s4thex sth4i sth4l stho4l stholi4 s4thou s4thyo stia4 s4tib -s3tic s5tica 3stick 2s3tid s4tie stif4 s3tifi s3tify sti4g3i 5stigmi s4tih s3tima -3s4timu sti3na s4ting st3ing. s3tini s4tipu 3s4tir s3tis s4tisp 4stl s3tled s4tli -4s4tm stmen4t s4tn4 4sto. stoba4 4stoe s4tof s4togn stoi4a s4tok stol4e stom3ar -3s4tome. 4stomo s3tomy st4on 4ston. 3stone 4stonp sto4pi sto2r s5tor. sto3ral 4storat -s4torb storbi4 s4tord s4tore s4torg s4torm s4toss s4tota stot4in 2stow s4tp st6pra -st2r 4str. stra4l5ia strep3 s4tride s4trol. s4trop. 4stro5phe strot4 stroth4 3stroy 3struc -st1s4 stti4 s4tu2d s3tul s3tuo s4tup s4tur. stur2b 4sture 4s4tw 4stya 3s4tyl -styl4ic s4tyr 1su2 s4u. su3ab su3al su5ant su4b3 succi4na 3sud sudo4 suf3 -sug3ar su5ic su3in sui4t3 3s4ul sul4te 4sulto 3sum sumer4 sun4a sunam4 suni4t -4suno s4u3o su3p su4per. supra3 4surani su3rat sur4d 4sureth sur4gy sur3v s2us3 -3s4us. sus4a 4sush sus4i sus4y 4suter sut4s 4suy su3z 2s1v s4v. s4var -svel4 4s3vi svil4 s4viz s4vp s2w2 s4w. swam4p sw4e sweat3 s4wee swel4 -swell3i 3swer 4s5wic swiv3 2swo sy2b syb5a5 sy4bi 3s4yl syl3l syl4p 1sym -sym4i 3symm sy4mond 1sy2n3 4syn. syph3i syr2 sy4ro 3sy4s3 s2z 4sz. s4z4a -sz4i 4szk 4t's 2t. 1ta 3t4a. 4taa 2tab t4abas ta4ber tabi4a tab3li -5tab5olis tab3u 3taceo tachi4 tach3yp 4tacid ta5cis tac3it ta3cl 4tacol 4tacon t4ad -ta4dl 3tae1 4taeb tae3r2 6taerist taf4fer taf4fr ta2g tage2 t4agn tago3n4 t4ah -3tah. 3tai. 3tail tai5lor 4tainers 4tainf talem4 ta4len ta4lic 4talie ta4ling. ta3lini -taliv4 tal4k 4talkal tal6lan t2alli t4alloc t4allot talo4g t4alp 4taly. t4ameris tam4ie -ta4mis4 4tamm t2an t3anal tanan4 ta4nat 2tanc tan4ce tan2d tand3a ta4nes t4ang -tan4gui 4tani. 3tan5ic tani4e ta4nin tan4iz ta4nom ta4nou 4tansc 4tansi tan4tan. 2tap -ta4pa t4ape t4aph. ta4pi t4app t2a2r2 t4ar. ta4ran taran4ti 4tarct 3t4ar4d tar3g -targu4 3tari tar3ie tar4is. tar4ism tar4ius tark4i tar3n tar4te 4tarth tarvel4 3tary -4taryt 3t4as. tas4c 3tasm tassem4 tas4sie tas4t t2asta 3tastas 4tasth ta3sy ta5tar -t4ath ta4the t4ati 4tatica 3tatiz ta4toc ta4tol ta4tos 4tatrop tat4ta ta4tu 4tatun -4tauc tau4m tavan4 tave4s tawa4 taw5n4 tax3 taxim4 tazo4l 2tb2 4tb. t4bili -tb4l t3bu 2tc tcau4 t3ce 4tch1 tche4 tch3eri tchet4ti tch3i t4chl t4cho -tc4i tci4t t1cr 2t1d2 tdev4 4t4e. 3teac teach3 3te1an 4teat te3ato 3teau -3tebr 1tec te4can te3ce te3chi 3techn t3e3chu 4te4col 3tect 2t1ed tee4l tee3pl -t4eer. tefi4 teg2 te3gi 3tegic teg4n t3egor te4gri teg3um teher4 te2i tei4n -te4k t2el 3tel. 3teleg tel3eo 3telep teler4 4teless tel3ev teli4e tel4lab 3telle -4tello. tel4o 4tely. te2m t4ema 4temen 3tem3iz temp4 tempt3i 4tenac. 3tenan ten3ant -t3enat t4enci 3tend ten4d3a 3tene. 4tenes te4ni te5nia ten3in te4no. te4n4or 3tens4 -4tens. 5tensi ten4tag ten4tee 3tenti ten4tie t4enu ten3ur 4tenus 4teny 3te3o teob4 -4teocr teo4r1 teorbi4 tep4i te4pl 1ter ter5an 3terb 3terc terci4 t4e4re ter5ec -3tered ter4enti ter3est tere4v 3ter5f 4teri. 3te3ria 5terie. ter5ine t4ering 3te3rio ter4is. -ter3ist 3te3riu 4terj. 4terjum t2erl 3t4erm ter4mor 5ternity ter3o teroge6no ter4oma tero2s -ter4os. 3terp t4erpe 3terr 3ters ter3t terte4 3ter3v tervar4i 1t4es 3tesc te2si -tes3im tes4o 3tesq 4tess. test3ab tes5tam 4testan tes3tat 4testr tes5tu 4tesv te4th -3teti 4teto tet1r 3teu2 teuc4 teur3i teu3t te3ve tev4i tewom4 3tex. texam4 -texami4 2tf2 tfal4 4t3fu 2t1g2 4tg. 4t3ga 4t3ge2 tgen3 tgeni4a t2h 4th. -5thaemi tha4lat thal5m tha4lo tha3no thar3g 5thargic 5thart thatch4e 4thau thaz4 4thead -th3eas theat4e 3theci 4theg 3the1i 4theid 4thelb the3m 4the4mo 1then 3theni the4ol. -4thep 3thera 5there. 3therea 4thereg th3erf 3theria th3erin 3therm th3ern th3ery th4es -the4sh thes3p 3thet 4theta thet4e 4thetec th3e3ter 4theth 4theto 4thet4y 3theu 4thexa -th4ey 2thf 4thh 3thiaz 4thica 4thina thin2g 3think 3thi3o thion4 thiop4 3this -4thisc 3thiz 2th3l 4thm thn4 1tho 3thodic 4thof tho4la 4thole tholi4a thol4ic. -5tholica 5tholicis 5tholiciz 5thologis 5thology thon2 4t3hoo th3opo 4thore thor3o 4thorse tho2t -4thour 4thouse 4thov 1thr thral4l thre4 thren4 4throd. thro4n thron3i 4ths 1thu -4thum4 3thus thy4b th1yl3 3thyo 3thyro th2ys 1ti 3ti. 3t4i2a 4tiam. tia4r -ti3ari 2ti3b tib5ia ti4bit ti4biu 4t1ibl 2tic t4icac ti4cas tich3i 3ticia ti4cil -3ticit 4tick 3ticl 3tico 4ticoen 4tict 3tic3u 4ticuf 4ticut tid2 4tidae ti2dal -3tide. ti3ego 3tien tier4i ti1es 4ties. ti3f 3tif. 3ti4fe tif4f tight4y 3t4i2gi -tig3i5a 4tigm t4igr 3tigu ti4j 3tik tik4e 4tilb 3tilen 3til2i 4tilik til6ise. -tilit4 tili4tu til6ize. t4illary til4lit 4tillu t4iln ti3lob tim2 ti3ma 3timab tim3b -3time timet4a ti4mete 3timo timon4i tim3u 4timul 2tin 3t4in. 5ti3nad 3tine. 3tined -t4ingb ting4e tin4gi 4tingl 4tingn tin4gr 5tingu 3tinic t4init tin4j ti4nose 4tinsk -t4in4t 5tinus t4invi 3tio2 4tio3la 4tiolu 4tipas. ti4pater ti3pe ti6peda ti3ph ti4pho -ti3pl ti4pod ti4pot tipri4 tip4sa 4tipul t4iq 4tir. 3tire tiren4 ti4ret tir2i -5tir3ic tir3in 4tiris 4tiriz 3tis. tisa4m ti3san tis4c 4tisec tiser4 2t1isf tish4c -4tishm tisi4 tis4l tiso4p ti3s4p 4tispu tisso4 t2ist tistal4 3tisti 4tistici tis4tr -ti3su ti3sy t2it ti3ta tita4n 4titc tit4e t4itia 3tition tit4is ti3tl tit4li -ti4tol ti3tr 3t4iu 3tiv ti3v2a ti4v3is ti4voc t3i3zen t4jan tj4e tju4 t1k -tk4i4 4tky 2t1l2 4t4l. 4tla t4lal tlan2 tland3 tl4e 3t4le. 3tled. 4tlee -3tlem 4tless 4tley 4tlie 4tlik tlil4 2t1m 4tma 4tm4e 4tmo tmos5p 2t1n -4tn' 4tn4a tna4si 4tne tnton4 1t2o 3to. toas4 toat4 3to3b toba4l to4be. -3toca 3tocr to3crat 3tode 4todu toe4a to4ed 4toest tof4i 3tog 4togly 4togne -4togo 4toh. 4tohm 3toid toi4di toile4 2tok tok4l 4toko t5okou 4to3ky tol3em -tolici4 tolo4n 3tom. 4tomal to4mall tomar3 to4matis to4matiz to4matop 3tome 4tomen tome4r -2tomi 3t4o4mic 5tomic. 5tomica t3omist tomoto4 4tomou tom4s tomu4 2tomy ton2 t4on. -5tona4l to5nalit toneast4 tone4ga to5net 3tonic ton4io 6tonish. 6tonishm tonni4 tonsi4 to2ny -tooe4 4toor to3pa 4topau 4tope. 3toph 4tophel 4topoe to5py 4topy. 3tor 5to5ratio -torbi4t 4tordi to4rea 4torer 4torga to1ri tori4de tori4en tor3iz 4torm. torpo4 torth4 -to3ry 3tos tos4e to3sp 4tosph tos4s tostel4 3tot 4tota. to3tem to3the to2to -totro4 totu4 4toua 4tound. tour4a tovar4 4tovis 4towah 3tow4n 5town. tows4 3tox3 -tox5i 2t1p2 tpeli4 tpelli4 tph4 tpi4 tport4 4tpr t6pran tpre4 1tr2 4t4r. -4tra' 3tra. tracas4 tra3ch t4racid 4tradd tra2ga 4tragalu t4rago 4tragu 3tral t4rame -4tramen tra3mi t4ranc t4rand 3trans 4trans. trans3a t4rapt t4rar 4trarch. trar6chis tras4 -tra5ven 4travi4o4 t4razz t2r4e 3tre. tre4ad 4treal treci4 t4reg tre4i t4re2m tre4ma -2treo tre2p 4treph trep3o3n 5tresc t4ri. 3t2ri3a 4triate. 3t2rib 4trib. trib3a tri4be -3t4rice t5richa 3tricia t4rid. 3t2rie 4trij t4rill trilli4 4trime 2trimo t4rin trinar4 -trion4a t4rios tri4ph tri4pu t2ris trisylla6 t2rit trith4 tri4tia trit3ur t4riu 3trix -triza4b trob4 tro3e trof4i t4rogeny trogo4 t6rograp tro4i tron4e tron4g tron3i 4trony -4troom t4roph 4trophy. 3tropic 4tropis tropos4 4t3ropou 2t3ropy t4ror 4tros. tro3sp 3trot -troth4e trous4e t4rox 4truc tru4de tru4e 4trug tru3i tru5si t4ruv 3try. 2ts2 -4t4s. t4sa. tsan4 4ts3b ts4c t4schl ts4e tsel4 4tsf t1sh t4shor ts4i -t3sky ts3m tsot4 t1s4p 4t1s4t4 tsta4 t4sug4 t4suk t4sus tsut4 ts3v 4tsy -t4sy. t4syn4 2t1t2 t4t' 4t4t. 4t3ta ttak4 ttar4iu t4tb t3ted t4teme ttend3 -tte4r t4tes. t4teso t4tg 4t3ti tti5a tti2d tti4la t5tingl t4tins t4tite tt4j -t4tk t3t4l t4tm4 t4tn tto2n ttori4 ttra4m ttrib3 t4try t4ts ttu4o ttu4p -ttur3 1tu tu1a tuag4 t6ualiser t6ualizer tub2 tu3b4e tu4bel tu3bu 4tuc. tuca4 -tu4des tu2di tud3ie tu3di3n tud3y 2tue t3u3en t4uf4 3tu3i3 tuil4 tu4is tuit4 -2tu1l 4tuln 3tum. 4tumb 3tumi tump3 tun4 tund4 tu4ne tu3nic tun5in tunk4 -tuo2 4tup. tu6rabl tura4m tu6rativ 3turb tur4ber 3ture 4turet4 tur4f 5turgic 3turi -tu3rie tur3is tur3iz tur3o turre4 4turt turti4 3t4us tuss4 tu4ta tu4val tuy4e -2t1v 2tw2 4twa t4wain twee4n 3twine twir4 twis4 twist5 4two twor6thil 4twy -txi4 1ty2 4tya 4tye tyers4 4tyf ty3i t3y3lat tyle4u t3ymo ty4mol. 4tyoc -3typ typo4t 4typu t4ys 4tysc 4tyu 4tyw 2t2z tz4e tzg4 tz4il 4t4zl -tzo4 tzs4 2u. ua2 ua3b 4uac uad1 uadi4 uaf4 uag4i uain2 uaint3 -u1al ual4a ua3ma ua4mas ua4mu ua3na uanan4 u4ane u4a3p uar3an 4uar4d u4are -uarri4an uar3te u1at uat4edn ua4tr 4uau uay4a u2b1 ub2a ubb4 ub5bl ub3c4 -ub3d ubde2 u4be. u4bele u3b4er u5beri ubi4as u3bic u4bin4g 4u4bis u3b4it ubi5ta -ubl4 ub3li ub3m ubor3 ubor4a ub3p u3bri u4bro 4ubs4 u4b2u ub4ul u5bulat -ub4y 2uc u3ca 4ucah ucar4 3ucat ucate4 ucay4 uc3c uccum4 4uce u4cep -u3chu4 uc1ib u3cif ucig4 uci4p u3c2it u4c4k3 4uc4l ucle3 u3co uco3p u1cr -uc4rac uc6ratize uct4a uc4te u1cu ucuro4 ucy4 u4cy. uda3i u4dam u3dan u4dax -ud3d4 u3den u4dene u4dep ud3er. ud4er4a uder4s udev4 udg3eo udge4t u1di u2dian -ud2ib udic4e 4udiciab u2die ud3ied u3dim udi3ni u3dino u3dio u2dis4 ud3ish ud4it -udi4u u3do3 udop4 u4dru u2du ud3ule u2dy 4udz ue4a ueb4 uebe4 u4ed -ue4gia uehon4 ue2li ue4lia u4elo uel4p uen4ce u4ene u4eni u4eno4 uere4 ueri4l -uer2r uer4y u4es2 ues4th ues3tr uet2 ue4tr 2u2f2 4uf. u3fa 4ufa. 3ufac -4uff uff3ab uff3ing uf3fl uf5fle uf4i u3f4l u4flo uf4r u3f4u 2ug u3gal -4ugao ugar4i u3gat u4gh ug4ha ugh4es ugh3i ught2 ugh3te 4ugi ugi4e ugli4a -ugmen4 ug4ne 4u3go u4gout ugre4 u3gu u4gul ugz4 u3ha uhe3 uh4s ui2 -4ui. ui4a. ui4ab ui3al ui3a4n ui4at uiber4 ui4bu u2ic ui4de uid3i 4uidit -ui3e ui4er uie4ti uig2 u2il ui3la uil4d u4ile uil3iz uil4lo. uil2t3 ui3nar -uine4a uin4ty uin4u4 ui3o 4uip ui3po 4uiq 4uir uirel4 uir4ite uirre4 uis2 -u4is. ui4sat uiset4 uis3in u5issa uis4tr ui4tab ui4tr uitz4 u4iv ujer4 u4jt -2uk u1ka 4uke ukem4 u3kh 4ul. u3l4a ula4ca. u4lai 6ularis. 4ulb ul4bl -ul3c 4ulca 4ulchra ul4e 4u3lea 4u5lei uleit4 ulei4u u3len 4uleo u4les u3let -4uleu u4ley 4ulf ulfa4t ul2ger ul2gi ulg3in ul4gy u1li 4uli4a ulian4i u3lic -ul4ie4 uli4m u4lina ul3ing ulin3i ul5ish u3l4it ul4iu 4ull ul4labi ul4larb ul4lard -ul4ler. ull4i ullian4 ul4lite ul4loc ullo4d ul4low uln4 u3l4o 4ulok ulo4mi ulon4 -uloni4 4ulp ulpha4 ul4pie ulpi4ta ul4py 4uls 4ult ult5ant ulti3l u1l4u 4ulu. -ulu4n ul3v ulw4 4uly4 u5ly. u4m. um3ab u5mac u4mag u2mam uma5ro uma4s -u3mat uma6top 4umba um4bie um2bin u1m2e 3umell u3men umen4s u4mer. u6mer6ic. u1mi -u2mic umid3 u3mig u2ming umi3ni umin4is u4mism. um4nis u3mo umoni4 umo4nit umon4o -umon4t u5mor5o um2p umpe4 um4phy ump3i ump3y u4ms4 umt4 u3mu1 umu4le u2my -un1 4una. un5abl un4alas una4mo un4ani un4ary unat4ic. una4tr u4naut unb4 un5bl -2unc uncer4a unde6mon und2i un5dit un4dro. un4dry. un3e une2a uneb4 unek4 u4nep -un4er un5ero uner4r u4nes une4so un4et unf4 unfi4bro unflo4 unful4l ung4a unga4m -un4ger. un4gia ung4l un5gu un4ia4 un4ic u3nic. u3n4if uniflo4 unig3 uni4j uni2m -u4nin un5ing u2n3io u3nip un4iq u4n3ish un4ism u1n4it uni4us u3ni1v u4nj un3l -un3m unmatu4 4unn4 unne4c unob3l unof3 unon4e uno4pr un3p4 u4n3r4 unrav3 unre4co -unremis6 unrep4u un3s4 unsca4 unscru6ta unsepa4 unta4l 3untar untar3y untel4l un3ten un4ter. -un4terj unti4t unti4u un3tr unu4c unuch3 un3v unvict4 unvo2 uny2 unzi4 u1o -4uo. uob4 uodio4 uolan4 u4om4 u2on4 uop4e uopi4 u2or u3o3re uor3idi uo2s -uo2t u3ou u4ov 2u1p u4p. u4pab upat4 u3p4e u4ped uper3 u4peralt upercivi6 -uperlu6 up4ha upha4n uphe3m uphi4 u4p3ing upi4p upl4 u3ple u4plet u4pp uppe4t -uppor4 upre4 up1s u4pt upt3ib u3p4u uqui4 4ur. 4u3ra. u3rac 4urad ura4da -4urae ur3agi 4urale u4ram. ura4ma u4r3anc ura5no ur4ant u3rar2 u3r4as u4rasi u1r4at -u4rb urbabili6 ur4ber. ur2bin urb3ing ur2c4 urd4a urdis4 ur4dish urd4w u1rea 3urea. -u5reo ur3er ures4 u1ret ure4tic ur4fer ur4f3i ur4fr u4rg urg3in u3ria uri4ac -uri4at. u1r4ic uric3u u3rid 4ur2ie u3rien u2ring u3rio uri4ol ur2it uri3ta uriti4u -ur4lies. urmoun4 urmu4r u4rn 4urni urni4e urn4is ur4nov 4uro u3r4o. uroce4 u3roi -u4rol. uro3p uro2t urovi4 urox4 urpo4 urpre4 urr4 4urren urre4t ur4rit ur2s -ursu5a urten4 ur4tho ur4ty u1ru ur4vie ury4d ury4lo u2s u4s. u5sad u3s4an -us4ap us4as us3c us4ch us4cr use3a use4o usep4 us3es u4set us4h1 4ush2a -4usi. us4ia usi3an u3sic us4ie us5ies usil4i4 u3sill u4sin us4it us1n us4o -u3sor 4us3p us4pa usper4 us4po us4ri 2uss2 ussel4 us4seli uss4i ust3ab us4th -ustha4 us4tia us4tivel us4tiven us4tly ustmen4 us3to2 ustom3a us4top us3tr us3tu us4ty -u3s4u usur4e usur2p usyn4 2ut 4u4t. ut2a uta4h u3tal uta4ma u3t4an u4tance -utan4i 3utar3y u3tatio utb4 u4tc4 utca4 utci4 u4te4co u3te3i u3tell ut4em ut4en -ute4or ute4r u2t3er. uter5in uter4me uter4op u2tes ut3g uth4ic uthor3 u4thr utiabili6 -u3tic u3til2 u4tily ut4im u3tina uti6nate. u3tini u3tino 4utio u4tish utj4 utk4 -u4t3l4 uto4mata u3ton utre4m ut4rop ut1s4 ut4sy ut3t uttel4 ut4tit utto2 u3tu -u4tup utura4 ut1w utyr5 u1v u4valu u4ved uvel4 uve3n u4verneu u3vi uvii4 -u4vis u3vu uw4e u2x 4ux. ux3i uxili4 uxle4 uxm4 uxt4 uxu1r uya4n -uyk4 uz2e uz4is uz4ze 2v. 1va 3va. 2v1ab v4aba v4abh 3v4ac va3ca -vac3il vac3u 2vag va4gen vager4 v4agg v4a4gi 5vagina vaha4 3vai vail3a v2al -val4a va1le val4er va3leri valer4y va5lesc vali2 val3ia va3lie val4ise 3vall val4len -valli4 val3or valor3i val3u 4valu. vam4ph vampi4 va4na van4ce van4ch 4vanco 2vane -van1g 4vang. vange4 van4i vani4e van5it va3niz va3no van4u va4pi vapor3i va3ra -vard4 vardi4e va3riet var6iola var4is varle4 vars4 4varts va4ru 2vasa v5asat 5vasc -va4se vas3ec vass4i 2vast v3as3ta vastat6i 4vatar 3vate 4vatina 3vatio vats4 4vatt. -v4ce 2vd 4v4e. 2vea 3veal ve4b ve2bl 2ved v4ed. 4veda v3edl veg1e3 -vehe3 ve2i vei4o 2vel v3eled v3eler ve2li v4elin. v3el3is veli4u v4ella. vel3ler -3velo vel4od vel3op 4veloy vel4t vel3v v4ely4 2vem ve5nan veng3i ven3ie veno4 -ve2n3o2m vens4e 3vent ven4tag ven6tat 4venth ven4tis ven4ua 4vep v4era. ver4an. 4verati -verc4 5vere. ver3ea ver3ei ver3em 2veren 4vereu ve2ri ver4ia 4verie veri3ta ver3m4 -verma4n vermi6no 4verneur 1vers v4ers. 3verte vert3er 3verti ver4tit ver3u 6verwear. v2es -4ves. ve4sac 3vesc ve2si ves3p ves3ti vet3e vet5ou 4veve vey3o 4vh. 3v4i. -v4ian vi4ano 4viar vi4ary vias4 vi4ata vi3b vi4be vi4bi vi4bri vi2car vi4ce -4viced 4vicen 3vich. 5vict victi2 vic2t3u 3v4icu vi2d 4v3iden 3vid3i 4vid4i4v v4ie. -vi4en vie4r vi4erw 3viet viet5i vi4eto vie4w vi3f vi4fe 4v3iga v4ign v4ig1o -vigor3 vi4gou vii4u 3vik vik4i v4ile. vil2i v2ili2a vil3if 3ville 4villed 2vin -v2ina 3vina. vi4nato 3vinci 3vine. vi4net vin4ic vini4f vi4nom v4i3ny4 vi1o vio3l -vio4lis v3i2or5 viot4 vi4pa vi3pe 3viru v2is vis5ag 4visat 3visc v3isec 3visi -vi4sis vis3i4t 3viso vis1u3 vi3ta v4ital vita3m v3itat vitato4 v4ite vitori4 vi2t1r -vi4tra vit4ro vit4ry vit4u vi2v viv3al 4vivis v4iza v4ized 4vizz 2vk 4vki -vli4 2vn 1vo vo4cato voet4 3vog 3voi2 voi4c void3 4voird vo4j 3vok -vo2la vol3at 3volt 4vol4to vol3un 3volv vom3i von4a 4vonar 4vonp vont4 v4oq -vo2r 3vor4c vo4ri 5vorot v3orou 4vos vo2t 4vot. 3vote 4votee 3voti 3vour -vour3i 3vout vo3vi 3vow vowe4 4vowi 3voy 4vp. vra4 v4reu vro4n 2vs -v3ski v3sky 2vt 4vt. 4vty 1vu 6vulgati 3vur 2vv v4va vv4i v3vy -vy4e v4zi 2w. 1wa 4w1ab w4abs wa2g wagi4 3wak wa4kan wak3en wal4k -wallad4 wal4ley wam4pl wa4ne 4wane. wa4ny waq4 war2 war4d warde4 war4er warmon6 -warri4 war4t was4 wash3 wasp3 was5s4 waster4 w4at 3wa3te wat4i wat4ta wat4th -wau4f wavel4 3way. wc4t wd4i wdo4 wdus4 we2 wear2 wea3r4ie wear4ine wea3ry -wea4t weath3 we4b w2ed weep3 4weet3 weev4 weil4 weir4 wel3i wel2l wel4ta -we4ly 4w1er w3eri wern4 w4err wer4s wes4e west1 western5 w4et 4wett. w4ey -wf2 wg4r wgra4 1wh2 4wh. wha4 whan4 whar4 whel2 whi4 who4 why4 -w2i2 w4i. w4ia w4i4c w4i4d wid3o w4iec wield3 wien4 wi3er wi3est wi4f -3wife wi4g wign4 will3in wil4lo. wilm4 4wim wimp4 w4in. win2d win4dr win2g -win3i w4inn w4ins wir4l 1w4ise wis4p5 w4i4t with3 4witt. w4kn w4l. wl3ed -wl4ey wl3ing wl4ings w1m wm4e w2n1 wn4de w4n3er w3nes wnfal4 wn3i wn4ia -wn4ingt wnpa4 1wo2 wok4 wol2 wolf4f wol3l 3wom3 wo4ma won4e won4g won4t -woo4 3wood woozi4er wor4c wor2d wor4sen 3wort wo3ven wows4 wp2 1wr2 wr4e -3wri2 w2s ws4ab ws4er w3s4k wsma4 w3so ws4pe ws4t 2wt wtu4 wuh4 -wum4 wur2 wur4l wut4 wuz4 4ww. 2wy2 wyc5l wydi4 wyom4 w2z x1 -2x. xac4 x4aca xac5er x3ae xa2g xal2 xamer6a xa4m3i xami4nab xa4na x4andro -xap4 xar4 x4ario xas3p xas4ti xato4 x4au x4c. x3ce xchan4 x3ci xci4t3a -xcom4b xcu4 xcub4 2xe2 x4e. x3ec xe3cr xec5utiv xec3uto x4ed x5edl x5edn -xe3g xel4 xe3mi xer2 xe4ra xer4gu xe3ro x3es xhi2 xhil3 xhila3 xhu2 -2xi x3ia xi3b xibi4 xica4l x3id2 xi3di xi4dic xi4din x3ie xi3f x3ig -xig4r x3il xili4a xiliar4 xi4mis x3in x5ine xi4no x3io xi4p x3is xist5ent. -xis4ti x3iv xiv3i x4ix x3le xle4i xm4e xn4t x3o. xochro4 xo4di xo2g -xogen4 xon4ar xop4 xo5r2a xospo4 xo2t x4p. xpan4d x3pe xpec2 xpect6ed. xpecto3 -xpe3d2 xpen4c xper2 xpres4sa x4pt xpun4 x4r. xs2 xt2 x4t. x3ti xt4r -xtra5ve xtre4me xtro3v xu1a xu3b xu4l2 xulian4 xulli4 x5ult xuper4 4xus xy1 -xyb4 xyl3at x4yle xy4s x3y3t y4'l y4'r y2a. y1ab y4abe ya4co yac4t -yaen4 y4ageu 4y4ai ya4j y1al y4ala ya5lin y2am 5yam4a y1an 3yang ya3nid -y4ank yan4ode 1y2ar y4ard 4yaro ya4s yaw4p y4az yb4a yba5r 4ybd y4bele -y4bia 4y4biu yb4o ybrid3 y1c y5cem 3ycep yc3er3 y4ceri4 yce4te ych2 ychasi4 -ychas4ti ych3n y5cho ychor4 y3ci y4cida y4cif y4cinal y4cliu yclos4 yc3n4 ycog4 -yco3p yco3s ycosta4 ycot4t 4ycra ycran4 y4crat yctag4 y1d y4d' y4dad y4dali -y4dam yd4di y2de y3den y3der 4y2d5ia y4dic ydi4o ydi4v yd4l 4y4domo 4y3dr -y4driat ydroc4 ydro3s ydro3t 4y4e. ye4a yeas4 yeat4 y4ed ye3le ye3lo yel4p -ye4m yen2 ye3n4i ye4o y1e4r y4era. yer4e y2erf y4erl yers4e yes3te yet2 -ye4u4 yeus4 3yev y4fed yfire4 4yga y2gap yge2 ygen3 2y2gi yg3i3a y3gie -4ygo ygro3s 4ygy y1h yhisto6 yh4v yh4w y1i y3i4a y4ie yk4e yk4j -ykn4 y2l y4l. y5lae yl3am y3lar4 y3lax 4yld y4le. y3ledo2 4ylem ylen4 -y4lest yl1ic 4ylid4 yl3ism y3lit 4yll yl3la yllar4 yl3li yllo4m yllo4n 4ylna -y3lo y4loch yl4on yl2p yl4ph ylphe4 ylphi4 4ylsu 4yl3t y3lum y3lus yl3v -y3ly 2ym 4ym. 4yma ymbo4 ymbol3 ym4c y3men 4ymer 4y2mi 5y5miz y3mo -ymo4b 4ymol. y4monds ympa3 ymphan3 ym4phe ymp4i ymple4 2yn y1na y4nae yna3mit -y4namy y4n4ar ynar4chi y4nast. yn3c ynde4 y2ne yn4ep yn4et yn1g yng4a 5yngec -6yngologi y2ni ynia4 yn2n yno4e ynos4 3ynou yn1t yntro4 3yny. 4ynya yo3de -yog4i y3oi 5yoke yoky4 yom4i yon2 y3one4 y1op yopi4t 2yp y2pa y4paes -yp4e yper3 yperpu4 y4pes ypeta4 y4peu 4yphag yphe2 yph3il y3pho 4yphon ypho4t -4yp3i yp4ico 3yplo ypo3a ypo3c y4pocr y3pogr ypo4lite ypo3n ypos5tas ypos4to y4poth. -y4pou y4pre. y4pri yp3s yp2ta ypt3an yp3y y3ra. y3rag y2re y5rene. y4rhi -y4ria yriam4 y4r1ic yr4if 5yrint yr4le y4rm yr4ma yrme3 y4rn y1ro y4rod -yro3st yrro4 y2s ysal2 ysca4 yscrin4 ys3e3al ys4el ys4es y3sh ys2i y4siac. -ys3i3al y4sias ys3ic y3sid ys3io 3y5sis y5sit 4ysk ysme4 ys4o ysom4 yss4 -ys4sal ysse4 ys3t ystem3 ystem4l ysthe4u ys4tl ys4toi ys4tone ysto4p ystri6 y3su -y4sur ys4us 3y3sy 2y2t y4t. ytan4 yte4l ytel4l 3y3ten yte4r y4teu y3them -y4th3i ytho4gr yth3op yth3ro yt1i y4til y4tio ytish4 y4tium y3to 4ytom y4tomo -y4tomy ytos4t y3tr ytt4 y3tu yub4 y4uc yu4go. yu4le y1v y1w yw4c -ywon4 2y2x 4yx3e yx3i yxi4a. yxi4d 4yxo 4yy4a 2yz y2za y4zan y4zygy -2z. 1za1 3za. 2z1ab za4ch 4zaf za4g 3zal za4la 4zald zale4 3zan -4zank za4nu za4q 2zar2 z4ar. za2rat zar4ia za4ry zas4 za4st 3zat 4zath -za4u 2zb 2ze. zeal3 ze4bra. ze4da zeg4 3zel. zel4le ze4n zen4e 4zener -zenti4 ze4p z1er zer4a zer4o z4ert zevi4 zg4e 4zgi zh4d z1i 3zi. -3zia. zi4at zib4 zi4be4 4zic. 3z4ie. zi4ery zi1es z4if zi4k 4zilin z4ilo -4z4im 2zin2 3zin. z4in4c zinc3i zind4 zin4e 3zine. z3ing. 4ziot zir4ca zi4r4i -4zirit 4zky z5ky. 1z2l2 4zla 3zle 4zlo 1zo zo1a 4zoam zocer4 zo1i -zoker4 zo4lam zolan4 4zolin 4zolo 3zo2n zonar4 zo4ne zon4if zo1o zoo4i zook4 -2zop 3zor zori4l zos4e 2zot zo3ti zo4tic z3ra zs4c z4so4 zuid4 zu4o -4zwi 3zyg zy2ga zym4 z1z2 z3za z4zed 4zzi z3zl z3zo zzu4 z4zw diff --git a/assets/hyphenation/it.pat b/assets/hyphenation/it.pat deleted file mode 100644 index 552bb5d26..000000000 --- a/assets/hyphenation/it.pat +++ /dev/null @@ -1,431 +0,0 @@ -% title: Hyphenation patterns for Italian -% copyright: Copyright (C) 2008-2011 Claudio Beccari -% notice: This file is part of the hyph-utf8 package. -% See http://www.hyphenation.org/tex for more information. -% language: -% name: Italian -% tag: it -% version: 4.9 2014/04/22 -% authors: -% - -% name: Claudio Beccari -% contact: claudio.beccari (at) gmail.com -% licence: -% - This file is available under any of the following licences: -% - -% name: LPPL -% version: 1.3 -% or_later: true -% url: http://www.latex-project.org/lppl.txt -% status: maintained -% maintainer: Claudio Beccari, e-mail claudio dot beccari at gmail dot com -% - -% name: MIT -% url: https://opensource.org/licenses/MIT -% text: > -% Permission is hereby granted, free of charge, to any person -% obtaining a copy of this software and associated documentation -% files (the "Software"), to deal in the Software without -% restriction, including without limitation the rights to use, -% copy, modify, merge, publish, distribute, sublicense, and/or sell -% copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following -% conditions: -% -% The above copyright notice and this permission notice shall be -% included in all copies or substantial portions of the Software. -% -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -% OTHER DEALINGS IN THE SOFTWARE. -% hyphenmins: -% typesetting: -% left: 2 -% right: 2 -% changes: -% - 2014-04-22 - Add few patterns involving `h' -% - 2011-08-16 - Change the licence from GNU LGPL into LPPL v1.3. -% - 2010-05-24 - Fix for Italian patterns for proper hyphenation of -ich and Ljubljana. -% - 2008-06-09 - Import of original ithyph.tex into hyph-utf8 package. -% - 2008-03-08 - (last change in ithyph.tex) -% texlive: -% encoding: ascii -% babelname: italian -% legacy_patterns: ithyph.tex -% message: Italian hyphenation patterns -% description: |- -% Hyphenation patterns for Italian in ASCII encoding. -% Compliant with the Recommendation UNI 6461 on hyphenation -% issued by the Italian Standards Institution -% (Ente Nazionale di Unificazione UNI). -% ========================================== -% -% These hyphenation patterns for the Italian language are supposed to comply -% with the Recommendation UNI 6461 on hyphenation issued by the Italian -% Standards Institution (Ente Nazionale di Unificazione UNI). No guarantee -% or declaration of fitness to any particular purpose is given and any -% liability is disclaimed. -% -\patterns{ -.a3p2n % After the Garzanti dictionary: a-pnea, a-pnoi-co,... -.anti1 -.anti3m2n -.bio1 -.ca4p3s -.circu2m1 -.contro1 -.di2s3cine -.e2x1eu -.fran2k3 -.free3 -.li3p2sa -.narco1 -.opto1 -.orto3p2 -.para1 -.ph2l -.ph2r -.poli3p2 -.pre1 -.p2s -.re1i2scr -.sha2re3 -.tran2s3c -.tran2s3d -.tran2s3l -.tran2s3n -.tran2s3p -.tran2s3r -.tran2s3t -.su2b3lu -.su2b3r -.wa2g3n -.wel2t1 -2'2 -a1ia -a1ie -a1io -a1iu -a1uo -a1ya -2at. -e1iu -e2w -o1ia -o1ie -o1io -o1iu -1b -2bb -2bc -2bd -2bf -2bm -2bn -2bp -2bs -2bt -2bv -b2l -b2r -2b. -2b' -1c -2cb -2cc -2cd -2cf -2ck -2cm -2cn -2cq -2cs -2ct -2cz -2chh -c2h -2ch. -2ch'. -2ch''. -2chb -ch2r -2chn -c2l -c2r -2c. -2c' -.c2 -1d -2db -2dd -2dg -2dl -2dm -2dn -2dp -d2r -2ds -2dt -2dv -2dw -2d. -2d' -.d2 -1f -2fb -2fg -2ff -2fn -f2l -f2r -2fs -2ft -2f. -2f' -1g -2gb -2gd -2gf -2gg -g2h -g2l -2gm -g2n -2gp -g2r -2gs -2gt -2gv -2gw -2gz -2gh2t -2g. -2g' -.h2 -1h -2hb -2hd -2hh -hi3p2n -h2l -2hm -2hn -2hr -2hv -2h. -2h' -.j2 -1j -2j. -2j' -.k2 -1k -2kg -2kf -k2h -2kk -k2l -2km -k2r -2ks -2kt -2k. -2k' -1l -2lb -2lc -2ld -2l3f2 -2lg -l2h -l2j -2lk -2ll -2lm -2ln -2lp -2lq -2lr -2ls -2lt -2lv -2lw -2lz -2l. -2l'. -2l'' -1m -2mb -2mc -2mf -2ml -2mm -2mn -2mp -2mq -2mr -2ms -2mt -2mv -2mw -2m. -2m' -1n -2nb -2nc -2nd -2nf -2ng -2nk -2nl -2nm -2nn -2np -2nq -2nr -2ns -n2s3fer -2nt -2nv -2nz -n2g3n -2nheit -2n. -2n' -1p -2pd -p2h -p2l -2pn -3p2ne -2pp -p2r -2ps -3p2sic -2pt -2pz -2p. -2p' -1q -2qq -2q. -2q' -1r -2rb -2rc -2rd -2rf -r2h -2rg -2rk -2rl -2rm -2rn -2rp -2rq -2rr -2rs -2rt -r2t2s3 -2rv -2rx -2rw -2rz -2r. -2r' -1s2 -2shm -2sh. -2sh' -2s3s -s4s3m -2s3p2n -2stb -2stc -2std -2stf -2stg -2stm -2stn -2stp -2sts -2stt -2stv -2sz -4s. -4s'. -4s'' -.t2 -1t -2tb -2tc -2td -2tf -2tg -t2h -2th. -t2l -2tm -2tn -2tp -t2r -t2s -3t2sch -2tt -t2t3s -2tv -2tw -t2z -2tzk -tz2s -2t. -2t'. -2t'' -1v -2vc -v2l -v2r -2vv -2v. -2v'. -2v'' -1w -w2h -wa2r -2w1y -2w. -2w' -1x -2xb -2xc -2xf -2xh -2xm -2xp -2xt -2xw -2x. -2x' -y1ou -y1i -1z -2zb -2zd -2zl -2zn -2zp -2zt -2zs -2zv -2zz -2z. -2z'. -2z'' -.z2 -} % Pattern end - diff --git a/assets/hyphenation/pl.pat b/assets/hyphenation/pl.pat deleted file mode 100644 index 1a3c6c1d3..000000000 --- a/assets/hyphenation/pl.pat +++ /dev/null @@ -1,4154 +0,0 @@ -% title: Hyphenation patterns for Polish -% copyright: Copyright (C) 1987-1995 Hanna Kołodziejska, Bogusław Jackowski, Marek Ryćko -% notice: This file is part of the hyph-utf8 package. -% See http://www.hyphenation.org/tex for more information. -% language: -% name: Polish -% tag: pl -% version: 3.0a, Wednesday, May 17th, 1995 -% authors: -% - -% name: Hanna Kołodziejska -% - -% name: Bogusław Jackowski -% contact: b_jackowski (at) gust.org.pl -% - -% name: Marek Ryćko -% contact: marek (at) do.com.pl -% licence: -% - This file is made available under any of the following licences: -% - -% name: MIT -% url: https://opensource.org/licenses/MIT -% text: > -% Permission is hereby granted, free of charge, to any person -% obtaining a copy of this software and associated documentation -% files (the “Software”), to deal in the Software without -% restriction, including without limitation the rights to use, -% copy, modify, merge, publish, distribute, sublicense, and/or sell -% copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following -% conditions: -% -% The above copyright notice and this permission notice shall be -% included in all copies or substantial portions of the Software. -% -% THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -% OTHER DEALINGS IN THE SOFTWARE. -% - text: > -% This macro file belongs to the public domain -% under the conditions specified by the author of TeX: -% -% “Macro files like PLAIN.TEX should not be changed in any way, -% except with respect to preloaded fonts, -% unless the changes are authorized by the authors of the macros.” -% -% Donald E. Knuth -% hyphenmins: -% typesetting: -% left: 2 -% right: 2 -% changes: -% - The first version of the patterns was developed -% by Hanna Kołodziejska (1987). -% - The adaptation to the LeX format (see below) and extensive modification -% were done by Bogusław Jackowski & Marek Ryćko (1987--1989). -% - The hyphenation rules were further improved and adapted to the -% TeX 3.x requirements by Hanna Kołodziejska (1991). -% - Lone-standing version (3.0a) of patterns was prepared (under pressure -% from LaTeX users) by Bogusław Jackowski and Marek Ryćko, following -% Mariusz Olko's suggestions, 1995. -% - The LeX format mentioned above was the first version of the adaptation -% of TeX to the Polish language. The next version is called MeX. -% texlive: -% encoding: qx -% babelname: polish -% legacy_patterns: plhyph.tex -% message: Polish hyphenation patterns -% description: |- -% Hyphenation patterns for Polish in QX and UTF-8 encodings. -% These patterns are also used by Polish TeX formats MeX and LaMeX. -% -\patterns{ -.ćć8 -.ćł8 -.ćń8 -.ćś8 -.ćź8 -.ćż8 -.ć8 -.ćb8 -.ćc8 -.ćd8 -.ćf8 -.ćg8 -.ćh8 -.ćj8 -.ćk8 -.ćl8 -.ćm8 -.ćn8 -.ćp8 -.ćr8 -.ćs8 -.ćt8 -.ćv8 -.ćw8 -.ćwier2ć3 -.ćx8 -.ćz8 -.łć8 -.łł8 -.łń8 -.łś8 -.łź8 -.łż8 -.ł8 -.łb8 -.łc8 -.łd8 -.łf8 -.łg8 -.łh8 -.łj8 -.łk8 -.łl8 -.łm8 -.łn8 -.łp8 -.łr8 -.łs8 -.łt8 -.łv8 -.łw8 -.łx8 -.łz8 -.ńć8 -.ńł8 -.ńń8 -.ńś8 -.ńź8 -.ńż8 -.ń8 -.ńb8 -.ńc8 -.ńd8 -.ńf8 -.ńg8 -.ńh8 -.ńj8 -.ńk8 -.ńl8 -.ńm8 -.ńn8 -.ńp8 -.ńr8 -.ńs8 -.ńt8 -.ńv8 -.ńw8 -.ńx8 -.ńz8 -.ść8 -.śł8 -.śń8 -.śś8 -.śź8 -.śż8 -.ś8 -.śb8 -.śc8 -.śd8 -.śf8 -.śg8 -.śh8 -.śj8 -.śk8 -.śl8 -.śm8 -.śn8 -.śp8 -.śró2d5 -.śródr2 -.śr8 -.śs8 -.śt8 -.śv8 -.św8 -.światło3w2 -.śx8 -.śz8 -.źć8 -.źł8 -.źń8 -.źś8 -.źź8 -.źż8 -.ź8 -.źb8 -.źc8 -.źdź8 -.źd8 -.źf8 -.źg8 -.źh8 -.źj8 -.źk8 -.źl8 -.źm8 -.źn8 -.źp8 -.źr8 -.źs8 -.źt8 -.źv8 -.źw8 -.źx8 -.źz8 -.żć8 -.żł8 -.żń8 -.żś8 -.żź8 -.żż8 -.ż8 -.żb8 -.żc8 -.żd8 -.żf8 -.żg8 -.żh8 -.żj8 -.żk8 -.żl8 -.żm8 -.żn8 -.żp8 -.żr8 -.żs8 -.żt8 -.żv8 -.żw8 -.żx8 -.żz8 -.a2b2s3t -.a2d3 -.ad4a -.ad4e -.ad4i -.ad4o -.ad4u -.ad4y -.ad5apt -.ad5iu -.ad5op -.ad5or -.ae3ro -.aeroa2 -.aeroe2 -.aeroi2 -.aeroo2 -.aerou2 -.antya2 -.antye2 -.antyi2 -.antyo2 -.antyu2 -.arcy3ł2 -.arcy3b2 -.arcy3bz2 -.arcy3k2 -.arcy3m2 -.arcya2 -.arcye2 -.arcyi2 -.arcyo2 -.arcyu2 -.au3g2 -.au3k2 -.au3t2 -.auto3ch2 -.autoa2 -.autoe2 -.autoi2 -.autoo2 -.autotran2s3 -.autou2 -.bć8 -.bł8 -.bń8 -.bś8 -.bź8 -.bż8 -.b8 -.bb8 -.bc8 -.bd8 -.be2z3 -.be3z4an -.be3z4ec -.be3z4ik -.bezch2 -.bezm2 -.bezo2 -.bezo2b1j -.bezw2 -.bezzw2 -.bf8 -.bg8 -.bh8 -.bj8 -.bk8 -.bl8 -.bm8 -.bn8 -.bp8 -.br8 -.brz8 -.bs8 -.bt8 -.bv8 -.bw8 -.bx8 -.bz8 -.cć8 -.cł8 -.cń8 -.cś8 -.cź8 -.cż8 -.c8 -.cało3ś2 -.cało3k2 -.cb8 -.cc8 -.cd8 -.cf8 -.cg8 -.ch8 -.chrz8 -.cienko3w2 -.ciepło3kr2 -.cj8 -.ck8 -.cl8 -.cm8 -.cn8 -.cp8 -.cr8 -.cs8 -.ct8 -.cv8 -.cw8 -.cx8 -.cz8 -.czarno3k2 -.czk8 -.cztere2ch3 -.czterechse2t3 -.cztero3ś2 -.czwó2r3 -.czwó3r4ą -.czwó3r4ę -.czwó3r4a -.czwó3r4e -.czwó3r4o -.dć8 -.dł8 -.długo3tr2 -.długo3w2 -.dń8 -.dś8 -.dź8 -.dż8 -.d8 -.daleko3w2 -.db8 -.dc8 -.dd8 -.de2z3 -.de3z4a3bil -.de3z4a3wu -.de3z4el -.de3z4er -.de3z4y -.deza2 -.dezo2 -.df8 -.dg8 -.dh8 -.dj8 -.dk8 -.dl8 -.dm8 -.dn8 -.do3ć2 -.do3ł2 -.do3ś2 -.do3ź2 -.do3ż2 -.do3b2 -.do3c2 -.do3d2 -.do3f2 -.do3g2 -.do3h2 -.do3k2 -.do3l2 -.do3m2 -.do3p2 -.do3r2 -.do3s2 -.do3t2 -.do3w2 -.do3z2 -.do4ł3k -.do4k3t -.do4l3n -.do4m3k -.do4r3s -.do4w3c -.do5m4k2n -.dobr2 -.dobrz2 -.doch2 -.docz2 -.dodź2 -.dodż2 -.dodz2 -.dogrz2 -.dopch2 -.doprz2 -.dorż2 -.dorz2 -.dosch2 -.dosm2 -.dosz2 -.dotk2 -.dotr2 -.dp8 -.dr8 -.drogo3w2 -.drz8 -.ds8 -.dt8 -.dv8 -.dwó2j3 -.dwó3j4ą -.dwó3j4ę -.dwó3j4a -.dwó3j4e -.dwó3j4o -.dw8 -.dx8 -.dy2s3 -.dy2z3 -.dy3s4e -.dy3s4o -.dy3s4ta -.dy3s4y -.dy3sz -.dy3z4e -.dyzu2 -.dz8 -.dziesięcio3ś2 -.dziewięćse2t3 -.dziewię2ć3 -.dziewięcio3ś2 -.e2k2s3 -.e2m3e2s5ze2t -.e2s1e2s1ma -.e2s1ha -.e2s1t -.egoa2 -.egoe2 -.egoi2 -.egoo2 -.egou2 -.eks4y -.elektroa2 -.elektroe2 -.elektroi2 -.elektroo2 -.elektrou2 -.fć8 -.fł8 -.fń8 -.fś8 -.fź8 -.fż8 -.f8 -.fb8 -.fc8 -.fd8 -.ff8 -.fg8 -.fh8 -.fj8 -.fk8 -.fl8 -.fm8 -.fn8 -.fp8 -.fr8 -.fs8 -.ft8 -.fv8 -.fw8 -.fx8 -.fz8 -.gć8 -.gł8 -.gń8 -.gś8 -.gź8 -.gż8 -.g8 -.gb8 -.gc8 -.gd8 -.ge2o3 -.gf8 -.gg8 -.gh8 -.gj8 -.gk8 -.gl8 -.gm8 -.gn8 -.go2u3 -.gp8 -.gr8 -.grubo3w2 -.grz8 -.gs8 -.gt8 -.gv8 -.gw8 -.gx8 -.gz8 -.hć8 -.hł8 -.hń8 -.hś8 -.hź8 -.hż8 -.h8 -.hb8 -.hc8 -.hd8 -.hf8 -.hg8 -.hh8 -.hipe2r3 -.hipe3r4o -.hipera2 -.hipere2 -.hj8 -.hk8 -.hl8 -.hm8 -.hn8 -.hp8 -.hr8 -.hs8 -.ht8 -.hv8 -.hw8 -.hx8 -.hz8 -.i2n3 -.i2s3l -.i3n4ic -.i3n4o -.i3n4u -.i4n5o2k -.in4f3lan -.ino3w2 -.izoa2 -.izoe2 -.izoi2 -.izoo2 -.izou2 -.jć8 -.jł8 -.jń8 -.jś8 -.jź8 -.jż8 -.j8 -.jadło3w2 -.jb8 -.jc8 -.jd8 -.jf8 -.jg8 -.jh8 -.jj8 -.jk8 -.jl8 -.jm8 -.jn8 -.jp8 -.jr8 -.js8 -.jt8 -.jv8 -.jw8 -.jx8 -.jz8 -.kć8 -.kł8 -.kń8 -.kś8 -.kź8 -.kż8 -.k8 -.kb8 -.kc8 -.kd8 -.kf8 -.kg8 -.kh8 -.kilkuse2t3 -.kilkuseto2 -.kj8 -.kk8 -.kl8 -.km8 -.kn8 -.koło3w2 -.kon2t2r3 -.kon3tr4a -.kon3tr4e -.kon3tr4o3l -.kon3tr4o3w -.kon3tr4y -.kon4tr5a2gi -.kon4tr5a2se -.kon4tr5a2sy -.kon4tr5a2ta -.kon4tr5adm -.kon4tr5akc -.kon4tr5alt -.kon4tr5arg -.kontro2 -.kontru2 -.kp8 -.krótko3tr2 -.krótko3w2 -.kr8 -.kro2ć3 -.krz8 -.ks8 -.kt8 -.kv8 -.kw8 -.kx8 -.kz8 -.lć8 -.lł8 -.lń8 -.lś8 -.lź8 -.lż8 -.l8 -.lb8 -.lc8 -.ld8 -.lf8 -.lg8 -.lh8 -.lj8 -.lk8 -.ll8 -.lm8 -.ln8 -.lp8 -.lr8 -.ls8 -.lt8 -.ludo3w2 -.lv8 -.lw8 -.lx8 -.lz8 -.mć8 -.mł8 -.mń8 -.mś8 -.mź8 -.mż8 -.m8 -.mb8 -.mc8 -.md8 -.mf8 -.mg8 -.mh8 -.mili3amp -.mj8 -.mk8 -.ml8 -.mm8 -.mn8 -.możno3w2 -.mp8 -.mr8 -.ms8 -.mt8 -.mv8 -.mw8 -.mx8 -.mz8 -.nć8 -.nł8 -.nń8 -.nś8 -.nź8 -.nż8 -.n8 -.na2d2 -.na2j -.na3ć2 -.na3ł2 -.na3ś2 -.na3ź2 -.na3ż2 -.na3b2 -.na3c2 -.na3dą -.na3dę -.na3dź2 -.na3d4łub -.na3d4ir -.na3d4much -.na3d4ręcz -.na3d4r2w -.na3d4repcz -.na3d4rept -.na3d4ruk -.na3d4rz -.na3d4worn -.na3daj -.na3de -.na3do -.na3dy -.na3dzi -.na3f2 -.na3g2 -.na3h2 -.na3ją -.na3ję -.na3jazd -.na3je -.na3k2 -.na3l2 -.na3m2 -.na3p2 -.na3r2 -.na3s2 -.na3t2 -.na3u2 -.na3w2 -.na3z2 -.na4d3o2b2ł -.na4d3o2bojcz -.na4d3o2bowi -.na4d3o2brot -.na4d3o2drz -.na4d3o2kien -.na4d3olbrz -.na4d5rzą -.na4d5rzę -.na4d5rzecz -.na4d5rzy -.na4d5ziem -.na4f3c -.na4f3t -.na4j3e2f -.na4j3e2g -.na4j3e2k2s -.na4j3e2ko -.na4j3e2n -.na4j3e2r -.na4j3e2s -.na4j3e2w -.na4j3emf -.na4j3eu -.na4r3c -.na4r3d -.na4r3k -.na4r3r -.na4r3t -.nabrz2 -.nach2 -.nacz2 -.nadśrod5ziem -.nad3ć2 -.nad3ł2 -.nad3ś2 -.nad3b2 -.nad3c2 -.nad3d2 -.nad3e2tat -.nad3f2 -.nad3g2 -.nad3h2 -.nad3i2 -.nad3j2 -.nad3k2 -.nad3l2 -.nad3m2 -.nad3n2 -.nad3p2 -.nad3r2 -.nad3s2 -.nad3t2 -.nad3u2 -.nad3w2 -.nad5ż2 -.nad5zó -.nad5z2mys -.nad5zo -.nad5zwycz -.nadch2 -.nadcz2 -.naddź2 -.nade3ć2 -.nade3ł2 -.nade3ś2 -.nade3ź2 -.nade3ż2 -.nade3b2 -.nade3c2 -.nade3d2 -.nade3f2 -.nade3g2 -.nade3h2 -.nade3k2 -.nade3l2 -.nade3m2 -.nade3p2 -.nade3r2 -.nade3s2 -.nade3t2 -.nade3w2 -.nade3z2 -.nade4p3c -.nade4p3n -.nade4p3t -.nadech2 -.nadecz2 -.nadedź2 -.nadedż2 -.nadedz2 -.naderż2 -.naderz2 -.nadesz2 -.nadsz2 -.nadtr2 -.nadz2 -.nagrz2 -.naj3ć2 -.naj3ł2 -.naj3ś2 -.naj3ź2 -.naj3ż2 -.naj3akt -.naj3au -.naj3b2 -.naj3c2 -.naj3d2 -.naj3f2 -.naj3g2 -.naj3h2 -.naj3i2 -.naj3k2 -.naj3l2 -.naj3m2 -.naj3o2 -.naj3o2ć2 -.naj3o2ł2 -.naj3o2ś2 -.naj3o2ź2 -.naj3o2ż2 -.naj3o2b2 -.naj3o2c2 -.naj3o2d2 -.naj3o2f2 -.naj3o2g2 -.naj3o2h2 -.naj3o2k2 -.naj3o2l2 -.naj3o2m2 -.naj3o2p2 -.naj3o2r2 -.naj3o2s2 -.naj3o2t2 -.naj3o2w2 -.naj3o2z2 -.naj3p2 -.naj3r2 -.naj3ro2z3 -.naj3s2 -.naj3t2 -.naj3u2 -.naj3w2 -.naj3z2 -.najbe2z3 -.najbezw2 -.najch2 -.najcz2 -.najdź2 -.najdż2 -.najdo3ć2 -.najdo3ł2 -.najdo3ś2 -.najdo3ź2 -.najdo3ż2 -.najdo3b2 -.najdo3c2 -.najdo3d2 -.najdo3f2 -.najdo3g2 -.najdo3h2 -.najdo3k2 -.najdo3l2 -.najdo3m2 -.najdo3p2 -.najdo3r2 -.najdo3s2 -.najdo3t2 -.najdo3w2 -.najdo3z2 -.najdoch2 -.najdocz2 -.najdodź2 -.najdodż2 -.najdodz2 -.najdorz2 -.najdosz2 -.najdotk2 -.najdz2 -.najkr2 -.najob3ć2 -.najob3ł2 -.najob3ś2 -.najob3ź2 -.najob3ż2 -.najob3c2 -.najob3d2 -.najob3f2 -.najob3g2 -.najob3h2 -.najob3j2 -.najob3k2 -.najob3l2 -.najob3m2 -.najob3n2 -.najob3p2 -.najob3s2 -.najob3t2 -.najob3w2 -.najobch2 -.najobcz2 -.najobdź2 -.najobdż2 -.najobdz2 -.najobrz2 -.najobsz2 -.najoch2 -.najocz2 -.najodź2 -.najod3ć2 -.najod3ś2 -.najod3c2 -.najod3d2 -.najod3f2 -.najod3g2 -.najod3h2 -.najod3j2 -.najod3k2 -.najod3l2 -.najod3m2 -.najod3n2 -.najod3p2 -.najod3s2 -.najod3t2 -.najod3w2 -.najod5ż2 -.najodch2 -.najodcz2 -.najoddź2 -.najoddż2 -.najoddz2 -.najodsz2 -.najodz2 -.najorz2 -.najosz2 -.najro3z4u -.najrz2 -.najsm2 -.najsz2 -.najtk2 -.najtr2 -.najucz2 -.najzw2 -.nakr2 -.napo2d2 -.napo3ć2 -.napo3ł2 -.napo3ś2 -.napo3ź2 -.napo3ż2 -.napo3b2 -.napo3c2 -.napo3f2 -.napo3g2 -.napo3h2 -.napo3k2 -.napo3l2 -.napo3m2 -.napo3p2 -.napo3r2 -.napo3s2 -.napo3t2 -.napo3w2 -.napo3z2 -.napo4m3p -.napoch2 -.napocz2 -.napodź2 -.napodż2 -.napod3d -.napomk2 -.naporz2 -.naposz2 -.naprz2 -.narż2 -.naro2z3 -.narz2 -.nasm2 -.nasz2 -.natch2 -.natk2 -.naz3m2 -.nazw2 -.nb8 -.nc8 -.nd8 -.ne2o3 -.nf8 -.ng8 -.nh8 -.nie3ć2 -.nie3ł2 -.nie3ś2 -.nie3ź2 -.nie3ż2 -.nie3b2 -.nie3c2 -.nie3d2 -.nie3f2 -.nie3g2 -.nie3h2 -.nie3k2 -.nie3l2 -.nie3m2 -.nie3p2 -.nie3r2 -.nie3s2 -.nie3t2 -.nie3u2 -.nie3w2 -.nie3z2 -.nie4c3c -.nie4c3k -.nie4dź3 -.nie4m3c -.nie4m3k -.niech2 -.niecz2 -.niedż2 -.niedo3ć2 -.niedo3ł2 -.niedo3ś2 -.niedo3ź2 -.niedo3ż2 -.niedo3b2 -.niedo3c2 -.niedo3d2 -.niedo3f2 -.niedo3g2 -.niedo3h2 -.niedo3k2 -.niedo3l2 -.niedo3m2 -.niedo3p2 -.niedo3r2 -.niedo3s2 -.niedo3t2 -.niedo3w2 -.niedo3z2 -.niedobrz2 -.niedoch2 -.niedocz2 -.niedodź2 -.niedodż2 -.niedodz2 -.niedokr2 -.niedomk2 -.niedopch2 -.niedorz2 -.niedosz2 -.niedotk2 -.niedz2 -.nieoć2 -.nieoł2 -.nieoś2 -.nieoź2 -.nieoż2 -.nieo2 -.nieob2 -.nieob3ć2 -.nieob3ś2 -.nieob3ź2 -.nieob3ż2 -.nieob3c2 -.nieob3d2 -.nieob3f2 -.nieob3g2 -.nieob3h2 -.nieob3j2 -.nieob3k2 -.nieob3m2 -.nieob3p2 -.nieob3s2 -.nieob3w2 -.nieobch2 -.nieobcz2 -.nieobdź2 -.nieobdż2 -.nieobdz2 -.nieobsz2 -.nieoc2 -.nieoch2 -.nieocz2 -.nieodź2 -.nieod2 -.nieod3ć2 -.nieod3ł2 -.nieod3ś2 -.nieod3c2 -.nieod3d2 -.nieod3f2 -.nieod3g2 -.nieod3h2 -.nieod3j2 -.nieod3k2 -.nieod3l2 -.nieod3n2 -.nieod3p2 -.nieod3s2 -.nieod3t2 -.nieod3wr -.nieod5ż2 -.nieodch2 -.nieodcz2 -.nieoddź2 -.nieoddż2 -.nieoddz2 -.nieodsz2 -.nieodw2 -.nieodz2 -.nieof2 -.nieog2 -.nieoh2 -.nieok2 -.nieol2 -.nieom2 -.nieop2 -.nieor2 -.nieorz2 -.nieos2 -.nieosz2 -.nieot2 -.nieow2 -.nieoz2 -.niepo2d2 -.niepo3ć2 -.niepo3ł2 -.niepo3ś2 -.niepo3ź2 -.niepo3ż2 -.niepo3b2 -.niepo3c2 -.niepo3dź2 -.niepo3d4łu -.niepo3d4much -.niepo3d4ręcz -.niepo3d4raż -.niepo3d4rap -.niepo3d4repcz -.niepo3d4rept -.niepo3d4waj -.niepo3d4woj -.niepo3do -.niepo3du -.niepo3dz2 -.niepo3f2 -.niepo3g2 -.niepo3h2 -.niepo3k2 -.niepo3l2 -.niepo3m2 -.niepo3p2 -.niepo3r2 -.niepo3s2 -.niepo3t2 -.niepo3w2 -.niepo3z2 -.niepo4d3o2choc -.niepo4d3o2strz -.niepoch2 -.niepocz2 -.niepod3ć2 -.niepod3ł2 -.niepod3ś2 -.niepod3b2 -.niepod3c2 -.niepod3d2 -.niepod3f2 -.niepod3g2 -.niepod3h2 -.niepod3j2 -.niepod3k2 -.niepod3l2 -.niepod3m2 -.niepod3n2 -.niepod3p2 -.niepod3r2 -.niepod3s2 -.niepod3t2 -.niepod3w2 -.niepod5ż -.niepodch2 -.niepodcz2 -.niepoddź2 -.niepoddż2 -.niepodsm2 -.niepodsz2 -.nieporz2 -.nieposm2 -.nieposz2 -.nieprzełk2 -.nieprze2d2 -.nieprze3ć2 -.nieprze3ł2 -.nieprze3ś2 -.nieprze3ź2 -.nieprze3ż2 -.nieprze3b2 -.nieprze3brz2 -.nieprze3c2 -.nieprze3dź2 -.nieprze3d4łuż -.nieprze3d4much -.nieprze3d4ramat -.nieprze3d4ruk -.nieprze3d4ryl -.nieprze3d4rz2 -.nieprze3d4um -.nieprze3dy -.nieprze3dz2 -.nieprze3e2k2s3 -.nieprze3f2 -.nieprze3g2 -.nieprze3h2 -.nieprze3k2 -.nieprze3l2 -.nieprze3m2 -.nieprze3n2 -.nieprze3p2 -.nieprze3r2 -.nieprze3s2 -.nieprze3t2 -.nieprze3w2 -.nieprze3z2 -.nieprze4d5łużyc -.nieprze4d5ż2 -.nieprze4d5z2a -.nieprze4d5zg2 -.nieprze4d5zim -.nieprze4d5zj -.nieprze4d5zl -.nieprze4d5zw2r -.nieprze4d5zwoj -.nieprzech2 -.nieprzecz2 -.nieprzed3ć2 -.nieprzed3ł2 -.nieprzed3ś2 -.nieprzed3c2 -.nieprzed3d2 -.nieprzed3f2 -.nieprzed3g2 -.nieprzed3h2 -.nieprzed3i2 -.nieprzed3j2 -.nieprzed3k2 -.nieprzed3l2 -.nieprzed3m2 -.nieprzed3n2 -.nieprzed3p2 -.nieprzed3r2 -.nieprzed3s2 -.nieprzed3sz2 -.nieprzed3t2 -.nieprzed3u2 -.nieprzed3w2 -.nieprzedch2 -.nieprzedcz2 -.nieprzeddź2 -.nieprzeddż2 -.nieprzeddz2 -.nieprzegrz2 -.nieprzekl2 -.nieprzekr2 -.nieprzepch2 -.nieprzerż2 -.nieprzerz2 -.nieprzesch2 -.nieprzesm2 -.nieprzesz2 -.nieprzetk2 -.nieprzetr2 -.niero2z3 -.niero3z4e -.niero3z4u -.nierozś2 -.nierozbrz2 -.nieroze3r2 -.nierozm2 -.nieroztr2 -.nierz2 -.niesu2b3 -.niesu3b4ie -.niesz2 -.nietk2 -.nietr2 -.nieucz2 -.nieuw2 -.niewy3ć2 -.niewy3ł2 -.niewy3ś2 -.niewy3ź2 -.niewy3ż2 -.niewy3b2 -.niewy3c2 -.niewy3d2 -.niewy3f2 -.niewy3g2 -.niewy3h2 -.niewy3k2 -.niewy3l2 -.niewy3m2 -.niewy3p2 -.niewy3r2 -.niewy3s2 -.niewy3t2 -.niewy3w2 -.niewy3z2 -.niewybrz2 -.niewych2 -.niewycz2 -.niewydź2 -.niewydż2 -.niewydz2 -.niewyrz2 -.niewysz2 -.niewytk2 -.niewytr2 -.niezw2 -.nj8 -.nk8 -.nl8 -.nm8 -.nn8 -.np8 -.nr8 -.ns8 -.nt8 -.nv8 -.nw8 -.nx8 -.nz8 -.oć2 -.oś2 -.ośmio3ś2 -.oź2 -.oż2 -.o2b2 -.o2d2 -.o2t3chł -.o3b4łą -.o3b4łę -.o3b4łoc -.o3b4luzg -.o3b4rać -.o3b4raso -.o3b4roń -.o3b4ron -.o3b4ryź -.o3b4ryz -.o3b4rz2 -.o3be -.o3bi -.o3d4iu -.o3d4ręt -.o3d4rap -.o3d4robin -.o3d4rut -.o3d4rwi -.o3d4rzeć -.o3d4rzw -.o3d6zia -.o3d6zie -.o3de -.o3l2śn -.o4b5łocz -.o4b5rzą -.o4b5rzęd -.o4b5rzez -.o4b5rzuc -.o4b5rzut -.o4b5rzyn -.o4d7ziar -.o4d7ziem -.oa3z -.ob3ć2 -.ob3ł2 -.ob3ś2 -.ob3ź2 -.ob3ż2 -.ob3c2 -.ob3d2 -.ob3f2 -.ob3g2 -.ob3h2 -.ob3j2 -.ob3k2 -.ob3l2 -.ob3m2 -.ob3n2 -.ob3o2strz -.ob3p2 -.ob3r -.ob3s2 -.ob3t2 -.ob3u2m2 -.ob3w2 -.obch2 -.obcz2 -.obdź2 -.obdż2 -.obdz2 -.obe3ć2 -.obe3ł2 -.obe3ś2 -.obe3ź2 -.obe3ż2 -.obe3b2 -.obe3c2 -.obe3d2 -.obe3f2 -.obe3g2 -.obe3h2 -.obe3k2 -.obe3l2 -.obe3m2 -.obe3p2 -.obe3r2 -.obe3r3t -.obe3s2 -.obe3t2 -.obe3w2 -.obe3z2 -.obe4c3n -.obe4z3w -.obech2 -.obecz2 -.obedź2 -.obedż2 -.obedz2 -.oberż2 -.ober3m -.oberz2 -.obesch2 -.obesz2 -.obetk2 -.obi3b2 -.obsz2 -.oc2 -.och2 -.ochrz2 -.ocz2 -.odź2 -.od3ć2 -.od3ś2 -.od3au -.od3b2 -.od3c2 -.od3d2 -.od3f2 -.od3g2 -.od3h2 -.od3i2 -.od3i2zo -.od3j2 -.od3k2 -.od3l2 -.od3m2 -.od3n2 -.od3o2s -.od3p2 -.od3r2 -.od3s2 -.od3t2 -.od3u2cz -.od3u2m2 -.od3w2 -.od5ż2 -.od5z2 -.odbe2z3 -.odch2 -.odcz2 -.oddź2 -.oddż2 -.oddz2 -.ode3ć2 -.ode3ł2 -.ode3ś2 -.ode3ź2 -.ode3ż2 -.ode3b2 -.ode3c2 -.ode3d2 -.ode3f2 -.ode3g2 -.ode3h2 -.ode3k2 -.ode3l2 -.ode3m2 -.ode3mk2 -.ode3p2 -.ode3r2 -.ode3s2 -.ode3t2 -.ode3w2 -.ode3z2 -.odech2 -.odecz2 -.odedź2 -.odedż2 -.odedz2 -.odepch2 -.oderż2 -.oderz2 -.odesz2 -.odetch2 -.odetk2 -.odkrz2 -.odrz2 -.odsz2 -.of2 -.ogólno3k2 -.og2 -.ognio3tr2 -.oh2 -.ok2 -.oka3m2 -.okr2 -.ole2o3 -.om2 -.op2 -.opch2 -.or2ż2 -.or2tę -.or2z2 -.os2 -.osie2m3 -.osiemse2t3 -.osz2 -.ot2 -.ow2 -.oz2 -.pć8 -.pł8 -.płasko3w2 -.pń8 -.półk2 -.półkr2 -.półm2 -.póło2 -.półob3r -.półom2d -.półprzy3m2k -.pó2ł3 -.pó3ł4ą -.pó3ł4ę -.pó3ł4ecz -.pó3ł4y -.pś8 -.pź8 -.pż8 -.p8 -.pb8 -.pc8 -.pch8 -.pd8 -.pełno3kr2 -.pe2r3 -.pe3c2k -.pe3r4e -.pe3r4i -.pe3r4o -.pe3r4u -.pe3r4y -.pe4r5i2n -.pee2se2l -.pepee2r -.pepee2s -.peze2t1pee2r -.pf8 -.pg8 -.ph8 -.pięćse2t3 -.pię2ć3 -.pięcio3ś2 -.pierwo3w2 -.piono3w2 -.pj8 -.pk8 -.pl8 -.pm8 -.pn8 -.połk2 -.po2d2 -.po3ć2 -.po3ł2 -.po3ś2 -.po3ź2 -.po3ż2 -.po3b2 -.po3c2 -.po3dą -.po3dę -.po3dź2 -.po3d4łu -.po3d4much -.po3d4naw -.po3d4ręcz -.po3d4rętw -.po3d4róż -.po3d4r2wi -.po3d4raż -.po3d4rap -.po3d4repcz -.po3d4rept -.po3d4roż -.po3d4robó -.po3d4roba -.po3d4robo -.po3d4roby -.po3d4rocz -.po3d4ruzg -.po3d4ryg -.po3d4rze -.po3d4wójn -.po3d4wór -.po3d4waj -.po3d4woi -.po3d4woj -.po3d4worz -.po3da -.po3de -.po3dej -.po3diu -.po3do -.po3du -.po3dy -.po3dz2 -.po3e2k2s3 -.po3f2 -.po3g2 -.po3h2 -.po3k2 -.po3l2 -.po3m2 -.po3p2 -.po3rż -.po3r2 -.po3s2 -.po3t2 -.po3w2 -.po3z2 -.po4ń3c -.po4cz3d -.po4cz3t -.po4d3ów -.po4d3e4k2s3 -.po4d3o2bóz -.po4d3o2biad -.po4d3o2bojcz -.po4d3o2braz -.po4d3o2choc -.po4d3o2dm -.po4d3o2f -.po4d3o2g -.po4d3o2kien -.po4d3o2kn -.po4d3o2kręg -.po4d3o2kres -.po4d3o2piecz -.po4d3o2ryw -.po4d3o2siniak -.po4d3o2strz -.po4d3obsz -.po4d3odd -.po4d3olbrz -.po4d3u2cz -.po4d3u2dz -.po4d3u2pa -.po4d3u2ral -.po4d3u2sta -.po4d3u2szcz -.po4d5ręczn -.po4d5zakr -.po4d5zam -.po4d5zast -.po4d5zbi -.po4d5ze -.po4d5zielenią -.po4d5zielenić -.po4d5zielenię -.po4d5zielenił -.po4d5zielenic -.po4d5zielenien -.po4d5zielenil -.po4d5zielenim -.po4d5zielenio -.po4d5zielenis -.po4d5ziem -.po4d5ziom -.po4d5zw2r -.po4l3s -.po4m3p -.po4r3c -.po4r3f -.po4r3n -.po4r3t -.po4st3d -.po4st3f -.po4st3g -.po4st3h -.po4st3i2 -.po4st3k -.po4st3l -.po4st3m -.po4st3p -.po4st3rom -.po4st3s -.po5d4uszczyn -.po5r4tę -.pobr2 -.pobrz2 -.poch2 -.pochrz2 -.pocz2 -.pod3ć2 -.pod3ł2 -.pod3ś2 -.pod3śró2d5 -.pod3alp -.pod3b2 -.pod3c2 -.pod3d2 -.pod3f2 -.pod3g2 -.pod3h2 -.pod3i2n -.pod3j2 -.pod3k2 -.pod3l2 -.pod3m2 -.pod3n2 -.pod3p2 -.pod3r2 -.pod3s2 -.pod3t2 -.pod3w2 -.pod5ż2 -.podch2 -.podcz2 -.poddź2 -.poddż2 -.pode3ć2 -.pode3ł2 -.pode3ś2 -.pode3ź2 -.pode3ż2 -.pode3b2 -.pode3c2 -.pode3d2 -.pode3f2 -.pode3g2 -.pode3h2 -.pode3k2 -.pode3l2 -.pode3m2 -.pode3p2 -.pode3r2 -.pode3s2 -.pode3t2 -.pode3tk2 -.pode3w2 -.pode3z2 -.podech2 -.podecz2 -.podedź2 -.podedż2 -.podedz2 -.podepch2 -.poderż2 -.poderz2 -.podesch2 -.podesz2 -.podro2z3 -.podsm2 -.podsz2 -.pogrz2 -.pokl2 -.pokr2 -.pom4pk -.pomk2 -.pona2d2 -.pona3ć2 -.pona3ł2 -.pona3ś2 -.pona3ź2 -.pona3ż2 -.pona3b2 -.pona3c2 -.pona3cz2 -.pona3dź2 -.pona3do -.pona3f2 -.pona3g2 -.pona3h2 -.pona3k2 -.pona3l2 -.pona3m2 -.pona3p2 -.pona3r2 -.pona3s2 -.pona3t2 -.pona3w2 -.pona3z2 -.pona4f3t -.ponabrz2 -.ponach2 -.ponad3ć2 -.ponad3ś2 -.ponad3c2 -.ponad3ch2 -.ponad3cz2 -.ponad3dź2 -.ponad3f2 -.ponad3g2 -.ponad3h2 -.ponad3j2 -.ponad3k2 -.ponad3l2 -.ponad3p2 -.ponad3s2 -.ponad3t2 -.ponadz2 -.ponarz2 -.ponasm2 -.ponasz2 -.ponaz3m2 -.ponazw2 -.ponie3k2 -.ponie3w2 -.popch2 -.popo3w2 -.poprz2 -.por4t1w -.por4tf -.por4tm -.poro2z3 -.poro3z4u -.porz2 -.posch2 -.posm2 -.posz2 -.potk2 -.potr2 -.poz4m2 -.poza3u2 -.pozw2 -.pp8 -.pr8 -.pra3s2 -.pra3w2nu -.pra3w2z -.prapra3w2nu -.predy2s3po -.prz8 -.przełk2 -.prze2d2 -.prze3ć2 -.prze3ł2 -.prze3ś2 -.prze3ź2 -.prze3ż2 -.prze3b2 -.prze3c2 -.prze3dą -.prze3dę -.prze3dź2 -.prze3d4łuż -.prze3d4much -.prze3d4o3br -.prze3d4o3st -.prze3d4o3zo -.prze3d4ramat -.prze3d4ruk -.prze3d4ryl -.prze3d4rz2 -.prze3d4um -.prze3dy -.prze3dz2 -.prze3e2k2s3 -.prze3f2 -.prze3g2 -.prze3h2 -.prze3k2 -.prze3l2 -.prze3m2 -.prze3n2 -.prze3p2 -.prze3r2 -.prze3s2 -.prze3t2 -.prze3u2 -.prze3w2 -.prze3z2 -.prze4d5łużyc -.prze4d5ż2 -.prze4d5o4stat -.prze4d5za -.prze4d5zg2 -.prze4d5zim -.prze4d5zj -.prze4d5zl -.prze4d5zw2r -.prze4d5zwoj -.przebr2 -.przebrz2 -.przech2 -.przechrz2 -.przeci2w3 -.przeci3w4ie -.przeciwa2 -.przeciww2 -.przecz2 -.przed3ć2 -.przed3ł2 -.przed3ś2 -.przed3a2gon -.przed3a2kc -.przed3alp -.przed3b2 -.przed3c2 -.przed3d2 -.przed3e2gz -.przed3e2mer -.przed3f2 -.przed3g2 -.przed3h2 -.przed3i2 -.przed3j2 -.przed3k2 -.przed3l2 -.przed3m2 -.przed3n2 -.przed3o2 -.przed3p2 -.przed3r2 -.przed3s2 -.przed3się3w2 -.przed3sz2 -.przed3t2 -.przed3u2 -.przed3w2 -.przedch2 -.przedcz2 -.przeddź2 -.przeddż2 -.przeddz2 -.przedgrz2 -.przedy2s3ku -.przegrz2 -.przekl2 -.przekr2 -.przemk2 -.przepch2 -.przerż2 -.przerz2 -.przesch2 -.przesm2 -.przesz2 -.przetk2 -.przetr2 -.przetran2s3 -.przy3ć2 -.przy3ł2 -.przy3ś2 -.przy3ź2 -.przy3ż2 -.przy3b2 -.przy3c2 -.przy3d2 -.przy3f2 -.przy3g2 -.przy3h2 -.przy3k2 -.przy3l2 -.przy3m2 -.przy3p2 -.przy3r2 -.przy3s2 -.przy3t2 -.przy3w2 -.przy3z2 -.przybr2 -.przych2 -.przycz2 -.przydź2 -.przydż2 -.przydz2 -.przygrz2 -.przymk2 -.przyoz2 -.przypch2 -.przyrż2 -.przyrz2 -.przysch2 -.przysz2 -.przytk2 -.ps8 -.pt8 -.pv8 -.pw8 -.px8 -.pz8 -.rć8 -.rł8 -.rń8 -.rś8 -.rź8 -.rż8 -.r8 -.rb8 -.rc8 -.rd8 -.retran2s3 -.rf8 -.rg8 -.rh8 -.rj8 -.rk8 -.rl8 -.rm8 -.rn8 -.ro2z3 -.ro3z4a -.ro3z4e -.ro3z4e3ć2 -.ro3z4e3ł2 -.ro3z4e3ś2 -.ro3z4e3ź2 -.ro3z4e3ż2 -.ro3z4e3b2 -.ro3z4e3c2 -.ro3z4e3d2 -.ro3z4e3f2 -.ro3z4e3g2 -.ro3z4e3h2 -.ro3z4e3k2 -.ro3z4e3l2 -.ro3z4e3m2 -.ro3z4e3p2 -.ro3z4e3r2 -.ro3z4e3s2 -.ro3z4e3t2 -.ro3z4e3w2 -.ro3z4e3z2 -.ro3z4ej -.ro3z4u -.ro4z5a2gi -.ro4z5a2nie -.ro4z5e2mo -.ro4z5e4g3z -.ro4z5e4n3t -.rozś2 -.rozbrz2 -.rozd2 -.rozech2 -.rozecz2 -.rozedź2 -.rozedż2 -.rozedz2 -.rozepch2 -.rozerż2 -.rozerz2 -.rozesch2 -.rozesz2 -.rozi2 -.rozm2 -.rozo2 -.rozpo3w2 -.rozt2 -.roztr2 -.rozw2 -.rp8 -.rr8 -.rs8 -.rt8 -.rv8 -.rw8 -.rx8 -.rz8 -.sć8 -.sł8 -.sń8 -.sś8 -.sź8 -.sż8 -.s8 -.samo3ch2 -.samo3k2 -.samo3p2 -.samo3w2 -.samoro2z3 -.sb8 -.sc8 -.sch8 -.sd8 -.sf8 -.sg8 -.sh8 -.siede2m3 -.siedemse2t3 -.siedmio3ś2 -.sj8 -.ską2d5że -.sk8 -.skl8 -.skr8 -.sl8 -.sm8 -.sn8 -.sobo3w2 -.spó2ł3 -.sp8 -.spo2d2 -.spo3ć2 -.spo3ł2 -.spo3ś2 -.spo3ź2 -.spo3ż2 -.spo3b2 -.spo3c2 -.spo3dz2 -.spo3f2 -.spo3g2 -.spo3h2 -.spo3k2 -.spo3l2 -.spo3m2 -.spo3p2 -.spo3r2 -.spo3s2 -.spo3t2 -.spo3w2 -.spo3z2 -.spo4r3n -.spo4r3t -.spoch2 -.spocz2 -.spodź2 -.spodż2 -.spod3d -.sporz2 -.sposz2 -.sr8 -.ss8 -.st8 -.stere2o3 -.stereoa2 -.stereoe2 -.stereoi2 -.stereoo2 -.stereou2 -.su2b3 -.su3b4ie -.su3b4otn -.supe2r3 -.supe3r4at -.supe3r4io -.supe4r5a2tr -.super5z2b -.supere2 -.supero2d1rzut -.sv8 -.sw8 -.sx8 -.sz8 -.sześćse2t3 -.sześcio3ś2 -.sze2ś2ć3 -.sze2s3 -.tć8 -.tł8 -.tń8 -.tś8 -.tź8 -.tż8 -.t8 -.ta2o3 -.ta2r7zan -.tb8 -.tc8 -.tch8 -.td8 -.te2o3 -.tf8 -.tg8 -.th8 -.tj8 -.tk8 -.tl8 -.tm8 -.tn8 -.toa3 -.tp8 -.tró2j3 -.tró3j4ą -.tró3j4ę -.tró3j4ecz -.tr8 -.tran2s3 -.tran3s4e -.tran3s4ie -.tran3s4y -.tran3sz -.tran4s5eu -.transa2 -.transo2 -.trz8 -.trze2ch3 -.trzechse2t3 -.ts8 -.tt8 -.tv8 -.tw8 -.tx8 -.tysią2c3 -.tysią3c4a -.tysią3c4e -.tysią3cz -.tysią4c5zł -.tz8 -.uć2 -.uś2 -.u3ł2 -.u3ź2 -.u3ż2 -.u3b2 -.u3c2 -.u3d2 -.u3f2 -.u3g2 -.u3h2 -.u3k2 -.u3l2 -.u3m2 -.u3n2 -.u3p2 -.u3r2 -.u3s2 -.u3t2 -.u3w2 -.u3z2 -.u4d3k -.u4f3n -.u4k3lej -.u4l3s -.u4l3t -.u4m3br -.u4n3c -.u4n3d -.u4p3p2s -.u4r3s -.u4st3n -.u4stc -.u4stk -.u4z3be -.ube2z3 -.ubezw2 -.ubr2 -.uch2 -.ucz2 -.udź2 -.udż2 -.udz2 -.ukr2 -.umk2 -.upch2 -.upo2d2 -.upo3ć2 -.upo3ł2 -.upo3ś2 -.upo3ź2 -.upo3ż2 -.upo3b2 -.upo3c2 -.upo3da -.upo3f2 -.upo3g2 -.upo3h2 -.upo3k2 -.upo3l2 -.upo3m2 -.upo3p2 -.upo3r2 -.upo3s2 -.upo3t2 -.upo3w2 -.upo3z2 -.upoch2 -.upocz2 -.upodź2 -.upodż2 -.upod3d -.uporz2 -.uposz2 -.urż2 -.uro2z3 -.urz2 -.usch2 -.usz2 -.utk2 -.utr2 -.uze3w2 -.vć8 -.vł8 -.vń8 -.vś8 -.vź8 -.vż8 -.v8 -.vb8 -.vc8 -.vd8 -.vf8 -.vg8 -.vh8 -.vj8 -.vk8 -.vl8 -.vm8 -.vn8 -.vp8 -.vr8 -.vs8 -.vt8 -.vv8 -.vw8 -.vx8 -.vz8 -.wć8 -.wł8 -.wń8 -.wś8 -.wź8 -.wż8 -.w8 -.wb8 -.wc8 -.wd8 -.we3ć2 -.we3ł2 -.we3ś2 -.we3ż2 -.we3b2 -.we3c2 -.we3d2 -.we3f2 -.we3g2 -.we3h2 -.we3k2 -.we3l2 -.we3m2 -.we3n2 -.we3p2 -.we3r2 -.we3s2 -.we3t2 -.we3w2 -.we3z2 -.we4ł3n -.we4k3t -.we4l3w -.we4n3d -.we4n3t -.we4r3b -.we4r3d -.we4r3n -.we4r3s -.we4r3t -.we4s3prz -.we4s3tch2 -.we4z3br -.we4z3gł -.wech2 -.wecz2 -.wedź2 -.wedż2 -.wedz2 -.wemk2 -.wepch2 -.werz2 -.wesz2 -.wetk2 -.wewną2trz3 -.wf8 -.wg8 -.wh8 -.wielo3ś2 -.wielo3d2 -.wielo3k2 -.wieluse2t3 -.wilczo3m2 -.wj8 -.wk8 -.wl8 -.wm8 -.wn8 -.wniebo3w2 -.wodo3w2 -.wp8 -.wr8 -.ws8 -.współi2 -.współo2b3w -.współu2 -.współw2 -.wspó2ł3 -.wsze2ch3 -.wszecho2 -.wszechw2 -.wt8 -.wv8 -.ww8 -.wx8 -.wy3ć2 -.wy3ł2 -.wy3ś2 -.wy3ź2 -.wy3ż2 -.wy3b2 -.wy3c2 -.wy3d2 -.wy3f2 -.wy3g2 -.wy3h2 -.wy3k2 -.wy3l2 -.wy3m2 -.wy3o2d3r -.wy3p2 -.wy3r2 -.wy3s2 -.wy3t2 -.wy3w2 -.wy3z2 -.wy4ż3sz -.wy4cz3ha -.wybr2 -.wybrz2 -.wych2 -.wycz2 -.wydź2 -.wydż2 -.wydr2 -.wydz2 -.wye2k2s3 -.wygrz2 -.wyi2zo -.wykl2 -.wykr2 -.wykrz2 -.wymk2 -.wypch2 -.wyprz2 -.wyrż2 -.wyrz2 -.wysch2 -.wysm2 -.wysz2 -.wytch2 -.wytk2 -.wytr2 -.wz8 -.xć8 -.xł8 -.xń8 -.xś8 -.xź8 -.xż8 -.x8 -.xb8 -.xc8 -.xd8 -.xf8 -.xg8 -.xh8 -.xj8 -.xk8 -.xl8 -.xm8 -.xn8 -.xp8 -.xr8 -.xs8 -.xt8 -.xv8 -.xw8 -.xx8 -.xz8 -.zć8 -.zł8 -.zło3w2 -.zń8 -.zś8 -.zź8 -.zż8 -.z8 -.za3ć2 -.za3ł2 -.za3ś2 -.za3ź2 -.za3ż2 -.za3b2 -.za3c2 -.za3d2 -.za3f2 -.za3g2 -.za3h2 -.za3k2 -.za3l2 -.za3m2 -.za3o2b3r -.za3o2b3s -.za3p2 -.za3r2 -.za3s2 -.za3t2 -.za3u2 -.za3w2 -.za3z2 -.za4k3t -.za4l3g -.za4l3k -.za4l3t -.za4m3k -.za4r3ch -.za4uto -.za5m4k2n -.zabr2 -.zabrz2 -.zach2 -.zacz2 -.zadź2 -.zadż2 -.zadośću4 -.zado2ść3 -.zadr2 -.zady2s3po -.zadz2 -.zagrz2 -.zai2n3 -.zai2zo -.zain4ic -.zakl2 -.zakr2 -.zakrz2 -.zanie3d2 -.zarż2 -.zarz2 -.zasch2 -.zasm2 -.zasz2 -.zatk2 -.zatr2 -.zb8 -.zc8 -.zd8 -.zde2z3 -.zde3z4awu -.zde3z4el -.zde3z4er -.zde3z4y -.zdy2s3kont -.zdy2s3kred -.zdy2s3kwal -.ze3ć2 -.ze3ł2 -.ze3ś2 -.ze3ź2 -.ze3ż2 -.ze3b2 -.ze3c2 -.ze3d2 -.ze3f2 -.ze3g2 -.ze3h2 -.ze3k2 -.ze3l2 -.ze3m2 -.ze3p2 -.ze3r2 -.ze3s2 -.ze3t2 -.ze3tk2 -.ze3w2 -.ze3z2 -.ze4r3k -.ze4t3e2m1e2s -.ze4t3e2s1e2l -.ze4t3emp -.ze4t3hap -.zech2 -.zecz2 -.zedź2 -.zedż2 -.zedz2 -.zekl2 -.zepch2 -.zerż2 -.zerz2 -.zesch2 -.zesm4 -.zesz2 -.zf8 -.zg8 -.zh8 -.zimno3kr2 -.zj8 -.zk8 -.zl8 -.zm8 -.zmartwy2ch3 -.zmartwychw2 -.zn8 -.znie3ć2 -.znie3ł2 -.znie3ń2 -.znie3ś2 -.znie3ź2 -.znie3ż2 -.znie3b2 -.znie3c2 -.znie3d2 -.znie3f2 -.znie3g2 -.znie3h2 -.znie3k2 -.znie3l2 -.znie3m2 -.znie3n2 -.znie3p2 -.znie3r2 -.znie3s2 -.znie3t2 -.znie3w2 -.znie3z2 -.znie4dź3 -.znie4m3c -.zniech2 -.zniecz2 -.zniedż2 -.zniedz2 -.znierz2 -.zniesz2 -.zo2o3 -.zp8 -.zr8 -.zro2z3 -.zro3z4u -.zs8 -.zt8 -.zv8 -.zw8 -.zx8 -.zz8 -ą1 -ę1 -ó1 -ó4w3cz -ś1c -ź2dź -1ś2ci -2ć1ń -2ć1ś -2ć1ź -2ć1ż -2ć1b -2ć1c -2ć1d -2ć1f -2ć1g -2ć1k -2ć1m -2ć1n -2ć1p -2ć1s -2ć1t -2ć1z -2ł1ć -2ł1ń -2ł1ś -2ł1ź -2ł1ż -2ł1b -2ł1c -2ł1d -2ł1f -2ł1g -2ł1h -2ł1j -2ł1k -2ł1l -2ł1m -2ł1n -2ł1p -2ł1r -2ł1s -2ł1t -2ł1w -2ł1z -2ń1ć -2ń1ł -2ń1ń -2ń1ś -2ń1ź -2ń1ż -2ń1b -2ń1c -2ń1d -2ń1f -2ń1g -2ń1h -2ń1j -2ń1k -2ń1l -2ń1m -2ń1n -2ń1p -2ń1r -2ń1s -2ń1t -2ń1w -2ń1z -2śćc -2ś1ś -2ś1ź -2ś1ż -2ś1b -2ś1d -2ś1f -2ś1g -2ś1k -2ś1p -2ś1s -2ś1t -2ś1z -2ślm -2śln -2ź1ć -2ź1ś -2ź1ż -2ź1b -2ź1c -2ź1d -2ź1f -2ź1g -2ź1k -2ź1l -2ź1m -2ź1n -2ź1p -2ź1s -2ź1t -2ź1w -2ź1z -2ż1ć -2ż1ł -2ż1ń -2ż1ś -2ż1ź -2ż1b -2ż1c -2ż1d -2ż1f -2ż1g -2ż1j -2ż1k -2ż1l -2ż1m -2ż1n -2ż1p -2ż1r -2ż1s -2ż1t -2ż1w -2ż1z -2błk -2b1ć -2b1ń -2b1ś -2b1ź -2b1ż -2b1c -2b1d -2b1f -2b1g -2b1k -2b1m -2b1n -2b1p -2b1s -2b1t -2b1z -2brn -2c1ć -2c1ń -2c1ś -2c1ź -2c1ż -2c1b -2c1d -2c1f -2c1g -2c1k -2c1l -2c1m -2c1n -2c1p -2c1s -2c1t -2ch1ć -2ch1ń -2ch1ś -2ch1ź -2ch1ż -2ch1b -2ch1c -2ch1d -2ch1f -2ch1g -2ch1k -2ch1m -2ch1n -2ch1p -2ch1s -2ch1t -2ch1z -2cz1ć -2cz1ń -2cz1ś -2cz1ź -2cz1ż -2cz1b -2cz1c -2cz1d -2cz1f -2cz1g -2cz1k -2cz1l -2cz1m -2cz1n -2cz1p -2cz1s -2cz1t -2cz1z -2dłb -2dłsz -2dź1ć -2dź1ń -2dź1ś -2dź1ź -2dź1ż -2dź1b -2dź1c -2dź1d -2dź1f -2dź1g -2dź1k -2dź1m -2dź1n -2dź1p -2dź1s -2dź1t -2dź1z -2dż1ć -2dż1ń -2dż1ś -2dż1ź -2dż1ż -2dż1b -2dż1c -2dż1d -2dż1f -2dż1g -2dż1k -2dż1m -2dż1n -2dż1p -2dż1s -2dż1t -2dż1z -2d1ć -2d1ń -2d1ś -2d1b -2d1c -2d1f -2d1g -2d1k -2d1m -2d1n -2d1p -2d1s -2d1t -2drn -2dz1ć -2dz1ń -2dz1ś -2dz1ź -2dz1ż -2dz1b -2dz1c -2dz1d -2dz1f -2dz1g -2dz1k -2dz1l -2dz1m -2dz1n -2dz1p -2dz1s -2dz1t -2dz1z -2f1c -2f1k -2f1m -2f1n -2głb -2g1ć -2g1ń -2g1ś -2g1ź -2g1ż -2g1b -2g1c -2g1d -2g1f -2g1k -2g1m -2g1p -2g1s -2g1t -2g1z -2h1ć -2h1ł -2h1ń -2h1ś -2h1ź -2h1ż -2h1b -2h1c -2h1d -2h1f -2h1g -2h1j -2h1k -2h1l -2h1m -2h1n -2h1p -2h1r -2h1s -2h1t -2h1w -2h1z -2j1ć -2j1ł -2j1ń -2j1ś -2j1ź -2j1ż -2j1b -2j1c -2j1d -2j1f -2j1g -2j1h -2j1k -2j1l -2j1m -2j1n -2j1p -2j1r -2j1s -2j1t -2j1w -2j1z -2kłb -2k1ć -2k1ń -2k1ś -2k1ź -2k1ż -2k1b -2k1c -2k1d -2k1f -2k1g -2k1m -2k1n -2k1p -2k1s -2k1sz -2k1t -2k1z -2l1ć -2l1ł -2l1ń -2l1ś -2l1ź -2l1ż -2l1b -2l1c -2l1d -2l1f -2l1g -2l1h -2l1j -2l1k -2l1m -2l1n -2l1p -2l1r -2l1s -2l1t -2l1w -2l1z -2m1ć -2m1ł -2m1ń -2m1ś -2m1ź -2m1ż -2m1b -2m1c -2m1d -2m1f -2m1g -2m1h -2m1j -2m1k -2m1l -2m1n -2m1p -2m1r -2m1s -2m1t -2m1w -2m1z -2n1ć -2n1ł -2n1ń -2n1ś -2n1ź -2n1ż -2n1b -2n1c -2n1d -2n1f -2n1g -2n1h -2n1j -2n1k -2n1l -2n1m -2n1p -2n1r -2n1s -2n1t -2n1w -2n1z -2ntn -2p1ć -2p1ń -2p1ś -2p1ź -2p1ż -2p1b -2p1c -2p1d -2p1f -2p1g -2p1k -2p1m -2p1n -2p1s -2p1sz -2p1t -2p1z -2pln -2r1ć -2r1ł -2r1ń -2r1ś -2r1ź -2r1ż -2r1b -2r1c -2r1d -2r1f -2r1g -2r1h -2r1j -2r1k -2r1l -2r1m -2r1n -2r1p -2r1s -2r1t -2r1w -2rz1ć -2rz1ł -2rz1ń -2rz1ś -2rz1ź -2rz1ż -2rz1b -2rz1c -2rz1d -2rz1f -2rz1g -2rz1h -2rz1j -2rz1k -2rz1l -2rz1m -2rz1n -2rz1p -2rz1r -2rz1s -2rz1t -2rz1w -2słb -2s1ź -2s1ż -2s1b -2s1d -2s1f -2s1g -2s1s -2snk -2stk -2stn -2stsz -2sz1ć -2sz1ś -2sz1c -2sz1f -2sz1k -2sz1l -2sz1m -2sz1n -2sz1p -2sz1s -2sz1t -2sz1w -2sz1z -2szln -2t1ć -2t1ń -2t1ś -2t1ź -2t1ż -2t1b -2t1c -2t1d -2t1f -2t1g -2t1k -2t1m -2t1n -2t1p -2t1s -2t1z -2tln -2trk -2trzn -2w1ć -2w1ł -2w1ń -2w1ś -2w1ź -2w1ż -2w1b -2w1c -2w1d -2w1f -2w1g -2w1j -2w1k -2w1l -2w1m -2w1n -2w1p -2w1r -2w1s -2w1t -2w1z -2z1ć -2z1ś -2z1c -2z1d -2z1f -2z1k -2z1p -2z1s -2z1t -2zdk -2zdn -3d2niow -3k2sz2t -3m2k2n -3m2nest -3m2nezj -3m2sk2n -3p2neu -3w2ład -3w2łos -3w2czas -4ć3ć -4ł3ł -4ź3ź -4ż3ż -4b3b -4c3c -4d3d -4f3f -4g3g -4h3h -4j3j -4k3k -4l3l -4m3m -4n3n -4p3p -4r3r -4t3t -4w3w -4z3z -8ć. -8ćć. -8ćł. -8ćń. -8ćś. -8ćź. -8ćż. -8ćb. -8ćc. -8ćd. -8ćf. -8ćg. -8ćh. -8ćj. -8ćk. -8ćl. -8ćm. -8ćn. -8ćp. -8ćr. -8ćs. -8ćt. -8ćv. -8ćw. -8ćx. -8ćz. -8ł. -8łć. -8łł. -8łń. -8łś. -8łź. -8łż. -8łb. -8łc. -8łd. -8łf. -8łg. -8łh. -8łj. -8łk. -8łl. -8łm. -8łn. -8łp. -8łr. -8łs. -8łt. -8łv. -8łw. -8łx. -8łz. -8ń. -8ńć. -8ńł. -8ńń. -8ńś. -8ńź. -8ńż. -8ńb. -8ńc. -8ńd. -8ńf. -8ńg. -8ńh. -8ńj. -8ńk. -8ńl. -8ńm. -8ńn. -8ńp. -8ńr. -8ńs. -8ńt. -8ńv. -8ńw. -8ńx. -8ńz. -8ś. -8ść. -8śł. -8śń. -8śś. -8śź. -8śż. -8śb. -8śc. -8śd. -8śf. -8śg. -8śh. -8śj. -8śk. -8śl. -8śm. -8śn. -8śp. -8śr. -8śs. -8śt. -8śv. -8św. -8śx. -8śz. -8ź. -8źć. -8źł. -8źń. -8źś. -8źź. -8źż. -8źb. -8źc. -8źd. -8źf. -8źg. -8źh. -8źj. -8źk. -8źl. -8źm. -8źn. -8źp. -8źr. -8źs. -8źt. -8źv. -8źw. -8źx. -8źz. -8ż. -8żć. -8żł. -8żń. -8żś. -8żź. -8żż. -8żb. -8żc. -8żd. -8żf. -8żg. -8żh. -8żj. -8żk. -8żl. -8żm. -8żn. -8żp. -8żr. -8żs. -8żt. -8żv. -8żw. -8żx. -8żz. -8b. -8bć. -8bł. -8bń. -8bś. -8bź. -8bż. -8bb. -8bc. -8bd. -8bf. -8bg. -8bh. -8bj. -8bk. -8bl. -8bm. -8bn. -8bp. -8br. -8brz. -8bs. -8bt. -8bv. -8bw. -8bx. -8bz. -8c. -8cć. -8cł. -8cń. -8cś. -8cź. -8cż. -8cb. -8cc. -8cd. -8cf. -8cg. -8ch. -8chł. -8chrz. -8chw. -8cj. -8ck. -8cl. -8cm. -8cn. -8cp. -8cr. -8cs. -8ct. -8cv. -8cw. -8cx. -8cz. -8czt. -8d. -8dć. -8dł. -8dń. -8dś. -8dź. -8dż. -8db. -8dc. -8dd. -8df. -8dg. -8dh. -8dj. -8dk. -8dl. -8dm. -8dn. -8dp. -8dr. -8drz. -8ds. -8dt. -8dv. -8dw. -8dx. -8dz. -8f. -8fć. -8fł. -8fń. -8fś. -8fź. -8fż. -8fb. -8fc. -8fd. -8ff. -8fg. -8fh. -8fj. -8fk. -8fl. -8fm. -8fn. -8fp. -8fr. -8fs. -8ft. -8fv. -8fw. -8fx. -8fz. -8g. -8gć. -8gł. -8gń. -8gś. -8gź. -8gż. -8gb. -8gc. -8gd. -8gf. -8gg. -8gh. -8gj. -8gk. -8gl. -8gm. -8gn. -8gp. -8gr. -8gs. -8gt. -8gv. -8gw. -8gx. -8gz. -8h. -8hć. -8hł. -8hń. -8hś. -8hź. -8hż. -8hb. -8hc. -8hd. -8hf. -8hg. -8hh. -8hj. -8hk. -8hl. -8hm. -8hn. -8hp. -8hr. -8hs. -8ht. -8hv. -8hw. -8hx. -8hz. -8j. -8jć. -8jł. -8jń. -8jś. -8jź. -8jż. -8jb. -8jc. -8jd. -8jf. -8jg. -8jh. -8jj. -8jk. -8jl. -8jm. -8jn. -8jp. -8jr. -8js. -8jt. -8jv. -8jw. -8jx. -8jz. -8k. -8kć. -8kł. -8kń. -8kś. -8kź. -8kż. -8kb. -8kc. -8kd. -8kf. -8kg. -8kh. -8kj. -8kk. -8kl. -8km. -8kn. -8kp. -8kr. -8ks. -8kst. -8kt. -8kv. -8kw. -8kx. -8kz. -8l. -8lć. -8lł. -8lń. -8lś. -8lź. -8lż. -8lb. -8lc. -8ld. -8lf. -8lg. -8lh. -8lj. -8lk. -8ll. -8lm. -8ln. -8lp. -8lr. -8ls. -8lt. -8lv. -8lw. -8lx. -8lz. -8m. -8mć. -8mł. -8mń. -8mś. -8mź. -8mż. -8mb. -8mc. -8md. -8mf. -8mg. -8mh. -8mj. -8mk. -8ml. -8mm. -8mn. -8mp. -8mr. -8ms. -8mst. -8mt. -8mv. -8mw. -8mx. -8mz. -8n. -8nć. -8nł. -8nń. -8nś. -8nź. -8nż. -8nb. -8nc. -8nd. -8nf. -8ng. -8nh. -8nj. -8nk. -8nl. -8nm. -8nn. -8np. -8nr. -8ns. -8nt. -8nv. -8nw. -8nx. -8nz. -8p. -8pć. -8pł. -8pń. -8pś. -8pź. -8pż. -8pb. -8pc. -8pd. -8pf. -8pg. -8ph. -8pj. -8pk. -8pl. -8pm. -8pn. -8pp. -8pr. -8prz. -8ps. -8pt. -8pv. -8pw. -8px. -8pz. -8r. -8rć. -8rł. -8rń. -8rś. -8rź. -8rż. -8rb. -8rc. -8rd. -8rf. -8rg. -8rh. -8rj. -8rk. -8rl. -8rm. -8rn. -8rp. -8rr. -8rs. -8rsz. -8rt. -8rv. -8rw. -8rx. -8rz. -8rzł. -8s. -8sć. -8sł. -8sń. -8sś. -8sź. -8sż. -8sb. -8sc. -8sch. -8sd. -8sf. -8sg. -8sh. -8sj. -8sk. -8skrz. -8sl. -8sm. -8sn. -8sp. -8sr. -8ss. -8st. -8str. -8strz. -8stw. -8sv. -8sw. -8sx. -8sz. -8szcz. -8szczb. -8szk. -8szn. -8szt. -8sztr. -8t. -8tć. -8tł. -8tń. -8tś. -8tź. -8tż. -8tb. -8tc. -8td. -8tf. -8tg. -8th. -8tj. -8tk. -8tl. -8tm. -8tn. -8tp. -8tr. -8trz. -8ts. -8tt. -8tv. -8tw. -8tx. -8tz. -8v. -8vć. -8vł. -8vń. -8vś. -8vź. -8vż. -8vb. -8vc. -8vd. -8vf. -8vg. -8vh. -8vj. -8vk. -8vl. -8vm. -8vn. -8vp. -8vr. -8vs. -8vt. -8vv. -8vw. -8vx. -8vz. -8w. -8wć. -8wł. -8wń. -8wś. -8wź. -8wż. -8wb. -8wc. -8wd. -8wf. -8wg. -8wh. -8wj. -8wk. -8wl. -8wm. -8wn. -8wp. -8wr. -8ws. -8wt. -8wv. -8ww. -8wx. -8wz. -8x. -8xć. -8xł. -8xń. -8xś. -8xź. -8xż. -8xb. -8xc. -8xd. -8xf. -8xg. -8xh. -8xj. -8xk. -8xl. -8xm. -8xn. -8xp. -8xr. -8xs. -8xt. -8xv. -8xw. -8xx. -8xz. -8z. -8zć. -8zł. -8zń. -8zś. -8zź. -8zż. -8zb. -8zc. -8zd. -8zdr. -8zdrz. -8zf. -8zg. -8zh. -8zj. -8zk. -8zl. -8zm. -8zn. -8zp. -8zr. -8zs. -8zt. -8zv. -8zw. -8zx. -8zz. -a1 -a2u -a2y -aa2 -ae2 -ai2 -ao2 -be2eth -be2f3sz2 -be2k1hend -bi2n3o2ku -bi2sz3kop -bi2z3ne2s3m -bi2z3nes -birmin2g1ham -blo2k1hauz -bo2s3ma -br2d -bro2a2d3way -bu2sz3me -buk2sz3pan -busine2ss3m -busines2s -c4h -c4z -cal2d1well -ch2ł -ch2j -ch2l -ch2r -ch2w -chus1t -cu2r7zon -dż2ł -dż2j -dż2l -dż2r -dż2w -dże4z3b -dże4z3m -d4ź -d4ż -d4z -deut4sch3land -drz2w -du2sz3past -e1 -e2r5zac -e2u -e2y -e3u2sz -ea2 -ee2 -ei2 -eo2 -fi2s3harm -fi2sz3bin -fo2k2s3t -fo2r5zac -fol2k1lor -fos2f1a2zot -ga3d2get -gado3p2ta -gol2f3s -golfsz2 -gran2d1ilo -gro4t3r -hi2sz3p -hu2cz1w -hu2x3ley -i1 -i2ą -i2ę -i2ó -i2a -i2e -i2i -i2o -i2u -i2y -in4nsbruck -in4sbruc -j2t1ł -j2t1r -ja4z4z3b -ja4z4z3m -karl2s1kron -karl2s1ruhe -kir2chhoff -kongre2s3m -led1w -lu2ft3waffe -lu2ks1fer -ly2o -ma2r5zł -ma2r5zl -ma2r5zn -mi2sz1masz -mie2r5zł -mie2r5zi -mon2t3real -moza2i3k -mu2r7zasich3l -na4ł3kows -na4r3v -o1 -o2y -oa2 -och3mistrz -oe2 -of2f3set -oi2 -oo2 -ou2 -pa2n3a2mer -pa2s3cal -pa2s3ch -połu3d2ni -po3d4nieprz -po3m2ną -po3m2nę -po3m2ni -po4rt2s3mo2uth -po4rt3land -poli3e2t -poli3u2re -powsze3d2ni -pr2chal -pre2sz3pa -r4z -ro2e3nt2gen -ro2k3rocz -ro2s3to3c2k -s4z -se2t3le -sko2r5zoner -sm2r -sowi3z2 -sy2n3opt -sy2s1tem -sza2sz1ły -sze2z1long -sze4ść -szto2k1holm -szyn2k1was -to3y2o3t -turboo2d3rzut -tygo3d2ni -u1 -u2y -ua2 -ue2 -ui2 -uo2 -uu2 -vo2lk2s3 -we2e2k1end -we4st3f -we4st3m -y1 -ya2 -ye2 -yi2 -yo2 -yu2 -ze4p3p -} -\hyphenation{ -be-zach -be-zami -by-naj-mniej -gdzie-nie-gdzie -ina-czej -na-dal -ni-gdy -ni-gdzie -niech-że -niech-by -ow-szem -pó-łach -pó-łami -pó-łek -pod-ów-czas -przy-naj-mniej -skąd-inąd -tró-jach -tró-jami -tró-jek -} diff --git a/assets/hyphenation/pt.pat b/assets/hyphenation/pt.pat deleted file mode 100644 index cb947c43b..000000000 --- a/assets/hyphenation/pt.pat +++ /dev/null @@ -1,503 +0,0 @@ -% title: Hyphenation patterns for Portuguese -% copyright: Copyright (C) 1987, 1994, 1996, 2015 Pedro J. de Rezende, 1996, 2015 J. Joao Dias Almeida, 2024 Leonardo Araujo and Aline Benevides -% notice: This file is part of the hyph-utf8 package. -% See http://www.hyphenation.org/tex for more information. -% language: -% name: Portuguese -% tag: pt -% version: 1.4 2024-07-13 -% authors: -% - -% name: Pedro J. de Rezende -% contact: rezende (at) ic.unicamp.br -% - -% name: J. Joao Dias Almeida -% contact: jj (at) di.uminho.pt -% - -% name: Leonardo Araujo -% contact: leolca (at) gmail.com -% - -% name: Aline Benevides -% contact: benevides.aline12 (at) gmail.com -% licence: -% name: BSD 3-clause licence -% url: https://opensource.org/licenses/BSD-3-Clause -% text: > -% Redistribution and use in source and binary forms, with or without -% modification, are permitted provided that the following conditions -% are met: -% * Redistributions of source code must retain the above copyright -% notice, this list of conditions and the following disclaimer. -% * Redistributions in binary form must reproduce the above copyright -% notice, this list of conditions and the following disclaimer in the -% documentation and/or other materials provided with the -% distribution. -% * Neither the name of the University of Campinas, of the University -% of Minho nor the names of its contributors may be used to endorse -% or promote products derived from this software without specific -% prior written permission. -% -% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PEDRO J. DE -% REZENDE OR J.JOAO DIAS ALMEIDA BE LIABLE FOR ANY DIRECT, INDIRECT, -% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -% OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -% AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY -% WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -% POSSIBILITY OF SUCH DAMAGE. -% hyphenmins: -% typesetting: -% left: 2 -% right: 3 -% changes: -% - Version 1.4 Release date: 13/07/2024 Leonardo Araujo and Aline Benevides -% - Version 1.3 Release date: 12/08/2015 Pedro J. de Rezende and J. Joao Dias Almeida -% - Version 1.2 Release date: 07/21/1996 Pedro J. de Rezende and J. Joao Dias Almeida -% - Version 1.1 Release date: 04/12/1994 Pedro J. de Rezende -% - Version 1.0 Release date: 02/13/1987 Pedro J. de Rezende -% texlive: -% synonyms: -% - portuges -% encoding: ec -% babelname: portuguese -% legacy_patterns: pthyph.tex -% message: Portuguese hyphenation patterns -% description: Hyphenation patterns for Portuguese in T1/EC and UTF-8 encodings. -% -\patterns{ -1b2l -1b2r -1ba -1be -1bi -1bo -1bu -1bá -1bâ -1bã -1bé -1bí -1bó -1bú -1bê -1bõ -1c2h -1c2l -1c2r -1ca -1ce -1ci -1co -1cu -1cá -1câ -1cã -1cé -1cí -1có -1cú -1cê -1cõ -1ça -1çe -1çi -1ço -1çu -1çá -1çâ -1çã -1çé -1çí -1çó -1çú -1çê -1çõ -1d2l -1d2r -1da -1de -1di -1do -1du -1dá -1dâ -1dã -1dé -1dí -1dó -1dú -1dê -1dõ -1f2l -1f2r -1fa -1fe -1fi -1fo -1fu -1fá -1fâ -1fã -1fé -1fí -1fó -1fú -1fê -1fõ -1g2l -1g2r -1ga -1ge -1gi -1go -1gu -1gu4a -1gu4e -1gu4i -1gu4o -1gá -1gâ -1gã -1gé -1gí -1gó -1gú -1gê -1gõ -1ja -1je -1ji -1jo -1ju -1já -1jâ -1jã -1jé -1jí -1jó -1jú -1jê -1jõ -1k2l -1k2r -1ka -1ke -1ki -1ko -1ku -1ká -1kâ -1kã -1ké -1kí -1kó -1kú -1kê -1kõ -1l2h -1la -1le -1li -1lo -1lu -1lá -1lâ -1lã -1lé -1lí -1ló -1lú -1lê -1lõ -1ma -1me -1mi -1mo -1mu -1má -1mâ -1mã -1mé -1mí -1mó -1mú -1mê -1mõ -1n2h -1na -1ne -1ni -1no -1nu -1ná -1nâ -1nã -1né -1ní -1nó -1nú -1nê -1nõ -1p2l -1p2r -1pa -1pe -1pi -1po -1pu -1pá -1pâ -1pã -1pé -1pí -1pó -1pú -1pê -1põ -1qu4a -1qu4e -1qu4i -1qu4o -1ra -1re -1ri -1ro -1ru -1rá -1râ -1rã -1ré -1rí -1ró -1rú -1rê -1rõ -1sa -1se -1si -1so -1su -1sá -1sâ -1sã -1sé -1sí -1só -1sú -1sê -1sõ -1t2l -1t2r -1ta -1te -1ti -1to -1tu -1tá -1tâ -1tã -1té -1tí -1tó -1tú -1tê -1tõ -1v2l -1v2r -1va -1ve -1vi -1vo -1vu -1vá -1vâ -1vã -1vé -1ví -1vó -1vú -1vê -1võ -1w2l -1w2r -1xa -1xe -1xi -1xo -1xu -1xá -1xâ -1xã -1xé -1xí -1xó -1xú -1xê -1xõ -1za -1ze -1zi -1zo -1zu -1zá -1zâ -1zã -1zé -1zí -1zó -1zú -1zê -1zõ -a3a -a3e -a3o -c3c -e3a -e3e -e3o -i3a -i3e -i3i -i3o -i3â -i3ê -i3ô -o3a -o3e -o3o -r3r -s3s -u3a -u3e -u3o -u3u -.p2si -.p2sí -.g2no -.g2nó -.g2nô -t2c -1p2neu -.t2m -.p2t -su2b3r -su2b3l -.su3b4li -a1ir. -u1ir. -1qu -1vô -1lô -1cô -1gô -1bô -1tô -1rô -1pô -a1é -a1í -a1ó -a1ú -e1á -e1â -e1ã -e1é -e1ê -e1í -e1ó -é1o -e1ú -i1á -i1ã -í1a -i1é -i1í -i1ó -í1o -i1u -i1ú -o1á -o1é -o1í -o1ó -u1á -u1ã -u1â -u1í -ú1o -1qu2á -1qu2â -1qu2í -1gu2í -a1ind -a1i1nh -e1imp -e1inc -e1inf -e1ing -e1ins -e1int -e1inv -u1iz. -a1iz. -4a. -4e. -4o. -1gu2á -1gu2ã -1qu2ã -.m2n -c2za -.s2 -1p2seu1d -1dô -1fô -1mô -1nô -1sô -1zô -tu1i -tu2it -tu2id -bu1i -nu1i -o1in -u1in -su1i -í1e -ju1i -fu1i -du1i -do1im -au1i -u1i1ç -u1ê -1gu2ê -1qu2ê -1çô -u1é -1gu2é -1qu2é -1xô -a1â -a1ã -a1ô -e1ô -.ne4o -o1ã -o1ê -o1im -o2i1na -pro1i1b -co2ima -.t2 -1- -} -\hyphenation{% Do NOT make any alterations to this list! --- PdR -hard-ware -soft-ware -} diff --git a/assets/spelling/en-extra.txt b/assets/spelling/en-extra.txt deleted file mode 100644 index 7d04908e3..000000000 --- a/assets/spelling/en-extra.txt +++ /dev/null @@ -1,241 +0,0 @@ -# English spelling supplement: common words the public-domain Moby list (1990s) lacks. -# Written by WordCraft contributors (MIT OR Apache-2.0); see ATTRIBUTION.md. -# One lowercase base form per line, sorted. Plurals, -ed, -ing, -er, -ly… come from the -# inflection rules in crates/proof/src/lib.rs, so list "desktop", not "desktops". -airbag -airfare -artcraft -autocomplete -autocorrect -autofill -autoformat -autosave -backend -bitcoin -bitmap -blockchain -blog -blogger -bluetooth -captcha -caregiver -carpool -cellphone -chatbot -chatroom -checkbox -checklist -childcare -clamour -clickable -colourful -colourisation -colourise -colourless -configurable -copyedit -coronavirus -covid -crowdfunding -crowdsource -crypto -cryptocurrency -customizable -customization -cyber -cybersecurity -cyberspace -database -dataset -daycare -desktop -download -downloadable -downsize -dropdown -earbud -ebook -ecommerce -email -emoji -emoticon -endeavour -esports -facebook -favicon -fintech -firewall -flowchart -foodie -freeware -frontend -fundraiser -gameplay -gamification -geeky -geolocation -gigabit -gigabyte -github -glamour -globalization -google -groupthink -hackathon -hardcode -hashtag -healthcare -homepage -homeschool -honour -honourable -honourably -hotspot -hyperlink -hypertext -inbox -incentivize -infographic -instagram -internet -interoperability -intranet -ipad -iphone -javascript -kickstart -kilobyte -laptop -lifestyle -linkedin -linux -livestream -lockdown -login -logout -lowercase -makeover -malware -manoeuvrable -megabyte -megapixel -meme -metadata -microchip -microsoft -middleware -mindset -multicultural -multiplayer -multitask -nerdy -netbook -netflix -newsfeed -offboard -offline -onboard -online -onscreen -outsource -paediatric -paralegal -passcode -petabyte -phishing -photobomb -playdate -playlist -plugin -podcast -popup -prequel -printout -prioritise -prioritization -prioritize -privatise -ransomware -recyclable -rigour -ringtone -roadmap -rollout -runtime -scalability -screencast -screensaver -screenshot -scrollbar -selfie -signup -sitemap -skillset -smartphone -smartwatch -smiley -snowboard -spellcheck -spellchecker -spreadsheet -spyware -stakeholder -standup -startup -subfolder -sunscreen -superfood -superuser -synthesise -tagline -takeout -techie -telecommute -teleconference -telehealth -telemedicine -terabyte -textbox -timeline -timestamp -toolbar -toolkit -touchpad -touchscreen -trackball -trackpad -travelogue -upcycle -upload -uppercase -upsell -upskill -usability -username -videoconference -videogame -vlog -vlogger -voicemail -walkthrough -webcam -webcast -webinar -weblog -webmaster -webpage -website -whatsapp -whiteboard -wifi -wiki -wikipedia -wireframe -wordcraft -workaround -workflow -workforce -workplace -workspace -workstation -youtube diff --git a/assets/spelling/en-variants.txt b/assets/spelling/en-variants.txt deleted file mode 100644 index 789ad5387..000000000 --- a/assets/spelling/en-variants.txt +++ /dev/null @@ -1,305 +0,0 @@ -# English spelling variants: American and British forms of the same word, for the English -# dialects (#1281, #1045). Written by WordCraft contributors from general knowledge of English -# spelling (MIT OR Apache-2.0); no other word list copied. See ATTRIBUTION.md. -# -# Each line: POLICY AMERICAN BRITISH -# POLICY is three letters, for American English (en-US), Canadian English (en-CA) and the -# British-spelling Englishes (en-GB, en-AU, en-NZ, en-IE, en-ZA, en-IN…), in that order: -# u only the American form is right there (the British one is flagged, American suggested) -# b only the British form is right there -# * both are right -# Inflections (colours, centred, organising, organisation…) come from the rules in -# crates/proof/src/en_variants.rs for -or/-our, -er/-re and -ize/-ise style pairs; other pairs -# get only a plural, so list their other forms on lines of their own. When in doubt a form is -# accepted: a false alarm is worse than a missed variant. - -# -or / -our -ubb arbor arbour -ubb ardor ardour -ubb armor armour -ubb armory armoury -ubb behavior behaviour -ubb behavioral behavioural -ubb candor candour -ubb clamor clamour -ubb color colour -ubb demeanor demeanour -ubb discolor discolour -ubb dishonor dishonour -ubb endeavor endeavour -ubb favor favour -ubb favorite favourite -ubb favoritism favouritism -ubb fervor fervour -ubb flavor flavour -ubb harbor harbour -ubb honor honour -ubb humor humour -ubb labor labour -ubb misbehavior misbehaviour -ubb neighbor neighbour -ubb neighborhood neighbourhood -ubb neighborly neighbourly -ubb odor odour -ubb parlor parlour -ubb rancor rancour -ubb rigor rigour -ubb rumor rumour -ubb savior saviour -ubb savor savour -ubb savory savoury -ubb splendor splendour -ubb succor succour -ubb tumor tumour -ubb unfavorable unfavourable -ubb valor valour -ubb vapor vapour -ubb vigor vigour - -# -er / -re -ubb caliber calibre -ubb center centre -ubb centimeter centimetre -ubb epicenter epicentre -ubb fiber fibre -ubb kilometer kilometre -ubb liter litre -ubb luster lustre -ubb maneuver manoeuvre -ubb meager meagre -ubb millimeter millimetre -ubb miter mitre -ubb ocher ochre -ubb saber sabre -ubb sepulcher sepulchre -ubb somber sombre -ubb specter spectre -*bb theater theatre -# A meter is also a measuring device in British English. -u** meter metre - -# -ize / -ise (British English accepts both: Oxford spelling uses -ize) -uu* agonize agonise -uu* apologize apologise -uu* authorize authorise -uu* capitalize capitalise -uu* categorize categorise -uu* centralize centralise -uu* characterize characterise -uu* civilize civilise -uu* colonize colonise -uu* commercialize commercialise -uu* computerize computerise -uu* criticize criticise -uu* crystallize crystallise -uu* customize customise -uu* decentralize decentralise -uu* demonize demonise -uu* deodorize deodorise -uu* destabilize destabilise -uu* digitize digitise -uu* dramatize dramatise -uu* economize economise -uu* emphasize emphasise -uu* energize energise -uu* equalize equalise -uu* familiarize familiarise -uu* fantasize fantasise -uu* fertilize fertilise -uu* finalize finalise -uu* formalize formalise -uu* fossilize fossilise -uu* galvanize galvanise -uu* generalize generalise -uu* globalize globalise -uu* harmonize harmonise -uu* hospitalize hospitalise -uu* humanize humanise -uu* hypnotize hypnotise -uu* hypothesize hypothesise -uu* idealize idealise -uu* idolize idolise -uu* immobilize immobilise -uu* immortalize immortalise -uu* immunize immunise -uu* industrialize industrialise -uu* initialize initialise -uu* italicize italicise -uu* itemize itemise -uu* jeopardize jeopardise -uu* legalize legalise -uu* legitimize legitimise -uu* liberalize liberalise -uu* localize localise -uu* magnetize magnetise -uu* marginalize marginalise -uu* maximize maximise -uu* mechanize mechanise -uu* memorize memorise -uu* mesmerize mesmerise -uu* minimize minimise -uu* mobilize mobilise -uu* modernize modernise -uu* monopolize monopolise -uu* moralize moralise -uu* nationalize nationalise -uu* naturalize naturalise -uu* neutralize neutralise -uu* normalize normalise -uu* optimize optimise -uu* organize organise -uu* ostracize ostracise -uu* oxidize oxidise -uu* pasteurize pasteurise -uu* patronize patronise -uu* penalize penalise -uu* personalize personalise -uu* plagiarize plagiarise -uu* polarize polarise -uu* popularize popularise -uu* pressurize pressurise -uu* prioritize prioritise -uu* privatize privatise -uu* publicize publicise -uu* pulverize pulverise -uu* randomize randomise -uu* rationalize rationalise -uu* realize realise -uu* recognize recognise -uu* reorganize reorganise -uu* revolutionize revolutionise -uu* satirize satirise -uu* scrutinize scrutinise -uu* sensitize sensitise -uu* socialize socialise -uu* specialize specialise -uu* stabilize stabilise -uu* standardize standardise -uu* sterilize sterilise -uu* stigmatize stigmatise -uu* subsidize subsidise -uu* summarize summarise -uu* symbolize symbolise -uu* sympathize sympathise -uu* synchronize synchronise -uu* synthesize synthesise -uu* systematize systematise -uu* tantalize tantalise -uu* terrorize terrorise -uu* theorize theorise -uu* trivialize trivialise -uu* urbanize urbanise -uu* utilize utilise -uu* vandalize vandalise -uu* verbalize verbalise -uu* victimize victimise -uu* visualize visualise -uu* vocalize vocalise -uu* westernize westernise - -# -yze / -yse -u*b analyze analyse -u*b catalyze catalyse -u*b dialyze dialyse -u*b electrolyze electrolyse -u*b hydrolyze hydrolyse -u*b paralyze paralyse -u*b psychoanalyze psychoanalyse - -# -ense / -ence -ubb defense defence -ubb offense offence -ubb pretense pretence -ubb defenseless defenceless -# In British English "license" and "practise" are the verbs, so only American English flags. -u** license licence -u** practice practise - -# Doubled l (American English accepts both) -*bb canceled cancelled -*bb canceling cancelling -*bb channeled channelled -*bb channeling channelling -*bb counseled counselled -*bb counseling counselling -*bb counselor counsellor -*bb counselors counsellors -*bb dueling duelling -*bb equaled equalled -*bb equaling equalling -*bb fueled fuelled -*bb fueling fuelling -*bb jeweler jeweller -*bb jewelers jewellers -*bb jewelry jewellery -*bb labeled labelled -*bb labeling labelling -*bb leveled levelled -*bb leveling levelling -*bb marveled marvelled -*bb marveling marvelling -*bb marvelous marvellous -*bb modeled modelled -*bb modeling modelling -*bb quarreled quarrelled -*bb quarreling quarrelling -*bb signaled signalled -*bb signaling signalling -*bb totaled totalled -*bb totaling totalling -*bb traveled travelled -*bb traveler traveller -*bb travelers travellers -*bb traveling travelling -*bb tunneled tunnelled -*bb tunneling tunnelling -*bb woolen woollen - -# -og / -ogue -*bb catalog catalogue -*bb cataloged catalogued -*bb cataloging cataloguing - -# ae / oe -u*b anemia anaemia -u*b anemic anaemic -u*b anesthesia anaesthesia -u*b anesthetic anaesthetic -**b archeology archaeology -**b archeologist archaeologist -u*b diarrhea diarrhoea -u*b edema oedema -u*b esophagus oesophagus -u*b estrogen oestrogen -u*b gynecologist gynaecologist -u*b gynecology gynaecology -u*b hematology haematology -u*b hemoglobin haemoglobin -u*b hemorrhage haemorrhage -u*b leukemia leukaemia -u*b orthopedic orthopaedic -u*b pediatric paediatric -u*b pediatrician paediatrician -u*b pediatrics paediatrics - -# Other words -u*b aluminum aluminium -u*b cozy cosy -ubb mold mould -ubb molded moulded -ubb molding moulding -ubb moldy mouldy -ubb molt moult -ubb pajamas pyjamas -ubb plow plough -ubb plowed ploughed -ubb plowing ploughing -u** program programme -u*b skeptic sceptic -u*b skeptical sceptical -u*b skepticism scepticism -ubb smolder smoulder -ubb smoldering smouldering -# "Tire" and "curb" are also verbs everywhere, so only American English flags. -uu* tire tyre -uu* curb kerb diff --git a/assets/spelling/ru-extra.txt b/assets/spelling/ru-extra.txt deleted file mode 100644 index 7741135ed..000000000 --- a/assets/spelling/ru-extra.txt +++ /dev/null @@ -1,214 +0,0 @@ -# Russian spelling supplement: common words older Russian Hunspell dictionaries lack. -# Written by WordCraft contributors (MIT OR Apache-2.0); see ATTRIBUTION.md. -# One word per line: the base form, a TAB, then its declension, from which -# crates/proof/src/ru/extra.rs makes every form: -# m masculine noun ending in a consonant (маркетплейс, -а, -у, -ом, -е, -ы/-и, -ов/-ей…) -# f feminine noun in -а (инфографика, -и, -е, -у, -ой…; genitive plural without ending) -# fia feminine noun in -ия (интеграция, -ии, -ию, -ией, -ий…) -# fs feminine noun in -ь (соцсеть, -и, -ью, -ей, -ям…) -# nie neuter noun in -ие (согласие, -ия, -ию, -ием, -ий…) -# no neuter noun in -о (юрлицо, -а, -у, -ом, -е; genitive plural without ending) -# adj adjective or participle in -ый, -ий, -ой (бонусный, газоиспользующий) -# ovat verb in -овать with its conjugation and participles (колеровать, администрировать) -# = the forms themselves, space-separated (verbs, nouns with a fleeting vowel) -# A word alone on its line doesn't decline (онлайн, медиа). Capitalized words are names and match -# only when written with a capital. - -# Commerce, marketing and the web -маркетплейс m -селлер m -фулфилмент m -ритейл m -ритейлер m -дистрибьютор m -дропшиппинг m -мерчандайзер m -эквайринг m -промокод m -кешбэк m -кэшбэк m -лендинг m -лид m -тренд m -бренд m -брендбук m -ребрендинг m -таргетинг m -геотаргетинг m -ретаргетинг m -таргетолог m -маркетолог m -копирайтер m -рерайтер m -контент m -блогер m -инфлюенсер m -хештег m -хэштег m -репост m -лайк m -подкаст m -стриминг m -вебинар m -чат m -бот m -чат-бот m -стартап m -франчайзинг m -аутсорсинг m -аутстаффинг m -коворкинг m -дедлайн m -фидбэк m -фидбек m -кейс m -скриншот m -скринкаст m -баннер m -виджет m -дашборд m -сервис m -сайт m -скан m -токен m -аккаунт m -логин m -пароль m -смартфон m -гаджет m -планшет m -ноутбук m -апгрейд m -апдейт m -бэкап m -бэкенд m -фронтенд m -датасет m -промпт m -интерфейс m -хостинг m -домен m -сниппет m -трафик m -юзер m -стикер m -чекбокс m -инпут m -плейсхолдер m -тултип m -гипсокартон m -пеноплекс m -праймер m -колер m -колорант m -Яндекс m -Битрикс m -Вебвизор m -Дзен m -инфографика f -аналитика f -оплата f -предоплата f -постоплата f -доплата f -переплата f -недоплата f -маркировка = маркировка маркировки маркировке маркировку маркировкой маркировкою маркировок маркировкам маркировками маркировках -отгрузка = отгрузка отгрузки отгрузке отгрузку отгрузкой отгрузкою отгрузок отгрузкам отгрузками отгрузках -колеровка = колеровка колеровки колеровке колеровку колеровкой колеровкою колеровок колеровкам колеровками колеровках -рассылка = рассылка рассылки рассылке рассылку рассылкой рассылкою рассылок рассылкам рассылками рассылках -выгрузка = выгрузка выгрузки выгрузке выгрузку выгрузкой выгрузкою выгрузок выгрузкам выгрузками выгрузках -интеграция fia -пагинация fia -госрегистрация fia -геолокация fia -монетизация fia -кастомизация fia -персонализация fia -сегментация fia -верификация fia -валидация fia -авторизация fia -аутентификация fia -оптимизация fia -визуализация fia -соцсеть fs -согласие nie -домовладение nie -рождение nie -паллетирование nie -неисполнение nie -непредоставление nie -бонусный adj -маркетинговый adj -мотивационный adj -гипоаллергенный adj -тиксотропный adj -сополимерный adj -покупочный adj -таргетированный adj -брендированный adj -кастомный adj -онлайновый adj -офлайновый adj -колеруемый adj -колерованный adj -заколерованный adj -газоиспользующий adj -колеровать ovat -заколеровать ovat -администрировать ovat -модерировать ovat -отмодерировать ovat -таргетировать ovat -монетизировать ovat -брендировать ovat -кастомизировать ovat -детектировать ovat -верифицировать ovat -валидировать ovat -генерировать ovat -сгенерировать ovat -мониторить = мониторить мониторю мониторишь мониторит мониторим мониторите мониторят мониторил мониторила мониторили мониторя мониторинг -гуглить = гуглить гуглю гуглишь гуглит гуглим гуглите гуглят гуглил гуглила гуглили -скачивать = скачивать скачиваю скачиваешь скачивает скачиваем скачиваете скачивают скачивал скачивала скачивали скачивается скачиваются -оценивание nie -медучреждение nie -юрлицо no -учётка = учётка учётки учётке учётку учёткой учёток учёткам учётками учётках учетка учетки учетке учетку учеткой учеток учеткам учетками учетках -перелинковка = перелинковка перелинковки перелинковке перелинковку перелинковкой перелинковок -поклейка = поклейка поклейки поклейке поклейку поклейкой поклеек -детекция fia -досудебный adj -категорийный adj -оркестратор m -оффер m -трекер m -бриф m -гайд m -монтажёр m -тенант m -мерчант m -амбассадор m -Роскомнадзор m -Директ m -Маркет m -вовлечённость fs -вовлеченность fs -укрывистость fs -# Words that don't decline -онлайн -офлайн -оффлайн -медиа -демо -франчайзи -киноа -смс -пн -вт -ср -чт -пт -сб -вс diff --git a/craft-i18n.rev b/craft-i18n.rev index fd264c675..2e3a43287 100644 --- a/craft-i18n.rev +++ b/craft-i18n.rev @@ -1 +1 @@ -9205108cfdced5177706264038848b68b2422d2b +062a6375c322c88b96691a17f7b5fed71c51f7a8 diff --git a/crates/engine/src/cmd/page/tests.rs b/crates/engine/src/cmd/page/tests.rs index 31ca0d8a5..78a1c7c10 100644 --- a/crates/engine/src/cmd/page/tests.rs +++ b/crates/engine/src/cmd/page/tests.rs @@ -374,6 +374,9 @@ fn line_numbering_options() { /// there breaks the word. #[test] fn hyphenation_options_and_manual_hyphenation() { + if crate::tests::no_language_data("hyphenation_options_and_manual_hyphenation") { + return; + } let mut s = Session::new(wordcraft_doc::Document::from_text(&"a extraordinarily be uncharacteristic of responsibilities the considerations ".repeat(12))); let r = s.run("layout.hyphenation", &json!({"zone": 36, "caps": false, "limit": 3})).unwrap(); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 7dabdf0af..24a3f9eec 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1,6 +1,6 @@ -// Reuse our hand-written Czech proof fixture; its install helper resolves this parent import. +// A copy of craft-proof's hand-written Czech fixture (`crates/proof/src/cs/test_dict.rs` in +// craft-libs, which a dependency's tests can't share); its install helper resolves this parent import. use wordcraft_proof::cs::install_dictionary; -#[path = "../../proof/src/cs/test_dict.rs"] mod czech_test_dict; use serde_json::json; @@ -18,6 +18,22 @@ mod revision_checks; mod typing; mod wordart; +/// True, after saying so on stderr, when this build lacks the Language data the proofing tests +/// need (craft-proof built without `CRAFT_I18N_DIR`). The probes are craft-proof's public +/// `english_source` (the embedded English dictionary) and its American/British variant table +/// (`colour` → `color`): a craft-i18n checkout without some of the files embeds only the rest, +/// so both are asked. Without them English still spells from craft-proof's built-in word list, +/// but there are no hyphenation patterns and no variant table, so the tests that check those +/// return early, as craft-proof's own do; with the data (CI, releases) they all run. +pub(crate) fn no_language_data(test: &str) -> bool { + let missing = wordcraft_proof::english_source(wordcraft_proof::ProofLang::En) != Some(wordcraft_proof::EnglishSource::Embedded) + || wordcraft_proof::en_variants::preferred("colour", wordcraft_proof::ProofLang::En).as_deref() != Some("color"); + if missing { + eprintln!("{test}: skipped, needs the Language data (set CRAFT_I18N_DIR to a craft-i18n checkout)"); + } + missing +} + fn s() -> Session { Session::new(wordcraft_doc::Document::new()) } @@ -8665,6 +8681,9 @@ fn pairs(v: &[(&str, &str)]) -> Vec<(String, String)> { /// it and Change fixes it. #[test] fn english_dialects_through_the_review_commands() { + if no_language_data("english_dialects_through_the_review_commands") { + return; + } let mut s = s(); run(&mut s, "document.setText", json!({"text": "The colour of the center."})); // US English by default: the British spelling is flagged, the American one isn't. @@ -8698,6 +8717,9 @@ fn english_dialects_through_the_review_commands() { /// and is proofed the same way when it comes back. #[test] fn english_dialects_survive_a_docx_round_trip() { + if no_language_data("english_dialects_survive_a_docx_round_trip") { + return; + } use std::io::Read; let mut s = s(); run(&mut s, "document.setText", json!({"text": "Colour and color. Centre and center."})); @@ -8726,6 +8748,9 @@ fn english_dialects_survive_a_docx_round_trip() { /// system, #1045); another region's language leaves US English. #[test] fn new_documents_follow_the_regions_english() { + if no_language_data("new_documents_follow_the_regions_english") { + return; + } let mut s = s(); s.system_region = wordcraft_geom::Region::from_tag("en_GB.UTF-8"); assert_eq!(s.region_english().as_deref(), Some("en-GB")); diff --git a/crates/proof/src/cs/test_dict.rs b/crates/engine/src/tests/czech_test_dict.rs similarity index 100% rename from crates/proof/src/cs/test_dict.rs rename to crates/engine/src/tests/czech_test_dict.rs diff --git a/crates/proof/Cargo.toml b/crates/proof/Cargo.toml deleted file mode 100644 index 4c1a5b112..000000000 --- a/crates/proof/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "wordcraft-proof" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "WordCraft proofing per language: spelling, suggestions, grammar checks and hyphenation for English (public-domain word list) and Polish (installed Hunspell dictionary, TeX patterns)" - -[lints] -workspace = true - -[dependencies] -log = { workspace = true } -miniz_oxide = "0.9" -unicode-normalization = "0.1" - -[dev-dependencies] -proptest = { workspace = true } diff --git a/crates/proof/src/affix.rs b/crates/proof/src/affix.rs deleted file mode 100644 index cb4621210..000000000 --- a/crates/proof/src/affix.rs +++ /dev/null @@ -1,1784 +0,0 @@ -//! Hunspell-style affix dictionaries — the subset the SJP.PL Polish dictionary (`pl_PL`) uses. -//! -//! A word is correct when it is a stem, a stem with one suffix, a stem with one prefix, or a stem -//! with a prefix and a suffix that both allow cross products; each rule applies only to stems -//! carrying its flag and ending (suffixes) or starting (prefixes) as its condition says. Highly -//! inflected languages store millions of forms this way in a few hundred thousand stems: the -//! Polish dictionary's ~350k stems and ~7,500 rules accept ~4.5 million forms. -//! -//! The rules and stems come from a Hunspell `.aff`/`.dic` pair ([`AffixDict::from_hunspell`]), -//! read at runtime from the dictionaries installed on the system ([`crate::hunspell`]). They can -//! also be stored in WordCraft's compact format ([`AffixDict::to_bytes`]): the rules as text -//! lines and the stems front-coded, raw-deflated together (the Polish dictionary is under 1 MB). -//! -//! Supported: one-character flags (at most 256 distinct, any Unicode character — `FLAG UTF-8`), -//! flag aliases (`AF`: a number standing for a set of flags), `PFX`/`SFX` with strip, append and -//! condition (`.`, letters, `[abc]`, `[^abc]`), cross products, continuation classes on suffixes -//! (at most two suffixes in a row, as in Hunspell), `FORBIDDENWORD`, `NOSUGGEST`, `KEEPCASE` (the -//! stem is a word only in its own letter case), input conversion (`ICONV`: ligatures such as `fi` -//! read as `fi`) and ignored characters (`IGNORE`), and the suggestion tables `TRY`, `KEY`, `MAP` -//! and `REP`. -//! Morphological data (`AM` aliases, the fields after a stem) is skipped. Rejected: compounding, -//! long or numeric flags, `NEEDAFFIX` and the other directives that change which words are correct. -//! -//! Lookups never panic: stems sit in one string, found through an open-addressing hash table, and -//! a probe allocates only small scratch strings for the candidate roots it tries. - -use std::collections::HashMap; -use std::hash::Hasher; - -use crate::patterns::{FxHasher, FxMap}; - -const MAGIC: &str = "WCAFF1"; -/// Longest word (in bytes) looked up; longer input is never a dictionary word. -pub const MAX_WORD_BYTES: usize = 120; -/// Empty slot in the stem hash table. -const EMPTY: u32 = u32::MAX; - -/// Directives that change which words are correct in ways this engine doesn't implement. -const UNSUPPORTED: &[&str] = &[ - "CHECKSHARPS", - "CIRCUMFIX", - "COMPLEXPREFIXES", - "COMPOUNDBEGIN", - "COMPOUNDEND", - "COMPOUNDFIRST", - "COMPOUNDFLAG", - "COMPOUNDLAST", - "COMPOUNDMIDDLE", - "COMPOUNDMIN", - "COMPOUNDPERMITFLAG", - "COMPOUNDRULE", - "FULLSTRIP", - "NEEDAFFIX", - "OCONV", - "ONLYINCOMPOUND", - "PSEUDOROOT", -]; - -/// Most distinct flags a dictionary may use: every single-byte flag (the Hungarian dictionary -/// uses 158). -const MAX_FLAGS: usize = 256; - -/// A set of flags, one bit per distinct flag character of the dictionary. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -struct Mask([u64; MAX_FLAGS / 64]); - -impl Mask { - const EMPTY: Mask = Mask([0; MAX_FLAGS / 64]); - - /// The set holding flag number `i` alone (empty when `i` is out of range). - fn bit(i: usize) -> Mask { - let mut m = Mask::EMPTY; - if let Some(w) = m.0.get_mut(i / 64) { - *w = 1 << (i % 64); - } - m - } - - /// The number of the lowest flag in the set, if any. - fn first(self) -> Option { - self.0.iter().enumerate().find(|(_, w)| **w != 0).map(|(i, w)| i * 64 + w.trailing_zeros() as usize) - } - - fn is_empty(self) -> bool { - self.0.iter().all(|&w| w == 0) - } - - /// Do the two sets share a flag? - fn meets(self, other: Mask) -> bool { - !(self & other).is_empty() - } - - /// Does this set hold every flag of `other`? - fn covers(self, other: Mask) -> bool { - (self & other) == other - } -} - -impl std::ops::BitAnd for Mask { - type Output = Mask; - fn bitand(mut self, o: Mask) -> Mask { - for (a, b) in self.0.iter_mut().zip(o.0) { - *a &= b; - } - self - } -} - -impl std::ops::BitOr for Mask { - type Output = Mask; - fn bitor(mut self, o: Mask) -> Mask { - self |= o; - self - } -} - -impl std::ops::BitOrAssign for Mask { - fn bitor_assign(&mut self, o: Mask) { - for (a, b) in self.0.iter_mut().zip(o.0) { - *a |= b; - } - } -} - -/// One character class of a rule's condition. -#[derive(Clone, Debug, PartialEq, Eq)] -enum Cond { - Any, - Char(char), - Set { negated: bool, chars: Box<[char]> }, -} - -impl Cond { - fn matches(&self, c: char) -> bool { - match self { - Cond::Any => true, - Cond::Char(x) => *x == c, - Cond::Set { negated, chars } => chars.contains(&c) != *negated, - } - } -} - -/// Parse a Hunspell condition (`.`, `[^i]e`, `[km]anie`…). -fn parse_cond(text: &str) -> Result, String> { - if text == "." { - return Ok(Box::new([])); - } - let mut out = Vec::new(); - let mut chars = text.chars(); - while let Some(c) = chars.next() { - match c { - '.' => out.push(Cond::Any), - '[' => { - let mut set = Vec::new(); - let mut negated = false; - let mut closed = false; - let mut first = true; - for d in chars.by_ref() { - if d == ']' { - closed = true; - break; - } - if first && d == '^' { - negated = true; - } else { - set.push(d); - } - first = false; - } - if !closed { - return Err(format!("unclosed `[` in condition `{text}`")); - } - out.push(Cond::Set { negated, chars: set.into_boxed_slice() }); - } - ']' => return Err(format!("stray `]` in condition `{text}`")), - c => out.push(Cond::Char(c)), - } - } - Ok(out.into_boxed_slice()) -} - -/// How many suffixes a word may carry in a row through continuation classes (`work` + -/// `able/B` + `s` → `workables` is two). Hunspell strips at most two suffixes, so a third -/// chained suffix is not a word; the bound also keeps hostile input from walking deep recursions. -const MAX_CHAIN: u8 = 2; -/// How many suffixes may sit under the outermost one: the depth `form_has_flag` walks. -const INNER_CHAIN: u8 = MAX_CHAIN - 1; -/// Most forms [`AffixDict::forms`] lists for one stem (real stems have a few hundred at most). -pub const MAX_FORMS: usize = 4096; -/// Most rules [`AffixDict::forms`] tries along continuation chains and prefix–suffix cross -/// products for one stem: real dictionaries need a few thousand, hostile ones are cut off. -const MAX_FORMS_WORK: usize = 1 << 18; - -/// One prefix or suffix rule. -#[derive(Clone, Debug)] -struct Rule { - /// Bit of the rule's flag in the stems' flag masks. - bit: Mask, - flag: char, - cross: bool, - strip: Box, - add: Box, - /// Continuation classes (`SFX A 0 able/B`): flags the derived form carries, licensing - /// further affixes. Chained application is bounded by [`MAX_CHAIN`]. - cont: Mask, - cond: Box<[Cond]>, - cond_text: Box, -} - -impl Rule { - /// Does `root` end (suffix rule) or start (prefix rule) as the condition says? - fn cond_matches(&self, root: &str, suffix: bool) -> bool { - if self.cond.is_empty() { - return true; - } - if suffix { - let mut it = root.chars().rev(); - self.cond.iter().rev().all(|c| it.next().is_some_and(|x| c.matches(x))) - } else { - let mut it = root.chars(); - self.cond.iter().all(|c| it.next().is_some_and(|x| c.matches(x))) - } - } -} - -/// Suffix rules sharing an appended string and a stripped one: one stem lookup serves them all. -#[derive(Clone, Debug, Default)] -struct SfxGroup { - strip: Box, - rules: Vec, -} - -/// A stem list with affix rules (see the module docs). -#[derive(Clone, Debug, Default)] -pub struct AffixDict { - /// All stems, concatenated, sorted by bytes. - words: String, - offs: Vec, - /// Per stem: index into `sets`. - flags: Vec, - /// Distinct flag masks. - sets: Vec, - /// The `FORBIDDENWORD` flag's bit; stems carrying it are not words (0: none declared). - forbidden: Mask, - /// Stems with NOSUGGEST are correct, but none of their forms are suggested. - no_suggest: Mask, - /// Stems with SUBSTANDARD are correct too, and likewise never suggested. - substandard: Mask, - /// Open-addressing hash table of stem indices (`EMPTY` = free). - table: Vec, - /// Flag char of each bit. - flag_chars: Vec, - prefixes: Vec, - suffixes: Vec, - /// Appended string → groups of suffix rules by stripped string. - sfx_index: FxMap, Vec>, - /// Per flag bit: the suffix rules of that flag, in rule order. - sfx_by_flag: Vec>, - /// Longest appended suffix, in chars. - max_sfx_chars: usize, - try_chars: String, - key: String, - map: Vec>, - rep: Vec<(String, String)>, - /// The `KEEPCASE` flag's bit: such stems are words only in their own letter case (0: none). - keepcase: Mask, - /// `ICONV`: input conversions applied before a lookup, `(from, to)`. - iconv: Vec<(String, String)>, - /// `IGNORE`: characters removed from input words (and from the stems and appended texts). - ignore: Box<[char]>, - /// Provenance and licence lines, kept through [`AffixDict::to_bytes`]. - comments: Vec, -} - -fn hash_str(s: &str) -> u64 { - let mut h = FxHasher::default(); - h.write(s.as_bytes()); - h.finish() -} - -/// Letter case of a word. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Casing { - /// No capitals (`kot`). - Lower, - /// First letter capital, no other (`Kot`). - Initial, - /// Every cased letter a capital, at least two (`KOT`). - Upper, - /// Anything else (`McDonald`, `kOT`). - Mixed, -} - -/// The casing of `w`. -pub fn casing(w: &str) -> Casing { - let (mut upper, mut lower, mut first_upper) = (0usize, 0usize, false); - for (i, c) in w.chars().enumerate() { - if c.is_uppercase() { - upper += 1; - if i == 0 { - first_upper = true; - } - } else if c.is_lowercase() { - lower += 1; - } - } - if upper == 0 { - Casing::Lower - } else if upper == 1 && first_upper { - Casing::Initial - } else if lower == 0 { - Casing::Upper - } else { - Casing::Mixed - } -} - -/// `w` with its first letter a capital. -pub fn capitalize(w: &str) -> String { - let mut c = w.chars(); - match c.next() { - Some(f) => f.to_uppercase().chain(c).collect(), - None => String::new(), - } -} - -/// Parsed rules and tables, shared by the Hunspell reader and the compact format. -#[derive(Default)] -struct Rules { - prefixes: Vec, - suffixes: Vec, - try_chars: String, - key: String, - map: Vec>, - rep: Vec<(String, String)>, - /// The `FORBIDDENWORD` flag: stems carrying it are listed but never correct. - forbidden: Option, - no_suggest: Option, - substandard: Option, - /// The `KEEPCASE` flag. - keepcase: Option, - iconv: Vec<(String, String)>, - ignore: Vec, - /// `AF` flag aliases: alias `n` (1-based) stands for `aliases[n - 1]`. - aliases: Vec, - /// The `AF` table opened with its count line, so flag fields are alias numbers. A table - /// without one is rejected, as in Hunspell: its lines are ignored. - aliases_on: bool, -} - -impl Rules { - /// Flags as written in a `.dic` entry or a continuation class: the flag characters - /// themselves, or with an `AF` table the alias its leading digits number, as Hunspell reads - /// them (`2x` is alias 2). A field without digits or an undefined alias reads as no flags: - /// the stem is still a word, the rule still applies, without the flags (Hunspell warns). - fn flags<'a>(&'a self, written: &'a str) -> &'a str { - if !self.aliases_on { - return written; - } - let digits = written.bytes().take_while(u8::is_ascii_digit).count(); - written - .get(..digits) - .and_then(|d| d.parse::().ok()) - .and_then(|n| n.checked_sub(1)) - .and_then(|i| self.aliases.get(i)) - .map_or("", String::as_str) - } - - /// `w` without the `IGNORE` characters. - fn strip_ignored(&self, w: &str) -> String { - if self.ignore.is_empty() { w.to_string() } else { w.chars().filter(|c| !self.ignore.contains(c)).collect() } - } -} - -/// The fields of an `.aff`/`.dic` line: Hunspell separates them with spaces and tabs only, so a -/// flag byte read as U+0085 or U+00A0 (see [`crate::hunspell::decode`]) stays a flag. -fn split_fields(line: &str) -> impl Iterator { - line.split([' ', '\t']).filter(|f| !f.is_empty()) -} - -/// The one flag character a directive (`FORBIDDENWORD w`) names. -fn one_flag(arg: &str, dir: &str, n: usize) -> Result { - let mut it = arg.chars().filter(|&c| c != EMOJI_SELECTOR); - match (it.next(), it.next()) { - // Only the field separators of `split_fields` can't be a flag: a flag byte read as - // U+0085 or U+00A0 is one. - (Some(c), None) if !matches!(c, ' ' | '\t') => Ok(c), - _ => Err(format!("line {}: {dir} needs one flag character", n + 1)), - } -} - -/// A rule as parsed from text, before flags become bits: `(flag, cross, strip, add, cont, cond)`. -type RawRule = (char, bool, String, String, String, String); - -/// Most distinct `TRY` characters kept; a hostile `.aff` could otherwise make every suggestion -/// round cost time and memory proportional to the file size. -const MAX_TRY_CHARS: usize = 128; - -/// Keeps the first [`MAX_TRY_CHARS`] distinct characters of a `TRY` value, in order. -fn cap_try(s: &str) -> String { - let mut out = String::new(); - let mut n = 0; - for c in s.chars() { - if n >= MAX_TRY_CHARS { - break; - } - if !out.contains(c) { - out.push(c); - n += 1; - } - } - out -} - -/// `0` means empty in Hunspell's strip and append fields. -fn zero_empty(s: &str) -> String { - if s == "0" { String::new() } else { s.to_string() } -} - -/// U+FE0F VARIATION SELECTOR-16 asks for the emoji form of the character before it. A dictionary -/// that uses an emoji as a flag may write it after the flag (RLA-ES Spanish: `PFX ☎️`, `banco/S☎️`), -/// but it is not a flag of its own: it is dropped wherever flags are read. -const EMOJI_SELECTOR: char = '\u{fe0f}'; - -/// Read Hunspell `.aff` text (already decoded to UTF-8). -fn parse_aff(aff: &str) -> Result { - let mut r = Rules::default(); - let mut cross: HashMap<(bool, char), bool> = HashMap::new(); - let mut map_header = false; - let mut rep_header = false; - let mut af_header = false; - let mut iconv_header = false; - for (n, line) in aff.lines().enumerate() { - let f: Vec<&str> = split_fields(line).collect(); - let Some(&dir) = f.first() else { continue }; - if dir.starts_with('#') { - continue; - } - let at = |k: usize| f.get(k).copied().unwrap_or(""); - match dir { - "SET" | "LANG" | "NAME" | "VERSION" | "HOME" | "WORDCHARS" | "MAXNGRAMSUGS" | "MAXDIFF" | "ONLYMAXDIFF" | "NOSPLITSUGS" - | "SUGSWITHDOTS" | "WARN" | "FORBIDWARN" | "MAXCPDSUGS" => {} - // Morphological aliases, and a flag that only matters for morphological generation: - // the words it marks are correct. - "AM" | "LEMMA_PRESENT" => {} - "SUBSTANDARD" => r.substandard = Some(one_flag(at(1), dir, n)?), - // The first `AF` or `ICONV` line is the table's count, as in Hunspell, even with a - // trailing comment (`AF 1559 # aliases`). - "AF" => { - if !af_header { - af_header = true; - // A count below 1 rejects the table too: flags stay literal (`AF 0`). - r.aliases_on = at(1).parse::().is_ok_and(|n| n > 0); - } else if r.aliases_on { - r.aliases.push(at(1).to_string()); - } - } - "ICONV" => { - if !iconv_header && at(1).parse::().is_ok() { - iconv_header = true; - } else if f.len() >= 3 { - r.iconv.push((at(1).to_string(), at(2).to_string())); - } - } - "IGNORE" => r.ignore = at(1).chars().collect(), - "KEEPCASE" => r.keepcase = Some(one_flag(at(1), dir, n)?), - // Per-character flag modes (`FLAG UTF-8`, as VERO's pt_BR dictionary uses) work - // with the one-character parsing here; only the multi-character encodings differ. - "FLAG" if !matches!(at(1), "UTF-8" | "DEFAULT" | "utf-8" | "default" | "UTF8" | "") => { - return Err(format!("line {}: FLAG {} is not supported (one-character flags only)", n + 1, at(1))); - } - "FLAG" => {} - "FORBIDDENWORD" => r.forbidden = Some(one_flag(at(1), dir, n)?), - "NOSUGGEST" => r.no_suggest = Some(one_flag(at(1), dir, n)?), - "TRY" => r.try_chars = cap_try(at(1)), - "KEY" => r.key = at(1).to_string(), - "MAP" => { - if !map_header && at(1).parse::().is_ok() { - map_header = true; - } else if !at(1).contains('(') { - r.map.push(at(1).chars().collect()); - } - } - "REP" => { - if !rep_header && at(1).parse::().is_ok() { - rep_header = true; - } else if f.len() >= 3 { - r.rep.push((at(1).replace('_', " "), at(2).replace('_', " "))); - } - } - "PFX" | "SFX" => { - let suffix = dir == "SFX"; - let mut flag_chars = at(1).chars().filter(|&c| c != EMOJI_SELECTOR); - let (Some(flag), None) = (flag_chars.next(), flag_chars.next()) else { - return Err(format!("line {}: flag `{}` is not one character", n + 1, at(1))); - }; - // Only a header ignores trailing fields; a rule condition beginning with # is literal. - let is_header = f.len() >= 4 && matches!(at(2), "Y" | "N") && at(3).parse::().is_ok() && !cross.contains_key(&(suffix, flag)); - if is_header { - cross.insert((suffix, flag), at(2) == "Y"); - continue; - } - if f.len() < 4 { - return Err(format!("line {}: short affix rule", n + 1)); - } - // `able/B`: the appended text and its continuation classes. - let (add, cont) = match at(3).split_once('/') { - Some((a, c)) => (a, c), - None => (at(3), ""), - }; - let cond = if f.len() >= 5 { at(4) } else { "." }; - parse_cond(cond).map_err(|e| format!("line {}: {e}", n + 1))?; - let c = cross.get(&(suffix, flag)).copied().unwrap_or(false); - let rule: RawRule = (flag, c, zero_empty(at(2)), zero_empty(add), cont.to_string(), cond.to_string()); - if suffix { - r.suffixes.push(rule); - } else { - r.prefixes.push(rule); - } - } - d if UNSUPPORTED.contains(&d) => return Err(format!("line {}: {d} is not supported", n + 1)), - _ => {} - } - } - // Aliases may be declared after the rules that use them; resolve once all are read. - // Hunspell removes `IGNORE` characters from the appended text only: a stripped text that - // holds one never matches a stem (stems lose them too), so the rule never applies. - let resolve = |rules: &mut Vec, r: &Rules| { - for rule in rules.iter_mut() { - rule.4 = r.flags(&rule.4).to_string(); - rule.3 = r.strip_ignored(&rule.3); - } - }; - let (mut pfx, mut sfx) = (std::mem::take(&mut r.prefixes), std::mem::take(&mut r.suffixes)); - resolve(&mut pfx, &r); - resolve(&mut sfx, &r); - (r.prefixes, r.suffixes) = (pfx, sfx); - Ok(r) -} - -impl AffixDict { - /// Build from Hunspell `.aff` and `.dic` text (decoded to UTF-8; the `.dic` count line and - /// morphological fields are ignored). `comments` (provenance, licence) are kept with the data. - pub fn from_hunspell(aff: &str, dic: &str, comments: &[&str]) -> Result { - // Some dictionaries open with a UTF-8 BOM; it must not glue itself to the first - // directive or turn the `.dic` count line into a stem. - let aff = aff.strip_prefix('\u{feff}').unwrap_or(aff); - let dic = dic.strip_prefix('\u{feff}').unwrap_or(dic); - let rules = parse_aff(aff)?; - let mut stems: Vec<(String, String)> = Vec::new(); - for (n, line) in dic.lines().enumerate() { - // The entry may hold spaces (`ad hoc/B\tpo:adv`); it ends where Hunspell ends it. - // Leading blanks belong to it, as in Hunspell (` lab` is no `lab`). - let tok = line.get(..crate::hunspell::dic_entry_end(line.as_bytes())).unwrap_or_default(); - if tok.is_empty() { - continue; - } - if n == 0 && tok.parse::().is_ok() { - continue; - } - // `\/` is a literal slash in a word; the first plain `/` starts the flags. - let mut word = String::new(); - let mut flags = String::new(); - let mut chars = tok.chars().peekable(); - let mut in_flags = false; - while let Some(c) = chars.next() { - if in_flags { - if c != EMOJI_SELECTOR { - flags.push(c); - } - } else if c == '\\' && chars.peek() == Some(&'/') { - word.push('/'); - chars.next(); - } else if c == '/' { - in_flags = true; - } else { - word.push(c); - } - } - let word = rules.strip_ignored(&word); - if word.is_empty() || word.len() > MAX_WORD_BYTES || word.contains(['\n', '/']) { - continue; - } - let flags = rules.flags(&flags).to_string(); - stems.push((word, flags)); - } - AffixDict::build(rules, stems, comments.iter().map(ToString::to_string).collect()) - } - - fn build(rules: Rules, mut stems: Vec<(String, String)>, comments: Vec) -> Result { - // Flags: every one a rule or a stem uses gets a bit. - let mut flag_chars: Vec = rules.prefixes.iter().chain(&rules.suffixes).map(|r| r.0).collect(); - flag_chars.extend(stems.iter().flat_map(|s| s.1.chars())); - flag_chars.sort_unstable(); - flag_chars.dedup(); - if flag_chars.len() > MAX_FLAGS { - return Err(format!("{} distinct flags; at most {MAX_FLAGS} are supported", flag_chars.len())); - } - let bit_of = |c: char| flag_chars.iter().position(|&x| x == c).map_or(Mask::EMPTY, Mask::bit); - let forbidden = rules.forbidden.map_or(Mask::EMPTY, bit_of); - let keepcase = rules.keepcase.map_or(Mask::EMPTY, bit_of); - // Stems sorted by bytes; homonyms (the same word twice) are merged. - stems.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes())); - let mut d = AffixDict { - words: String::with_capacity(stems.iter().map(|s| s.0.len()).sum()), - offs: Vec::with_capacity(stems.len() + 1), - flag_chars: flag_chars.clone(), - forbidden, - no_suggest: rules.no_suggest.map_or(Mask::EMPTY, bit_of), - substandard: rules.substandard.map_or(Mask::EMPTY, bit_of), - keepcase, - iconv: rules.iconv, - ignore: rules.ignore.into_boxed_slice(), - try_chars: rules.try_chars, - key: rules.key, - map: rules.map, - rep: rules.rep, - comments, - ..AffixDict::default() - }; - d.offs.push(0); - let mut set_ids: HashMap = HashMap::new(); - let mut i = 0; - while i < stems.len() { - let Some((word, _)) = stems.get(i) else { break }; - let mut mask = Mask::EMPTY; - let mut j = i; - while let Some((w, f)) = stems.get(j) - && w == word - { - mask |= f.chars().map(bit_of).fold(Mask::EMPTY, |a, b| a | b); - j += 1; - } - let next_id = u16::try_from(d.sets.len()).map_err(|_| "too many distinct flag sets".to_string())?; - let id = *set_ids.entry(mask).or_insert(next_id); - if usize::from(id) == d.sets.len() { - d.sets.push(mask); - } - d.words.push_str(word); - d.offs.push(u32::try_from(d.words.len()).map_err(|_| "stems too large".to_string())?); - d.flags.push(id); - i = j; - } - let mk = |(flag, cross, strip, add, cont, cond): RawRule| -> Result { - let cont = cont.chars().fold(Mask::EMPTY, |m, c| m | bit_of(c)); - Ok(Rule { bit: bit_of(flag), flag, cross, strip: strip.into(), add: add.into(), cont, cond: parse_cond(&cond)?, cond_text: cond.into() }) - }; - d.prefixes = rules.prefixes.into_iter().map(mk).collect::>()?; - d.suffixes = rules.suffixes.into_iter().map(mk).collect::>()?; - d.index(); - Ok(d) - } - - /// Build the suffix index and the stem hash table. - fn index(&mut self) { - let mut idx: FxMap, Vec> = FxMap::default(); - // Each (appended, stripped) pair's group, found by hash rather than by scanning the - // appended string's groups: a dictionary with many strips per suffix stays linear. - let mut group_of: FxMap<(&str, &str), usize> = FxMap::default(); - let mut by_flag: Vec> = vec![Vec::new(); MAX_FLAGS]; - for (i, r) in self.suffixes.iter().enumerate() { - let groups = idx.entry(r.add.clone()).or_default(); - let Ok(i) = u32::try_from(i) else { break }; - match group_of.get(&(&*r.add, &*r.strip)).and_then(|&g| groups.get_mut(g)) { - Some(g) => g.rules.push(i), - None => { - group_of.insert((&r.add, &r.strip), groups.len()); - groups.push(SfxGroup { strip: r.strip.clone(), rules: vec![i] }); - } - } - if let Some(list) = r.bit.first().and_then(|b| by_flag.get_mut(b)) { - list.push(i); - } - } - self.sfx_by_flag = by_flag; - self.max_sfx_chars = self.suffixes.iter().map(|r| r.add.chars().count()).max().unwrap_or(0); - self.sfx_index = idx; - let n = self.len(); - let size = (n.max(1) * 2).next_power_of_two(); - let mut table = vec![EMPTY; size]; - let mask = size - 1; - for i in 0..n { - let Some(w) = self.word(i) else { continue }; - let mut slot = hash_str(w) as usize & mask; - while table.get(slot).is_some_and(|&t| t != EMPTY) { - slot = (slot + 1) & mask; - } - if let (Some(t), Ok(i)) = (table.get_mut(slot), u32::try_from(i)) { - *t = i; - } - } - self.table = table; - } - - /// Number of stems. - pub fn len(&self) -> usize { - self.flags.len() - } - - pub fn is_empty(&self) -> bool { - self.flags.is_empty() - } - - /// Number of prefix and suffix rules. - pub fn rule_count(&self) -> usize { - self.prefixes.len() + self.suffixes.len() - } - - fn word(&self, i: usize) -> Option<&str> { - let a = *self.offs.get(i)? as usize; - let b = *self.offs.get(i + 1)? as usize; - self.words.get(a..b) - } - - /// The flag characters of `mask`, in flag order (how the compact format stores flags). - fn flag_chars_for(&self, mask: Mask) -> String { - self.flag_chars.iter().enumerate().filter(|&(i, _)| mask.meets(Mask::bit(i))).map(|(_, c)| c).collect() - } - - /// The character of `mask` when it holds exactly one flag. - fn flag_char(&self, mask: Mask) -> Option { - let s = self.flag_chars_for(mask); - let mut it = s.chars(); - match (it.next(), it.next()) { - (Some(c), None) => Some(c), - _ => None, - } - } - - /// The stems, in byte order. - pub fn stems(&self) -> impl Iterator + '_ { - (0..self.len()).filter_map(|i| self.word(i)) - } - - /// The stems starting with `prefix`, in byte order (a binary search, then a scan). - pub fn stems_with_prefix<'a>(&'a self, prefix: &str) -> impl Iterator + use<'a> { - let prefix = prefix.to_string(); - let first = { - let (mut lo, mut hi) = (0usize, self.len()); - while lo < hi { - let mid = lo + (hi - lo) / 2; - if self.word(mid).is_some_and(|w| w.as_bytes() < prefix.as_bytes()) { - lo = mid + 1; - } else { - hi = mid; - } - } - lo - }; - (first..self.len()).map_while(move |i| self.word(i).filter(|w| w.starts_with(prefix.as_str()))) - } - - /// Flag mask of a stem, if `w` is one. - fn stem_flags(&self, w: &str) -> Option { - self.stem_flags_except(w, Mask::EMPTY) - } - - /// Flag mask of a stem, if `w` is one and carries none of the flags in `deny`. - fn stem_flags_except(&self, w: &str, deny: Mask) -> Option { - if self.table.is_empty() { - return None; - } - let mask = self.table.len() - 1; - let mut slot = hash_str(w) as usize & mask; - for _ in 0..self.table.len() { - let t = *self.table.get(slot)?; - if t == EMPTY { - return None; - } - if self.word(t as usize) == Some(w) { - let id = *self.flags.get(t as usize)?; - let mask = self.sets.get(usize::from(id)).copied(); - // A stem carrying FORBIDDENWORD is listed but is not a word: no direct hit, - // and (this is the only stem lookup) no affixed form is built on it either. - return mask.filter(|m| !m.meets(self.forbidden | deny)); - } - slot = (slot + 1) & mask; - } - None - } - - /// Does `w` carry any flag of `flags` — as a stem, or as the form of a suffix rule whose - /// continuation classes include one (chained suffixation, depth-bounded)? - /// - /// The rules of a group that could lead to `w` are merged into one mask of their flags and - /// followed once, not once per rule: the answer is the same, but thousands of rules of one - /// flag cost one walk, not one each (#3613). - fn form_has_flag(&self, w: &str, flags: Mask, depth: u8, deny: Mask) -> bool { - if self.stem_flags_except(w, deny).is_some_and(|f| f.meets(flags)) { - return true; - } - if depth == 0 { - return false; - } - let mut root = String::with_capacity(w.len() + 8); - let cuts = std::iter::once(w.len()).chain(w.char_indices().rev().map(|(i, _)| i).filter(|&i| i > 0)); - for b in cuts.take(self.max_sfx_chars + 1) { - let (Some(base), Some(add)) = (w.get(..b), w.get(b..)) else { continue }; - let Some(groups) = self.sfx_index.get(add) else { continue }; - for g in groups { - root.clear(); - root.push_str(base); - root.push_str(&g.strip); - let inner = g - .rules - .iter() - .filter_map(|&i| self.suffixes.get(i as usize)) - .filter(|r| r.cont.meets(flags) && r.cond_matches(&root, true)) - .fold(Mask::EMPTY, |m, r| m | r.bit); - if !inner.is_empty() && self.form_has_flag(&root, inner, depth - 1, deny) { - return true; - } - } - } - false - } - - /// Is `w` exactly (case and all) a stem or an affixed form of one? `ICONV` and `IGNORE` - /// apply first. - pub fn contains(&self, w: &str) -> bool { - self.contains_except(&self.normalize(w), Mask::EMPTY) - } - - /// Exactly spelled dictionary words eligible for suggestions (NOSUGGEST and SUBSTANDARD - /// stems excluded). - pub fn contains_suggestible(&self, w: &str) -> bool { - self.contains_except(&self.normalize(w), self.no_suggest | self.substandard) - } - - /// `w` after the `ICONV` conversions (at each position the longest match wins) and without - /// the `IGNORE` characters. - fn normalize<'a>(&self, w: &'a str) -> std::borrow::Cow<'a, str> { - if self.iconv.is_empty() && self.ignore.is_empty() { - return std::borrow::Cow::Borrowed(w); - } - // One pass: the input is copied only from the first change on. - let mut out: Option = None; - let mut rest = w; - while let Some(c) = rest.chars().next() { - let done = w.get(..w.len() - rest.len()).unwrap_or_default(); - let hit = self.iconv.iter().filter(|(from, _)| !from.is_empty() && rest.starts_with(from.as_str())).max_by_key(|(from, _)| from.len()); - match hit { - Some((from, to)) => { - out.get_or_insert_with(|| done.to_string()).extend(to.chars().filter(|c| !self.ignore.contains(c))); - rest = rest.get(from.len()..).unwrap_or_default(); - } - None => { - if self.ignore.contains(&c) { - out.get_or_insert_with(|| done.to_string()); - } else if let Some(o) = out.as_mut() { - o.push(c); - } - rest = rest.get(c.len_utf8()..).unwrap_or_default(); - } - } - } - out.map_or(std::borrow::Cow::Borrowed(w), std::borrow::Cow::Owned) - } - - /// [`AffixDict::contains`] on normalized input, where stems carrying a flag in `deny` don't - /// count (case variants of `KEEPCASE` stems). - fn contains_except(&self, w: &str, deny: Mask) -> bool { - if w.is_empty() || w.len() > MAX_WORD_BYTES { - return false; - } - if self.stem_flags_except(w, deny).is_some() || self.suffixed(w, Mask::EMPTY, deny) { - return true; - } - let mut root = String::with_capacity(w.len() + 8); - for r in &self.prefixes { - let Some(rest) = w.strip_prefix(&*r.add) else { continue }; - if rest.is_empty() { - continue; - } - root.clear(); - root.push_str(&r.strip); - root.push_str(rest); - if !r.cond_matches(&root, false) { - continue; - } - if self.stem_flags_except(&root, deny).is_some_and(|f| f.meets(r.bit)) { - return true; - } - if self.form_has_flag(&root, r.bit, INNER_CHAIN, deny) { - return true; - } - if r.cross && self.suffixed(&root, r.bit, deny) { - return true; - } - } - false - } - - /// Is `w` a stem with one suffix? With `need` set (a prefix was taken off), the stem must - /// carry those flags too and the suffix must allow cross products. - fn suffixed(&self, w: &str, need: Mask, deny: Mask) -> bool { - let mut root = String::with_capacity(w.len() + 8); - // Each split point from the end: the appended string is `w[b..]`, at most the longest - // suffix, and something must stay before it. - let cuts = std::iter::once(w.len()).chain(w.char_indices().rev().map(|(i, _)| i).filter(|&i| i > 0)); - for b in cuts.take(self.max_sfx_chars + 1) { - let (Some(base), Some(add)) = (w.get(..b), w.get(b..)) else { continue }; - let Some(groups) = self.sfx_index.get(add) else { continue }; - for g in groups { - root.clear(); - root.push_str(base); - root.push_str(&g.strip); - let rules = || g.rules.iter().filter_map(|&i| self.suffixes.get(i as usize)); - // The stem path: `root` is a stem carrying the rule's flag (and the prefix's, - // when a cross product is being checked). - if let Some(f) = self.stem_flags_except(&root, deny) - && f.covers(need) - && rules().any(|r| f.meets(r.bit) && (need.is_empty() || r.cross) && r.cond_matches(&root, true)) - { - return true; - } - // Continuation classes: `root` need not be a stem carrying this rule's flag — - // it may be a form that gained the flag from another rule's continuation. The - // matching rules' flags are looked for together, in one walk. - if need.is_empty() { - let flags = rules().filter(|r| r.cond_matches(&root, true)).fold(Mask::EMPTY, |m, r| m | r.bit); - if !flags.is_empty() && self.form_has_flag(&root, flags, INNER_CHAIN, deny) { - return true; - } - } - } - } - false - } - - /// Is `w` correct, Hunspell style: as written, or — for a capitalized word or one in capitals - /// — in lower case (and capitalized), since a sentence may start with any word and headings - /// may be in capitals. Lower-case input never matches a capitalized stem (`warszawa`), and a - /// `KEEPCASE` stem matches only as written. - pub fn check(&self, w: &str) -> bool { - self.check_except(w, Mask::EMPTY) - } - - /// Whether a suggestion is correct in its displayed case and has an eligible stem. - pub fn can_suggest(&self, w: &str) -> bool { - self.check_except(w, self.no_suggest | self.substandard) - } - - /// Whether `w` is a word only through a `KEEPCASE` stem, so it is correct in this letter case - /// alone (`km` from `km/K`, which `Km` and `KM` are not). - pub(crate) fn case_locked(&self, w: &str) -> bool { - self.keepcase != Mask::EMPTY && self.contains(w) && !self.contains_except(&self.normalize(w), self.keepcase) - } - - fn check_except(&self, w: &str, deny: Mask) -> bool { - let w = self.normalize(w); - if self.contains_except(&w, deny) { - return true; - } - let deny = deny | self.keepcase; - match casing(&w) { - Casing::Lower | Casing::Mixed => false, - Casing::Initial => self.contains_except(&w.to_lowercase(), deny), - Casing::Upper => { - let lower = w.to_lowercase(); - self.contains_except(&capitalize(&lower), deny) || self.contains_except(&lower, deny) - } - } - } - - /// Every form of the stem `stem` (itself included), unsorted, possibly with repeats; at most - /// [`MAX_FORMS`], and at most [`MAX_FORMS_WORK`] rules tried along the continuation chains and - /// cross products, so a dictionary with thousands of self-continuing rules can't make one stem - /// cost millions of strings (#3613). - pub fn forms(&self, stem: &str) -> Vec { - let Some(f) = self.stem_flags(stem) else { return Vec::new() }; - let mut out = vec![stem.to_string()]; - let mut cross_sfx = Vec::new(); - // Suffix forms queued with the continuation classes they carry, for chained expansion; - // each (form, classes) pair is expanded once. - let mut chain: Vec<(String, Mask)> = Vec::new(); - let mut queued: std::collections::HashSet<(String, Mask)> = std::collections::HashSet::new(); - let mut work = 0usize; - for r in &self.suffixes { - if !f.meets(r.bit) || !r.cond_matches(stem, true) { - continue; - } - let Some(base) = stem.strip_suffix(&*r.strip) else { continue }; - let form = format!("{base}{}", r.add); - if r.cross { - cross_sfx.push(form.clone()); - } - if !r.cont.is_empty() && queued.insert((form.clone(), r.cont)) { - chain.push((form.clone(), r.cont)); - } - out.push(form); - if out.len() >= MAX_FORMS { - return out; - } - } - // Continuation classes chain suffixes (`workable/B` → `workables`), up to [`MAX_CHAIN`] - // suffixes in all — the same bound the checker walks. - 'chain: for _ in 0..INNER_CHAIN { - let level = std::mem::take(&mut chain); - if level.is_empty() { - break; - } - for (form, flags) in level { - for r in self.suffixes_with(flags).into_iter().filter_map(|i| self.suffixes.get(i as usize)) { - work += 1; - if work > MAX_FORMS_WORK { - break 'chain; - } - if !r.cond_matches(&form, true) { - continue; - } - let Some(base) = form.strip_suffix(&*r.strip) else { continue }; - let next = format!("{base}{}", r.add); - if !r.cont.is_empty() && queued.insert((next.clone(), r.cont)) { - chain.push((next.clone(), r.cont)); - } - out.push(next); - if out.len() >= MAX_FORMS { - return out; - } - } - } - } - for r in &self.prefixes { - if !f.meets(r.bit) || !r.cond_matches(stem, false) { - continue; - } - let Some(rest) = stem.strip_prefix(&*r.strip) else { continue }; - out.push(format!("{}{rest}", r.add)); - if r.cross { - for s in &cross_sfx { - work += 1; - if out.len() >= MAX_FORMS || work > MAX_FORMS_WORK { - return out; - } - if let Some(rest) = s.strip_prefix(&*r.strip) { - out.push(format!("{}{rest}", r.add)); - } - } - } - if out.len() >= MAX_FORMS { - return out; - } - } - out - } - - /// The suffix rules carrying any flag of `mask`, in rule order. - fn suffixes_with(&self, mask: Mask) -> Vec { - let mut rules: Vec = Vec::new(); - let mut lists = 0; - for (bit, list) in self.sfx_by_flag.iter().enumerate() { - if mask.meets(Mask::bit(bit)) && !list.is_empty() { - rules.extend_from_slice(list); - lists += 1; - } - } - if lists > 1 { - rules.sort_unstable(); - } - rules - } - - /// The `TRY` letters, most frequent first (suggestions try these). - pub fn try_chars(&self) -> &str { - &self.try_chars - } - - /// The `KEY` keyboard rows, separated by `|` (adjacent letters are likely mistypes). - pub fn key(&self) -> &str { - &self.key - } - - /// The `MAP` groups: letters often confused with each other (`aą`, `zżź`, `oóu`…). - pub fn map(&self) -> &[Vec] { - &self.map - } - - /// The `REP` table: common misspellings, `(wrong, right)`. - pub fn rep(&self) -> &[(String, String)] { - &self.rep - } - - /// The provenance and licence lines. - pub fn comments(&self) -> &[String] { - &self.comments - } - - /// Serialize to WordCraft's compact format: raw-deflate of `WCAFF1`, the comments, the - /// tables and rules as text lines, a `WORDS` line, then one line per stem, front-coded - /// against the previous one (a byte with the shared prefix length in bytes, the rest of the - /// word, `/` and its flags if any). - pub fn to_bytes(&self) -> Vec { - let mut out = String::new(); - out.push_str(MAGIC); - out.push('\n'); - for c in &self.comments { - out.push_str("# "); - out.push_str(c.trim()); - out.push('\n'); - } - if !self.try_chars.is_empty() { - out.push_str(&format!("TRY {}\n", self.try_chars)); - } - if !self.key.is_empty() { - out.push_str(&format!("KEY {}\n", self.key)); - } - for m in &self.map { - out.push_str(&format!("MAP {}\n", m.iter().collect::())); - } - for (a, b) in &self.rep { - out.push_str(&format!("REP {} {}\n", a.replace(' ', "_"), b.replace(' ', "_"))); - } - for (directive, mask) in - [("FORBIDDENWORD", self.forbidden), ("NOSUGGEST", self.no_suggest), ("SUBSTANDARD", self.substandard), ("KEEPCASE", self.keepcase)] - { - if !mask.is_empty() - && let Some(c) = self.flag_char(mask) - { - out.push_str(&format!("{directive} {c}\n")); - } - } - for (a, b) in &self.iconv { - out.push_str(&format!("ICONV {a} {b}\n")); - } - if !self.ignore.is_empty() { - out.push_str(&format!("IGNORE {}\n", self.ignore.iter().collect::())); - } - let z = |s: &str| if s.is_empty() { "0".to_string() } else { s.to_string() }; - for (kind, rules) in [("PFX", &self.prefixes), ("SFX", &self.suffixes)] { - for r in rules { - let y = if r.cross { "Y" } else { "N" }; - // Continuation classes ride on the appended field, as in the `.aff` source. - let mut add = z(&r.add); - let cont = self.flag_chars_for(r.cont); - if !cont.is_empty() { - add.push('/'); - add.push_str(&cont); - } - out.push_str(&format!("{kind} {} {y} {} {} {}\n", r.flag, z(&r.strip), add, r.cond_text)); - } - } - out.push_str("WORDS\n"); - let mut raw = out.into_bytes(); - let mut prev: &[u8] = &[]; - for i in 0..self.len() { - let Some(w) = self.word(i) else { continue }; - let b = w.as_bytes(); - let p = prev.iter().zip(b).take_while(|(x, y)| x == y).count().min(255); - raw.push(p as u8); - raw.extend_from_slice(b.get(p..).unwrap_or_default()); - let mask = self.flags.get(i).and_then(|&id| self.sets.get(usize::from(id))).copied().unwrap_or_default(); - if !mask.is_empty() { - raw.push(b'/'); - raw.extend_from_slice(self.flag_chars_for(mask).as_bytes()); - } - raw.push(b'\n'); - prev = b; - } - miniz_oxide::deflate::compress_to_vec(&raw, 10) - } - - /// Read data written by [`AffixDict::to_bytes`]. - pub fn from_bytes(data: &[u8]) -> Result { - let raw = miniz_oxide::inflate::decompress_to_vec_with_limit(data, 64 << 20) - .map_err(|e| format!("affix dictionary: inflate failed: {:?}", e.status))?; - let words_at = raw.windows(7).position(|w| w == b"\nWORDS\n").ok_or("affix dictionary: no WORDS section")?; - let head = std::str::from_utf8(raw.get(..words_at).unwrap_or_default()).map_err(|_| "affix dictionary: bad utf-8 in the rules")?; - let mut lines = head.lines(); - if lines.next() != Some(MAGIC) { - return Err("affix dictionary: bad header".into()); - } - let mut rules = Rules::default(); - let mut comments = Vec::new(); - for line in lines { - if let Some(c) = line.strip_prefix("# ") { - comments.push(c.to_string()); - continue; - } - let f: Vec<&str> = split_fields(line).collect(); - let at = |k: usize| f.get(k).copied().unwrap_or(""); - match at(0) { - "TRY" => rules.try_chars = cap_try(at(1)), - "KEY" => rules.key = at(1).to_string(), - directive @ ("FORBIDDENWORD" | "NOSUGGEST" | "SUBSTANDARD" | "KEEPCASE") => { - let c = one_flag(at(1), directive, 0).map_err(|_| format!("affix dictionary: bad {directive} in `{line}`"))?; - match directive { - "FORBIDDENWORD" => rules.forbidden = Some(c), - "NOSUGGEST" => rules.no_suggest = Some(c), - "SUBSTANDARD" => rules.substandard = Some(c), - _ => rules.keepcase = Some(c), - } - } - "ICONV" if f.len() == 3 => rules.iconv.push((at(1).to_string(), at(2).to_string())), - "IGNORE" => rules.ignore = at(1).chars().collect(), - "MAP" => rules.map.push(at(1).chars().collect()), - "REP" => rules.rep.push((at(1).replace('_', " "), at(2).replace('_', " "))), - kind @ ("PFX" | "SFX") => { - let mut fc = at(1).chars(); - let (Some(flag), None) = (fc.next(), fc.next()) else { return Err(format!("affix dictionary: bad flag in `{line}`")) }; - if f.len() != 6 { - return Err(format!("affix dictionary: bad rule `{line}`")); - } - // Continuation classes ride on the appended field, as in the `.aff` source. - let (add, cont) = match at(4).split_once('/') { - Some((a, c)) => (zero_empty(a), c.to_string()), - None => (zero_empty(at(4)), String::new()), - }; - let rule: RawRule = (flag, at(2) == "Y", zero_empty(at(3)), add, cont, at(5).to_string()); - if kind == "SFX" { - rules.suffixes.push(rule); - } else { - rules.prefixes.push(rule); - } - } - "" => {} - other => return Err(format!("affix dictionary: unknown line `{other}`")), - } - } - let body = raw.get(words_at + 7..).unwrap_or_default(); - let mut stems: Vec<(String, String)> = Vec::new(); - let mut cur: Vec = Vec::new(); - let mut i = 0; - while let Some(&p) = body.get(i) { - let rest = body.get(i + 1..).unwrap_or_default(); - let len = rest.iter().position(|&b| b == b'\n').ok_or("affix dictionary: truncated")?; - let line = rest.get(..len).unwrap_or_default(); - i += len + 2; - let p = p as usize; - if p > cur.len() { - return Err("affix dictionary: bad prefix".into()); - } - let (tail, flags) = match line.iter().position(|&b| b == b'/') { - Some(k) => (line.get(..k).unwrap_or_default(), line.get(k + 1..).unwrap_or_default()), - None => (line, &[][..]), - }; - cur.truncate(p); - cur.extend_from_slice(tail); - let word = std::str::from_utf8(&cur).map_err(|_| "affix dictionary: bad utf-8 in a stem")?; - let flags = std::str::from_utf8(flags).map_err(|_| "affix dictionary: bad utf-8 in flags")?; - if word.is_empty() || word.len() > MAX_WORD_BYTES { - return Err("affix dictionary: bad stem".into()); - } - stems.push((word.to_string(), flags.to_string())); - } - AffixDict::build(rules, stems, comments) - } -} - -#[cfg(test)] -pub(crate) mod tests { - use super::*; - - const AFF: &str = "SET UTF-8\nTRY aeoiułkr\n\nPFX b Y 1\nPFX b 0 nie .\n\nSFX a Y 3\nSFX a 0 y [^y]\nSFX a ek ka ek\nSFX a 0 em .\n\nSFX c N 1\nSFX c a ą a\n\nMAP 2\nMAP aą\nMAP oóu\nREP 2\nREP ż rz\nREP u ó\n"; - const DIC: &str = "4\nkot/ab\npiesek/a\nkrowa/c\nWarszawa/c\n"; - - fn dict() -> AffixDict { - AffixDict::from_hunspell(AFF, DIC, &["test data"]).unwrap() - } - - #[test] - fn affix_headers_allow_comments_but_rule_conditions_stay_literal() { - let aff = "SET UTF-8\nPFX X Y 1 # prefix comment\nPFX X 0 ne . # rule comment\nSFX y Y 1 # suffix comment\nSFX y 0 y . # rule comment\n"; - let d = AffixDict::from_hunspell(aff, "1\nstrom/Xy\n", &[]).unwrap(); - assert_eq!(d.rule_count(), 2, "headers must not become affix rules"); - for w in ["strom", "stromy", "nestrom", "nestromy"] { - assert!(d.contains(w), "{w}"); - } - for w in ["stromY1", "stromN1", "X", "y"] { - assert!(!d.contains(w), "{w}"); - } - let hash = AffixDict::from_hunspell("SFX H Y 1\nSFX H 0 # .\nTRY a#b\nKEY a#b\n", "1\nstrom/H\n", &[]).unwrap(); - assert!(hash.contains("strom#"), "a hash in an affix field is literal"); - let literal = AffixDict::from_hunspell( - "PFX P Y 1 # header\nPFX P 0 pre #\nSFX S Y 1 # header\nSFX S 0 post #\n", - "3\n#stem/P\nstem#/S\nstem/PS\n", - &[], - ) - .unwrap(); - assert!(literal.contains("pre#stem") && literal.contains("stem#post")); - assert!(!literal.contains("prestem") && !literal.contains("stempost"), "# must remain a literal condition"); - - assert_eq!(hash.try_chars(), "a#b"); - assert_eq!(hash.key(), "a#b"); - assert_eq!(AffixDict::from_bytes(&hash.to_bytes()).unwrap().key(), "a#b"); - } - - #[test] - fn try_alphabet_is_capped_and_deduplicated() { - let aff = format!("TRY {}\n", "abcdefghijklmnopqrstuvwxyz".repeat(10_000)); - let d = AffixDict::from_hunspell(&aff, "1\nkot\n", &[]).unwrap(); - assert_eq!(d.try_chars(), "abcdefghijklmnopqrstuvwxyz"); - let compact = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert_eq!(compact.try_chars(), "abcdefghijklmnopqrstuvwxyz"); - - // More distinct characters than the cap, each repeated: keep the first 128 in order. - let many: String = (0x100u32..0x100 + 300).filter_map(char::from_u32).collect(); - let aff = format!("TRY {many}{many}\n"); - let want: String = many.chars().take(128).collect(); - let d = AffixDict::from_hunspell(&aff, "1\nkot\n", &[]).unwrap(); - assert_eq!(d.try_chars(), want); - let compact = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert_eq!(compact.try_chars(), want); - - // An oversized TRY line fed straight to the compact reader is capped as well. - let raw = format!("{MAGIC}\nTRY {many}{many}\nWORDS\n\0kot\n"); - let bytes = miniz_oxide::deflate::compress_to_vec(raw.as_bytes(), 6); - assert_eq!(AffixDict::from_bytes(&bytes).unwrap().try_chars(), want); - } - - /// Shared hand-written fixture: collectors must reject every path to NOSUGGEST forms. - pub(crate) fn assert_nosuggest_is_honoured(suggest: impl Fn(&AffixDict, &str, usize) -> Vec) { - let aff = "SET UTF-8\nNOSUGGEST N\nFORBIDDENWORD F\nTRY abeklmnostxy\nMAP 1\nMAP ae\nKEY ak\nREP 2\nREP xy toka\nREP zz normal\nPFX X Y 1\nPFX X 0 pre .\nSFX A Y 1\nSFX A 0 ble/B .\nSFX B Y 1\nSFX B 0 s .\n"; - let d = AffixDict::from_hunspell(aff, "4\ntoka/NXA\nnormal/XA\nTkname/N\nbanned/F\n", &[]).unwrap(); - for word in d.forms("toka").into_iter().chain(["TOKA".into(), "Toka".into(), "Tkname".into(), "TKNAME".into()]) { - assert!(d.check(&word), "NOSUGGEST is still correct: {word}"); - assert!(!d.can_suggest(&word), "NOSUGGEST form: {word}"); - } - for input in ["tokax", "tokablesx", "pretokax", "pretokablesx", "toka", "TOKA", "tkname", "teka", "xy", "normaltoka", "toak", "taak"] { - let results = suggest(&d, input, 32); - assert!(results.iter().all(|word| word.split(' ').all(|part| d.can_suggest(part))), "{input}: {results:?}"); - assert!(results.iter().all(|word| !word.to_lowercase().contains("toka") && word.to_lowercase() != "tkname"), "{input}: {results:?}"); - } - assert!(suggest(&d, "norrmal", 8).contains(&"normal".into())); - assert!(suggest(&d, "zz", 8).contains(&"normal".into())); - // Filtering happens before display casing: ordinary mixed-case stems retain the - // existing all-caps suggestion behaviour, with and without a NOSUGGEST declaration. - for aff in ["", "NOSUGGEST N\n"] { - let ordinary = AffixDict::from_hunspell(aff, "2\nmixCase\nhidden/N\n", &[]).unwrap(); - assert!(suggest(&ordinary, "MIXCASX", 8).contains(&"MIXCASE".into())); - } - } - - #[test] - fn nosuggest_is_correct_but_not_suggestible_and_survives_compact_format() { - let d = AffixDict::from_hunspell("NOSUGGEST N\nSFX S Y 1\nSFX S 0 s .\n", "2\nsecret/NS\nnormal/S\n", &[]).unwrap(); - for dict in [&d, &AffixDict::from_bytes(&d.to_bytes()).unwrap()] { - for word in ["secret", "secrets", "Secret", "SECRETS"] { - assert!(dict.check(word), "{word}"); - assert!(!dict.can_suggest(word), "{word}"); - } - assert!(dict.contains_suggestible("normal") && dict.can_suggest("NORMALS")); - } - assert!(AffixDict::from_hunspell("NOSUGGEST NN\n", "1\nword\n", &[]).is_err()); - } - - #[test] - fn stems_suffixes_prefixes_and_cross_products() { - let d = dict(); - assert_eq!(d.len(), 4); - for w in ["kot", "koty", "kotem", "piesek", "pieska", "piesekem", "krowa", "krową", "niekot", "niekoty", "niekotem", "Warszawa", "Warszawą"] - { - assert!(d.contains(w), "{w}"); - } - for w in ["kotą", "pieseka", "pieskem", "niepiesek", "niekrową", "krowy", "warszawa", "", "nie", "y", "ka"] { - assert!(!d.contains(w), "{w}"); - } - } - - #[test] - fn case_variants() { - let d = dict(); - assert!(d.check("Kot") && d.check("KOT") && d.check("Koty") && d.check("WARSZAWĄ") && d.check("Warszawą")); - assert!(!d.check("warszawa") && !d.check("kOT") && !d.check("KoT")); - assert_eq!(casing("Kot"), Casing::Initial); - assert_eq!(casing("KOT"), Casing::Upper); - assert_eq!(casing("K"), Casing::Initial); - assert_eq!(casing("kot"), Casing::Lower); - assert_eq!(casing("McDonald"), Casing::Mixed); - assert_eq!(capitalize("żółw"), "Żółw"); - } - - #[test] - fn forms_are_all_accepted() { - let d = dict(); - let mut forms = d.forms("kot"); - forms.sort(); - forms.dedup(); - assert_eq!(forms, ["kot", "kotem", "koty", "niekot", "niekotem", "niekoty"]); - for stem in d.stems().map(str::to_string).collect::>() { - for f in d.forms(&stem) { - assert!(d.contains(&f), "{stem} → {f}"); - } - } - } - - #[test] - fn compact_format_round_trips() { - let d = dict(); - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert_eq!(back.len(), d.len()); - assert_eq!(back.rule_count(), d.rule_count()); - assert_eq!(back.comments(), ["test data"]); - assert_eq!(back.rep(), d.rep()); - assert_eq!(back.map(), d.map()); - assert_eq!(back.try_chars(), "aeoiułkr"); - for w in ["kotem", "niekoty", "pieska", "krową", "Warszawą"] { - assert!(back.contains(w), "{w}"); - } - assert!(!back.contains("kotą")); - } - - #[test] - fn flag_utf8_and_default_modes_are_accepted() { - // `FLAG UTF-8` (VERO pt_BR) and `FLAG DEFAULT` are per-character flag modes: the same - // mode the reader already assumes. Only `long` and `num` stay unsupported. - for mode in ["UTF-8", "DEFAULT"] { - let aff = format!("SET UTF-8\nFLAG {mode}\nSFX a Y 1\nSFX a 0 y [^y]\n"); - let d = AffixDict::from_hunspell(&aff, "1\nkot/a\n", &[]).unwrap(); - assert!(d.contains("kot") && d.contains("koty"), "{mode}"); - } - assert!(AffixDict::from_hunspell("FLAG UTF-8\n", "0\n", &[]).is_ok(), "FLAG alone is harmless"); - } - - #[test] - fn emoji_flags_with_a_variation_selector_load() { - // RLA-ES (the Spanish dictionaries in LibreOffice and most Linux distributions) writes its - // telephone flag as ☎ followed by U+FE0F, in the rule and on the stems. - let aff = "SET UTF-8\nFLAG UTF-8\nPFX \u{260e}\u{fe0f} Y 1\nPFX \u{260e}\u{fe0f} 0 tele .\nSFX S Y 1\nSFX S 0 s [aeiou]\n"; - let d = AffixDict::from_hunspell(aff, "2\nbanco/S\u{260e}\u{fe0f}\nbasura/S\n", &[]).unwrap(); - assert!(d.contains("banco") && d.contains("bancos")); - assert!(d.contains("telebanco") && d.contains("telebancos"), "the ☎ prefix applies"); - assert!(d.contains("basuras") && !d.contains("telebasura"), "only to stems that carry it"); - assert!(!d.flag_chars.contains(&EMOJI_SELECTOR), "the selector is not a flag"); - } - - #[test] - fn emoji_forbiddenword_and_nosuggest_flags_load() { - // The same selector after the flag of the directives that name one flag. - let aff = "SET UTF-8\nFLAG UTF-8\nFORBIDDENWORD \u{260e}\u{fe0f}\nNOSUGGEST \u{1f6ab}\u{fe0f}\n"; - let d = AffixDict::from_hunspell(aff, "3\nbanco\nbanko/\u{260e}\u{fe0f}\nbancu/\u{1f6ab}\u{fe0f}\n", &[]).unwrap(); - assert!(d.contains("banco") && d.contains("bancu")); - assert!(!d.contains("banko"), "a stem with the FORBIDDENWORD flag is not a word"); - } - - #[test] - fn more_than_64_distinct_flags_load() { - // VERO pt_BR uses 106 distinct flags (FLAG UTF-8). Build one with every basic Latin - // letter, the digits and eight accented letters: 70 flags, more than 64. - let mut flags = String::new(); - let mut aff = String::from("SET UTF-8\nFLAG UTF-8\n"); - let chars: Vec = ('a'..='z').chain('A'..='Z').chain('0'..='9').chain(['á', 'é', 'í', 'ó', 'ú', 'ç', 'ã', 'õ']).collect(); - assert_eq!(chars.len(), 70); - for c in chars { - aff.push_str(&format!("SFX {c} N 1\nSFX {c} 0 {c} .\n")); - flags.push(c); - } - let d = AffixDict::from_hunspell(&aff, &format!("1\nverbo/{flags}\n"), &[]).unwrap(); - assert!(d.contains("verbo")); - assert!(d.contains("verboa") && d.contains("verboz") && d.contains("verbo9")); - assert!(d.contains("verboç") && d.contains("verboã"), "multi-byte UTF-8 flags"); - assert!(!d.contains("verboß"), "ß is not a flag and not a stem"); - } - - #[test] - fn more_than_128_distinct_flags_load() { - // The Hungarian dictionary uses 158 single-byte flags: 200 flags here, U+0080 up. - let flags: String = (0x80u32..0x80 + 200).filter_map(char::from_u32).collect(); - let mut aff = String::from("SET UTF-8\n"); - for (i, c) in flags.chars().enumerate() { - aff.push_str(&format!("SFX {c} N 1\nSFX {c} 0 x{i} .\n")); - } - let half: String = flags.chars().step_by(2).collect(); - let d = AffixDict::from_hunspell(&aff, &format!("1\nszó/{half}\n"), &[]).unwrap(); - assert!(d.contains("szóx0") && d.contains("szóx198") && d.contains("szóx130")); - assert!(!d.contains("szóx1") && !d.contains("szóx199") && !d.contains("szóx131")); - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert!(back.contains("szóx198") && !back.contains("szóx199")); - let too_many: String = (0x100u32..0x100 + 257).filter_map(char::from_u32).collect(); - assert!(AffixDict::from_hunspell("SET UTF-8\n", &format!("1\nszó/{too_many}\n"), &[]).is_err()); - } - - #[test] - fn forbidden_words_are_rejected() { - // VERO pt_BR marks spellings it must reject (flag ý under FLAG UTF-8) as stems carrying - // FORBIDDENWORD: listed, but never correct and never inflected. - let aff = "SET UTF-8\nFLAG UTF-8\nFORBIDDENWORD ý\nSFX a Y 1\nSFX a 0 y [^y]\n"; - let d = AffixDict::from_hunspell(aff, "2\nkot/a\nimundo/ý\n", &[]).unwrap(); - assert!(d.check("kot") && d.check("koty") && d.check("Kot") && d.check("KOT")); - assert!(!d.check("imundo") && !d.check("Imundo") && !d.check("IMUNDO")); - assert!(!d.contains("imundoy"), "a forbidden stem must not inflect"); - assert!(d.forms("imundo").is_empty()); - } - - #[test] - fn continuation_classes_chain_suffixes() { - // `SFX A 0 able/B`: the form derived by A carries flag B, so a suffix of class B may - // follow (workable → workables). The bare stem has A only, so `works` stays wrong. - let aff = "SET UTF-8\nFLAG UTF-8\nSFX A N 1\nSFX A 0 able/B .\nSFX B N 1\nSFX B 0 s .\n"; - let d = AffixDict::from_hunspell(aff, "1\nwork/A\n", &[]).unwrap(); - assert!(d.contains("work") && d.contains("workable") && d.contains("workables")); - assert!(!d.contains("works"), "the stem has A, not B"); - assert!(!d.contains("workabless"), "B declares no continuation"); - assert!(d.check("Workable") && d.check("WORKABLES")); - } - - #[test] - fn continuation_chains_stop_at_two_suffixes() { - // Hunspell strips at most two suffixes: work + able/B + s/C is a word, a third (C) is not. - let aff = "SET UTF-8\nFLAG UTF-8\nSFX A N 1\nSFX A 0 able/B .\nSFX B N 1\nSFX B 0 s/C .\nSFX C N 1\nSFX C 0 x .\n"; - let d = AffixDict::from_hunspell(aff, "1\nwork/A\n", &[]).unwrap(); - assert!(d.contains("workable") && d.contains("workables")); - assert!(!d.contains("workablesx"), "three chained suffixes exceed the limit"); - assert!(!d.forms("work").iter().any(|f| f == "workablesx")); - } - - #[test] - fn forms_include_continuation_chains() { - let aff = "SET UTF-8\nFLAG UTF-8\nSFX A N 1\nSFX A 0 able/B .\nSFX B N 1\nSFX B 0 s .\n"; - let d = AffixDict::from_hunspell(aff, "1\nwork/A\n", &[]).unwrap(); - let mut f = d.forms("work"); - f.sort(); - f.dedup(); - assert_eq!(f, ["work", "workable", "workables"]); - for form in d.forms("work") { - assert!(d.contains(&form), "every listed form is accepted: {form}"); - } - } - - #[test] - fn continuation_classes_merge_the_flags_of_a_group() { - // `s` under B and under C share one group (same append, same strip): a form carrying - // either flag takes it, and the stem carrying neither doesn't. - let aff = "SET UTF-8\nSFX A N 1\nSFX A 0 able/C .\nSFX D N 1\nSFX D 0 er/B .\nSFX B N 1\nSFX B 0 s .\nSFX C N 1\nSFX C 0 s [e]\n"; - let d = AffixDict::from_hunspell(aff, "2\nwork/A\nplay/D\n", &[]).unwrap(); - assert!(d.contains("workables") && d.contains("players")); - assert!(!d.contains("works") && !d.contains("plays") && !d.contains("workabless")); - assert!(d.forms("work").iter().any(|f| f == "workables") && d.forms("play").iter().any(|f| f == "players")); - } - - /// `n` suffix rules of flag A on one stem, `rule(i)` giving each rule's strip, append and condition. - fn many_rules(n: usize, rule: impl Fn(usize) -> String) -> AffixDict { - let mut aff = format!("SET UTF-8\nSFX A Y {n}\n"); - for i in 0..n { - aff.push_str(&format!("SFX A {}\n", rule(i))); - } - AffixDict::from_hunspell(&aff, "1\nkot/A\n", &[]).unwrap() - } - - #[test] - fn self_continuing_rules_check_in_linear_time() { - // Thousands of empty-append rules that continue with their own flag: each check used to - // follow every rule from every rule, millions of steps a word (#3613). The checks take - // about a millisecond; the old checker took 14 s for two of them at 16,000 rules, so at - // 40,000 (quadratic: minutes) the 10 s bound leaves a busy machine a wide margin. - let d = many_rules(40_000, |_| "0 0/A .".into()); - let t = std::time::Instant::now(); - assert!(d.check("kot") && !d.check("pies") && !d.check("kotx")); - assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed()); - } - - #[test] - fn forms_of_self_continuing_rules_are_bounded() { - // Every rule's form continues with every rule: millions of strings for one stem (#3613). - let same = many_rules(3000, |_| "0 0/A .".into()); - let distinct = many_rules(3000, |i| format!("0 x{i}/A .")); - let t = std::time::Instant::now(); - let (a, b) = (same.forms("kot"), distinct.forms("kot")); - // The cap on the count is what tells the old code (9 million forms each) apart; the - // generous time bound (tens of ms here) only backs up the rule-visit cap. - assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed()); - assert!(a.len() <= MAX_FORMS && b.len() <= MAX_FORMS, "{} {}", a.len(), b.len()); - assert!(b.iter().any(|f| f == "kotx0x1"), "chained forms are still listed"); - } - - #[test] - fn many_strips_per_suffix_index_in_linear_time() { - // One append with thousands of strips: the index used to scan the append's groups for - // each rule. Building 200,000 rules takes well under a second; the old index took 3 s at - // 30,000 and over 10 minutes at 300,000. - let n = 200_000; - let t = std::time::Instant::now(); - let d = many_rules(n, |i| format!("k{i} y .")); - assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed()); - assert_eq!(d.sfx_index.get("y").map(Vec::len), Some(n)); - assert!(!d.contains("koty")); - } - - #[test] - fn compact_format_round_trips_continuations_and_forbidden() { - let aff = "SET UTF-8\nFLAG UTF-8\nFORBIDDENWORD ý\nSFX A N 1\nSFX A 0 able/B .\nSFX B N 1\nSFX B 0 s .\n"; - let d = AffixDict::from_hunspell(aff, "2\nwork/A\nimundo/ý\n", &[]).unwrap(); - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert!(back.contains("work") && back.contains("workable") && back.contains("workables")); - assert!(!back.contains("works"), "the stem has A, not B, after a round trip"); - assert!(!back.check("imundo") && !back.contains("imundoy"), "forbidden survives a round trip"); - } - - #[test] - fn dic_bom_glued_to_the_count_line_is_skipped() { - // VERO's .dic opens with a UTF-8 BOM glued to its count line; it must not become a stem. - let aff = "SET UTF-8\nSFX a Y 1\nSFX a 0 y [^y]\n"; - let d = AffixDict::from_hunspell(aff, "\u{feff}1\nkot/a\n", &[]).unwrap(); - assert_eq!(d.len(), 1); - assert!(d.contains("kot") && d.contains("koty")); - } - - #[test] - fn flag_aliases_stand_for_flag_sets() { - // `AF`: `/1` in the `.dic` and in a continuation class means the first alias's flags. - // Morphological aliases (`AM`) and the fields after a stem are skipped. - let aff = "SET UTF-8\nAF 2\nAF ab # 1\nAF c # 2\nAM 1\nAM po:noun\nSFX a Y 1\nSFX a 0 ok/2 . 1\nSFX b Y 1\nSFX b 0 at . 1\nSFX c Y 1\nSFX c 0 ban . 1\n"; - let d = AffixDict::from_hunspell(aff, "1\nház/1\t1\n", &[]).unwrap(); - assert!(d.contains("ház") && d.contains("házok") && d.contains("házat") && d.contains("házokban")); - assert!(!d.contains("házban"), "the stem has a and b, not c"); - // An undefined alias reads as no flags, as in Hunspell: the stem loads, without affixes. - let undefined = AffixDict::from_hunspell(aff, "2\nház/3\nkert/1\n", &[]).unwrap(); - assert!(undefined.check("ház") && !undefined.check("házok"), "alias 3 is not defined"); - assert!(undefined.check("kertok"), "the other entries keep their aliases"); - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert!(back.contains("házokban") && !back.contains("házban"), "aliases are expanded in the compact format"); - } - - #[test] - fn iconv_and_ignore_normalize_input() { - // Ligatures read as their letters; `IGNORE` characters vanish from input and stems. - let aff = "SET UTF-8\nICONV 2\nICONV fi fi\nICONV ffl ffl\nIGNORE ()\nSFX a Y 1\nSFX a 0 s .\n"; - let d = AffixDict::from_hunspell(aff, "2\nfine/a\nwaf(fle)/a\n", &[]).unwrap(); - assert!(d.check("fine") && d.check("fines") && d.check("Fine") && d.contains("fine")); - assert!(d.check("waffle") && d.check("waffles") && d.check("wa(ffle)")); - assert!(!d.check("finer")); - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert!(back.check("fines") && back.check("waffle") && back.check("waf(fle)")); - } - - #[test] - fn alias_numbers_are_read_as_hunspell_reads_them() { - // Checked with the Hunspell 1.7 command line. With an `AF` table a flag field is the alias - // its leading digits number: `fal/2x` is alias 2, `lab/b` has no flags. - let aff = "SET UTF-8\nAF 2\nAF B\nAF C\nSFX B Y 1\nSFX B 0 s .\nSFX C Y 1\nSFX C 0 x .\nSFX b Y 1\nSFX b 0 q .\n"; - let d = AffixDict::from_hunspell(aff, "3\nlab/b\nfal/2x\nkut/1\n", &[]).unwrap(); - assert!(d.check("lab") && !d.check("labq") && !d.check("labs")); - assert!(d.check("falx") && !d.check("fals") && d.check("kuts")); - // A table without its count line is rejected: no alias applies. - let broken = AffixDict::from_hunspell("SET UTF-8\nAF B\nAF C\nSFX B Y 1\nSFX B 0 s .\n", "2\nlab/1\nfal/2\n", &[]).unwrap(); - assert!(broken.check("lab") && !broken.check("labs") && !broken.check("fals")); - // `AF 0` is rejected the same way: `lab/B` keeps its literal flag. - let zero = AffixDict::from_hunspell("SET UTF-8\nAF 0\nSFX B Y 1\nSFX B 0 s .\n", "1\nlab/B\n", &[]).unwrap(); - assert!(zero.check("lab") && zero.check("labs")); - // An undefined alias in a continuation class leaves the rule in force, without it. - let cont = AffixDict::from_hunspell("SET UTF-8\nAF 1\nAF a\nSFX a Y 1\nSFX a 0 ok/7 .\n", "1\nhaz/1\n", &[]).unwrap(); - assert!(cont.check("haz") && cont.check("hazok")); - } - - #[test] - fn a_rep_count_line_may_carry_a_comment() { - let d = AffixDict::from_hunspell("SET UTF-8\nREP 1 # confusions\nREP x y\n", "1\nay\n", &[]).unwrap(); - assert_eq!(d.rep(), [("x".to_string(), "y".to_string())]); - } - - #[test] - fn count_lines_may_carry_a_comment() { - // Hunspell reads the first `AF` and `ICONV` line as the count, comment and all: `AF 2 #` - // is no alias, and `ICONV 1 # …` doesn't turn `1` into `#`. - let aff = "SET UTF-8\nAF 2 # aliases\nAF a # 1\nAF b # 2\nICONV 1 # ligatures\nICONV q y\nSFX a Y 1\nSFX a 0 ok .\nSFX b Y 1\nSFX b 0 at .\n"; - let d = AffixDict::from_hunspell(aff, "3\nhaz/1\nkert/2\na1\n", &[]).unwrap(); - assert!(d.check("hazok") && !d.check("hazat") && d.check("kertat") && !d.check("kertok"), "alias 1 is `a`"); - assert!(d.check("a1") && !d.check("a#")); - // The UTF-8 reader keeps such a line as it is, so both readers agree on it. - let (a, dic) = crate::hunspell::decode(aff.as_bytes(), "1\nhaz/1\n".as_bytes()).unwrap(); - assert!(a.contains("AF 2 # aliases") && a.contains("ICONV 1 # ligatures"), "{a}"); - assert!(AffixDict::from_hunspell(&a, &dic, &[]).unwrap().check("hazok")); - } - - #[test] - fn dic_entries_may_hold_spaces() { - // `ad hoc/B` is one entry, as in Hunspell; `ad` alone is no word. - let aff = "SET UTF-8\nSFX B Y 1\nSFX B 0 s .\n"; - // Checked with the Hunspell 1.7 command line: without a tab or a morphological field the - // whole line is the entry (`kot/B 12` has the flag `B`), and leading blanks belong to it - // (` lab` is no `lab`). - let d = AffixDict::from_hunspell(aff, "5\nad hoc/B\tpo:adv\nword/B po:noun\nkot/B 12\n lab/B\nés így/B\n", &[]).unwrap(); - assert!(d.contains("ad hoc") && d.contains("ad hocs") && d.contains("és így") && d.contains("és ígys")); - assert!(!d.contains("ad") && !d.contains("hoc") && !d.contains("ads") && !d.contains("így")); - assert!(d.contains("words") && d.contains("kots") && !d.contains("kot/B")); - assert!(!d.contains("lab") && !d.contains("labs")); - } - - #[test] - fn ignored_characters_leave_the_stripped_text_alone() { - // Hunspell removes `IGNORE` characters from the appended text, not from the stripped - // one: `SFX a bx c` never matches the stem `abx` (stored as `ab`), so `ac` is wrong, - // while `SFX a b cx` appends `c` (checked with the Hunspell 1.7 command line). - let strip = AffixDict::from_hunspell("SET UTF-8\nIGNORE x\nSFX a Y 1\nSFX a bx c .\n", "1\nabx/a\n", &[]).unwrap(); - assert!(strip.check("ab") && strip.check("abx") && !strip.check("ac")); - let add = AffixDict::from_hunspell("SET UTF-8\nIGNORE x\nSFX a Y 1\nSFX a b cx .\n", "1\nab/a\n", &[]).unwrap(); - assert!(add.check("ac") && add.check("acx")); - for d in [&strip, &add] { - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert_eq!(back.check("ac"), d.check("ac")); - } - } - - #[test] - fn normalizing_borrows_when_nothing_changes() { - let d = AffixDict::from_hunspell("SET UTF-8\nICONV 1\nICONV fi fi\nIGNORE ()\n", "1\nfine\n", &[]).unwrap(); - assert!(matches!(d.normalize("fine"), std::borrow::Cow::Borrowed("fine"))); - assert_eq!(d.normalize("(fi)ne"), "fine"); - assert!(matches!(dict().normalize("kot"), std::borrow::Cow::Borrowed("kot"))); - } - - #[test] - fn keepcase_stems_match_only_as_written() { - let aff = "SET UTF-8\nKEEPCASE k\nSFX a Y 1\nSFX a 0 s .\n"; - let d = AffixDict::from_hunspell(aff, "3\npH/k\nkm/ka\nkot/a\n", &[]).unwrap(); - assert!(d.check("pH") && d.check("km") && d.check("kms")); - assert!(!d.check("PH") && !d.check("Km") && !d.check("KM") && !d.check("Kms")); - assert!(d.check("Kot") && d.check("KOTS"), "other stems still follow the sentence and heading case"); - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert!(back.check("km") && !back.check("Km")); - } - - #[test] - fn substandard_and_lemma_present_words_are_correct() { - // `SUBSTANDARD` and `LEMMA_PRESENT` steer morphological generation, and a SUBSTANDARD - // stem is never suggested; the stems and rules they mark are still correct. - let aff = "SET UTF-8\nSUBSTANDARD x\nLEMMA_PRESENT l\nSFX a Y 2\nSFX a 0 y .\nSFX a 0 em/x .\n"; - let d = AffixDict::from_hunspell(aff, "2\nkot/a\npies/axl\n", &[]).unwrap(); - for dict in [&d, &AffixDict::from_bytes(&d.to_bytes()).unwrap()] { - assert!(dict.check("kot") && dict.check("koty") && dict.check("kotem")); - assert!(dict.check("pies") && dict.check("piesy") && dict.check("Piesem")); - assert!(dict.can_suggest("koty") && dict.can_suggest("kotem") && dict.contains_suggestible("kot")); - assert!(!dict.can_suggest("piesy") && !dict.can_suggest("Pies") && !dict.contains_suggestible("pies")); - } - } - - #[test] - fn unsupported_directives_are_refused_and_the_rest_load() { - // A dictionary that needs a directive outside this engine fails to load rather than - // accepting the wrong words. - for dir in UNSUPPORTED { - assert!(AffixDict::from_hunspell(&format!("SET UTF-8\n{dir} X\n"), DIC, &[]).is_err(), "{dir} should be refused"); - } - // The directives this engine reads, with no other change to the dictionary (`AF` would - // turn the flags of `DIC` into alias numbers; it has tests of its own). - for line in ["AM 1\nAM po:noun", "ICONV 1\nICONV fi fi", "IGNORE ()", "KEEPCASE k", "SUBSTANDARD x", "LEMMA_PRESENT l"] { - let d = AffixDict::from_hunspell(&format!("{AFF}{line}\n"), DIC, &[]).unwrap_or_else(|e| panic!("{line}: {e}")); - assert!(d.check("koty") && d.check("niekoty") && !d.check("kotx"), "{line}"); - } - } - - #[test] - fn fields_split_on_spaces_and_tabs_only() { - // A flag byte read as U+00A0 (no-break space) or U+0085 is a flag, not a separator. - let aff = "SET UTF-8\nSFX \u{a0} Y 1\nSFX \u{a0} 0 y .\nSFX \u{85} Y 1\nSFX \u{85} 0 em .\n"; - let d = AffixDict::from_hunspell(aff, "1\nkot/\u{a0}\u{85}\n", &[]).unwrap(); - assert!(d.contains("koty") && d.contains("kotem")); - let back = AffixDict::from_bytes(&d.to_bytes()).unwrap(); - assert!(back.contains("koty") && back.contains("kotem")); - // The same bytes as one-flag directives, as Hunspell reads them. - let aff = "SET UTF-8\nFORBIDDENWORD \u{a0}\nKEEPCASE \u{85}\nSFX a Y 1\nSFX a 0 s .\n"; - let d = AffixDict::from_hunspell(aff, "3\nkot/a\nrossz/\u{a0}\npH/\u{85}\n", &[]).unwrap(); - for dict in [&d, &AffixDict::from_bytes(&d.to_bytes()).unwrap()] { - assert!(dict.check("kots") && !dict.check("rossz")); - assert!(dict.check("pH") && !dict.check("PH")); - } - } - - #[test] - fn unsupported_and_broken_input_is_an_error() { - assert!(AffixDict::from_hunspell("FLAG long\n", DIC, &[]).is_err()); - assert!(AffixDict::from_hunspell("FLAG num\n", DIC, &[]).is_err()); - assert!(AffixDict::from_hunspell("COMPOUNDFLAG X\n", DIC, &[]).is_err()); - assert!(AffixDict::from_hunspell("SFX a Y 1\nSFX a 0 y [ab\n", DIC, &[]).is_err()); - assert!(AffixDict::from_bytes(b"not deflate").is_err()); - let ok = dict().to_bytes(); - assert!(AffixDict::from_bytes(&ok[..ok.len() / 2]).is_err()); - let garbage = miniz_oxide::deflate::compress_to_vec(b"WCAFF1\nWORDS\n\x09abc\n", 6); - assert!(AffixDict::from_bytes(&garbage).is_err(), "prefix longer than the previous word"); - let empty = AffixDict::default(); - assert!(!empty.contains("kot") && !empty.check("Kot") && empty.forms("kot").is_empty()); - } - - proptest::proptest! { - #[test] - fn lookups_never_panic(s in "\\PC{0,40}", bytes in proptest::collection::vec(proptest::num::u8::ANY, 0..200)) { - let d = dict(); - let _ = d.check(&s); - let _ = d.forms(&s); - let _ = AffixDict::from_bytes(&bytes); - let _ = AffixDict::from_bytes(&miniz_oxide::deflate::compress_to_vec(&bytes, 1)); - } - } -} diff --git a/crates/proof/src/affix_suggest.rs b/crates/proof/src/affix_suggest.rs deleted file mode 100644 index 1456f81c7..000000000 --- a/crates/proof/src/affix_suggest.rs +++ /dev/null @@ -1,556 +0,0 @@ -//! Spelling suggestions from an affix dictionary, one engine for every language that reads one -//! (Czech, Croatian, Indonesian, Italian, Polish, Portuguese, Russian, Spanish). Candidates come -//! in rounds of rising cost, each checked against the dictionary: -//! 1. letter case (`warszawa` → `Warszawa`); -//! 2. missing or wrong diacritics and other MAP slips, a few letters changed (`zolw` → `żółw`): -//! adding a missing accent is the commonest slip and ranks first, while taking away one the -//! writer typed ranks after the known confusions; -//! 3. the REP table of common confusions (`żeka` → `rzeka`); -//! 4. a missing space (`napewno` → `na pewno`); -//! 5. keyboard neighbours from the KEY rows, then one typo: a letter left out, added, swapped with -//! the next or mistyped (TRY letters); -//! 6. when that finds fewer than asked for: the stems that start like the word (MAP relatives -//! folded) and share the most letter pairs with it, whose forms are ranked by an edit distance in -//! which a MAP slip costs half (two typos at most). -//! -//! The tables come from the dictionary; a language's [`Tuning`] adds the ones its dictionaries may -//! lack and the slips no table can say. The input is bounded ([`MAX_LETTERS`]), and so is every -//! round, so a call costs a few thousand dictionary lookups at most. - -use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; - -use crate::affix::{AffixDict, Casing, capitalize, casing}; - -/// Longest word suggestions are looked for, in letters. -pub(crate) const MAX_LETTERS: usize = 40; -const MAX_MAP_VARIANTS: usize = 4096; -/// Stems the scan of round 6 expands. -const SCAN_STEMS: usize = 48; - -const COST_CASE: u32 = 0; -pub(crate) const COST_ADD_DIACRITIC: u32 = 4; -pub(crate) const COST_OTHER_MAP: u32 = 7; -const COST_DROP_DIACRITIC: u32 = 11; -const COST_REP: u32 = 10; -pub(crate) const COST_SPLIT: u32 = 12; -/// A missing space leaving a half shorter than three letters ([`Split::Anywhere`]). -const COST_SPLIT_SHORT: u32 = 17; -/// A missing space between two longer halves with no short word beside it ([`Split::Near`]). -pub(crate) const COST_SPLIT_LONG: u32 = 18; -const COST_KEY: u32 = 13; -const COST_SWAP: u32 = 14; -const COST_DROP_OR_ADD: u32 = 15; -pub(crate) const COST_MISTYPE: u32 = 16; -const COST_SCAN: u32 = 20; - -/// What a language adds to its dictionary's tables. -pub(crate) struct Tuning { - /// Where a missing space may go (round 4). - pub split: Split, - /// TRY letters for a dictionary without a TRY line. - pub alphabet: &'static str, - /// MAP groups the language adds to its dictionary's (each string is one group: `"cčć"`). - pub map: &'static [&'static str], - /// REP confusions the language adds to its dictionary's. - pub rep: &'static [(&'static str, &'static str)], - /// The cost of a REP confusion. - pub rep_cost: u32, - /// Most letters a MAP variant changes. - pub max_map_changes: usize, - /// The cost of changing letter `from` to its MAP relative `to`. - pub map_cost: fn(char, char) -> u32, - /// The cost of typing letter `c` at `i` of the word (`chars`, lower case) instead. - pub mistype_cost: fn(&[char], usize, char) -> u32, -} - -/// Where a missing space may go. -pub(crate) enum Split { - /// Anywhere, dearer when a half is shorter than three letters. - Anywhere, - /// After a short word that often runs into the next one (`na pewno`, `w ogóle`) or before one - /// that runs into the word before (`kocham cię`), dearer between two longer halves, and never - /// leaving a stray short half (`że ka`). - Near { leading: &'static [&'static str], trailing: &'static [&'static str] }, - /// The language's own rule: the cost of the halves `a b`, or `None` to skip them. - Custom(fn(&str, &str) -> Option), -} - -impl Tuning { - /// The dictionary's tables alone (Czech, Indonesian, Spanish). - pub(crate) const DEFAULT: Tuning = - Tuning { split: Split::Anywhere, alphabet: "", map: &[], rep: &[], rep_cost: COST_REP, max_map_changes: 4, map_cost, mistype_cost }; -} - -/// The cost of changing letter `from` to its MAP relative `to`. Adding a missing accent is the -/// commonest slip (typing without diacritics); an accent the writer did type is likelier right, -/// so taking one away costs more than a known confusion from the REP table. -pub(crate) fn map_cost(from: char, to: char) -> u32 { - match (from.is_ascii(), to.is_ascii()) { - (true, false) => COST_ADD_DIACRITIC, - (false, true) => COST_DROP_DIACRITIC, - _ => COST_OTHER_MAP, - } -} - -/// Any mistyped letter costs the same. -fn mistype_cost(_: &[char], _: usize, _: char) -> u32 { - COST_MISTYPE -} - -struct Collector<'a> { - d: &'a AffixDict, - /// The word, lower case. - input: String, - /// Candidate → the cheapest cost it was offered at (rounds don't run in rising cost). - tested: HashMap, - /// Suggestion → (cost, order found). - found: HashMap, - seq: usize, -} - -impl Collector<'_> { - /// Check a lower-case candidate (a word, or two separated by a space) and keep it if every - /// word is in the dictionary, as written or capitalized (names). - fn offer(&mut self, cand: &str, cost: u32) { - if cand.is_empty() || cand == self.input { - return; - } - match self.tested.get_mut(cand) { - Some(prev) if *prev <= cost => return, - Some(prev) => *prev = cost, - None => { - self.tested.insert(cand.to_string(), cost); - } - } - let mut words = Vec::new(); - let mut cost = cost; - for part in cand.split(' ') { - if self.d.contains_suggestible(part) { - words.push(part.to_string()); - } else { - let c = capitalize(part); - if !self.d.contains_suggestible(&c) { - return; - } - words.push(c); - // A name ranks after a common word at the same distance (`moze` → `może`, then - // `Mozę`). - cost += 1; - } - } - self.keep(words.join(" "), cost); - } - - /// Keep a suggestion known to be correct, unless the dictionary says never to offer it. - fn keep(&mut self, text: String, cost: u32) { - if !text.split(' ').all(|part| self.d.can_suggest(part)) { - return; - } - self.seq += 1; - let seq = self.seq; - let e = self.found.entry(text).or_insert((cost, seq)); - if cost < e.0 { - *e = (cost, seq); - } - } - - fn count_within(&self, cost: u32) -> usize { - self.found.values().filter(|(c, _)| *c <= cost).count() - } -} - -/// The MAP groups: the dictionary's, then the language's. -fn groups<'a>(d: &'a AffixDict, t: &Tuning) -> Cow<'a, [Vec]> { - if t.map.is_empty() { - return Cow::Borrowed(d.map()); - } - Cow::Owned(d.map().iter().cloned().chain(t.map.iter().map(|g| g.chars().collect())).collect()) -} - -/// The other members of every MAP group of `c` (its likely confusions), each once. -fn alternatives(c: char, map: &[Vec]) -> Vec { - let mut out: Vec = Vec::new(); - for x in map.iter().filter(|g| g.contains(&c)).flat_map(|g| g.iter().copied()) { - if x != c && !out.contains(&x) { - out.push(x); - } - } - out -} - -/// Every way of changing up to `t.max_map_changes` letters of `chars` to a MAP relative (`alts`, -/// letter by letter), with its cost, each written after `buf`. -fn map_variants(chars: &[char], alts: &[Vec], t: &Tuning, changes: usize, cost: u32, buf: &mut Vec, out: &mut Vec<(String, u32)>) { - if out.len() >= MAX_MAP_VARIANTS { - return; - } - let Some((&c, rest)) = chars.split_first() else { - if changes > 0 { - out.push((buf.iter().collect(), cost)); - } - return; - }; - let (here, later) = alts.split_first().map_or((&[][..], &[][..]), |(h, l)| (h.as_slice(), l)); - buf.push(c); - map_variants(rest, later, t, changes, cost, buf, out); - buf.pop(); - if changes < t.max_map_changes { - for &a in here { - buf.push(a); - map_variants(rest, later, t, changes + 1, cost + (t.map_cost)(c, a), buf, out); - buf.pop(); - } - } -} - -/// A letter's MAP representative (the first of its first group), lower case: `ż` → `z`, `ó` → `o`. -pub(crate) fn fold(c: char, map: &[Vec]) -> char { - let l = c.to_lowercase().next().unwrap_or(c); - map.iter().find(|g| g.contains(&l)).and_then(|g| g.first().copied()).unwrap_or(l) -} - -/// Optimal string alignment distance where a MAP slip (`a`/`ą`, `u`/`ó`) costs 1 and any other -/// edit 2; `limit + 1` once it is certainly above `limit`. -pub(crate) fn weighted_distance(a: &[char], b: &[char], related: &dyn Fn(char, char) -> bool, limit: u32) -> u32 { - let m = b.len(); - let over = limit + 1; - let mut prev2 = vec![0u32; m + 1]; - let mut prev: Vec = (0..=m as u32).map(|j| j * 2).collect(); - let mut cur = vec![0u32; m + 1]; - for (i, &ai) in a.iter().enumerate() { - if let Some(c) = cur.get_mut(0) { - *c = (i as u32 + 1) * 2; - } - let mut row_min = u32::MAX; - for (j, &bj) in b.iter().enumerate() { - let sub = if ai == bj { - 0 - } else if related(ai, bj) { - 1 - } else { - 2 - }; - let at = |v: &[u32], k: usize| v.get(k).copied().unwrap_or(u32::MAX / 4); - let mut v = (at(&prev, j + 1) + 2).min(at(&cur, j) + 2).min(at(&prev, j) + sub); - if i > 0 && j > 0 && a.get(i - 1) == Some(&bj) && b.get(j - 1) == Some(&ai) { - v = v.min(at(&prev2, j - 1) + 2); - } - if let Some(c) = cur.get_mut(j + 1) { - *c = v; - } - row_min = row_min.min(v); - } - if m > 0 && row_min > limit { - return over; - } - std::mem::swap(&mut prev2, &mut prev); - std::mem::swap(&mut prev, &mut cur); - } - prev.get(m).copied().unwrap_or(over).min(over) -} - -/// The cost of a missing space between `a` and `b`, or `None` when the split isn't offered. -fn split_cost(split: &Split, a: &str, b: &str) -> Option { - let long = |part: &str| part.chars().count() >= 3; - match split { - Split::Anywhere => Some(if long(a) && long(b) { COST_SPLIT } else { COST_SPLIT_SHORT }), - Split::Near { leading, trailing } => { - if (leading.contains(&a) && long(b)) || (long(a) && trailing.contains(&b)) { - Some(COST_SPLIT) - } else if long(a) && long(b) { - Some(COST_SPLIT_LONG) - } else { - None - } - } - Split::Custom(cost) => cost(a, b), - } -} - -/// Spelling suggestions for `word` from `d`, best first, at most `max`, in the word's letter case. -pub(crate) fn suggest_in(d: &AffixDict, word: &str, max: usize, t: &Tuning) -> Vec { - let word = word.trim(); - let n = word.chars().count(); - if n == 0 || n > MAX_LETTERS || max == 0 || d.is_empty() || word.contains(char::is_whitespace) { - return Vec::new(); - } - let case = casing(word); - let input = word.to_lowercase(); - let chars: Vec = input.chars().collect(); - let mut col = Collector { d, input: input.clone(), tested: HashMap::new(), found: HashMap::new(), seq: 0 }; - add_case(&mut col, word, &input); - let map = groups(d, t); - add_map_slips(&mut col, &chars, &map, t); - add_confusions(&mut col, &input, t); - add_splits(&mut col, &input, &t.split); - add_key_neighbours(&mut col, &chars); - add_typos(&mut col, &chars, t); - // 6. Further off: scan the stems that start like the word. - if col.count_within(COST_MISTYPE) < max { - scan(&mut col, &chars, &map); - } - let mut found: Vec<(String, (u32, usize))> = col.found.into_iter().collect(); - // Among equal costs, the change nearest the word's start is the likelier intent - // (the first changed letter breaks ties). - let diff_at = |text: &str| chars.iter().zip(text.chars()).position(|(a, b)| *a != b).unwrap_or(n); - found.sort_by_key(|(text, (cost, seq))| (*cost, diff_at(text), *seq)); - let mut out: Vec = Vec::new(); - for (text, _) in found { - let recased = match case { - Casing::Initial => capitalize(&text), - Casing::Upper if n > 1 => text.to_uppercase(), - _ => text.clone(), - }; - // A KEEPCASE word only exists in its dictionary case: `Km` would be flagged, so offer `km`. - let t = if recased != text && text.split(' ').any(|p| d.case_locked(p)) { text } else { recased }; - if t != word && !out.contains(&t) { - out.push(t); - } - if out.len() >= max { - break; - } - } - out -} - -/// Round 1: letter case. -fn add_case(col: &mut Collector, word: &str, input: &str) { - if col.d.contains(input) && word != input { - col.keep(input.to_string(), COST_CASE); - } else if col.d.contains(&capitalize(input)) && word != capitalize(input) { - col.keep(capitalize(input), COST_CASE); - } -} - -/// Round 2: accents and other MAP slips. -fn add_map_slips(col: &mut Collector, chars: &[char], map: &[Vec], t: &Tuning) { - let alts: Vec> = chars.iter().map(|&c| alternatives(c, map)).collect(); - let mut variants = Vec::new(); - map_variants(chars, &alts, t, 0, 0, &mut Vec::with_capacity(chars.len()), &mut variants); - variants.sort_by_key(|v| v.1); - for (v, cost) in &variants { - col.offer(v, *cost); - } -} - -/// Round 3: common confusions. -fn add_confusions(col: &mut Collector, input: &str, t: &Tuning) { - let rep = col.d.rep().iter().map(|(from, to)| (from.as_str(), to.as_str())).chain(t.rep.iter().copied()); - for (from, to) in rep { - if from.is_empty() { - continue; - } - for (i, _) in input.match_indices(from) { - if let (Some(a), Some(b)) = (input.get(..i), input.get(i + from.len()..)) { - col.offer(&format!("{a}{to}{b}"), t.rep_cost); - } - } - } -} - -/// Round 4: a missing space; both halves must be dictionary words. -fn add_splits(col: &mut Collector, input: &str, split: &Split) { - for (i, _) in input.char_indices().skip(1) { - let (Some(a), Some(b)) = (input.get(..i), input.get(i..)) else { continue }; - if let Some(cost) = split_cost(split, a, b) { - col.offer(&format!("{a} {b}"), cost); - } - } -} - -/// Round 5: keyboard neighbours from the dictionary's KEY rows. -fn add_key_neighbours(col: &mut Collector, chars: &[char]) { - for row in col.d.key().split('|').take(32) { - let keys: Vec = row.chars().take(128).collect(); - for pair in keys.windows(2) { - let [a, b] = pair else { continue }; - for (i, &c) in chars.iter().enumerate() { - let replacement = if c == *a { - Some(*b) - } else if c == *b { - Some(*a) - } else { - None - }; - if let Some(replacement) = replacement { - let mut v = chars.to_vec(); - if let Some(x) = v.get_mut(i) { - *x = replacement; - } - col.offer(&v.iter().collect::(), COST_KEY); - } - } - } - } -} - -/// Round 5, second half: one typo, with the TRY letters (the language's when the dictionary has -/// none), bounded and each once, in their order: a TRY line lists the commonest letters first, so -/// equal-cost candidates come out in that order. -fn add_typos(col: &mut Collector, chars: &[char], t: &Tuning) { - let d = col.d; - let n = chars.len(); - let try_chars = if d.try_chars().is_empty() { t.alphabet } else { d.try_chars() }; - let mut letters: Vec = Vec::new(); - for c in try_chars.chars().filter(|c| c.is_lowercase()).take(128) { - if !letters.contains(&c) { - letters.push(c); - } - } - let s = |v: &[char]| v.iter().collect::(); - for i in 0..n.saturating_sub(1) { - if chars.get(i) != chars.get(i + 1) { - let mut v = chars.to_vec(); - v.swap(i, i + 1); - col.offer(&s(&v), COST_SWAP); - } - } - for i in 0..n { - let mut v = chars.to_vec(); - v.remove(i); - col.offer(&s(&v), COST_DROP_OR_ADD); - } - for i in 0..=n { - for &c in &letters { - let mut v = chars.to_vec(); - v.insert(i, c); - col.offer(&s(&v), COST_DROP_OR_ADD); - } - } - for i in 0..n { - for &c in &letters { - if chars.get(i) != Some(&c) { - let mut v = chars.to_vec(); - if let Some(x) = v.get_mut(i) { - *x = c; - } - col.offer(&s(&v), (t.mistype_cost)(chars, i, c)); - } - } - } -} - -/// Round 6: rank the stems starting with the word's first letter (or a MAP relative of it, either -/// case) by shared letter pairs, then keep the forms of the best within two typos. -fn scan(col: &mut Collector, chars: &[char], map: &[Vec]) { - let d = col.d; - let Some(&first) = chars.first() else { return }; - let folded: Vec = chars.iter().map(|&c| fold(c, map)).collect(); - let pairs: HashSet<(char, char)> = std::iter::once(' ').chain(folded.iter().copied()).zip(folded.iter().copied()).collect(); - let mut firsts: Vec = std::iter::once(first).chain(alternatives(first, map)).collect(); - let upper: Vec = firsts.iter().flat_map(|c| c.to_uppercase()).collect(); - firsts.extend(upper); - firsts.sort_unstable(); - firsts.dedup(); - let n = folded.len(); - let mut best: Vec<(u32, &str)> = Vec::new(); - for f in firsts { - let prefix = f.to_string(); - for stem in d.stems_with_prefix(&prefix) { - let sf: Vec = stem.chars().map(|c| fold(c, map)).collect(); - let shared = std::iter::once(' ').chain(sf.iter().copied()).zip(sf.iter().copied()).filter(|p| pairs.contains(p)).count(); - if shared < 2 { - continue; - } - // Shared pairs, less the stem's letters the word doesn't explain, less a length gap - // beyond what an ending explains. - let extra = sf.len().saturating_sub(shared); - let gap = sf.len().abs_diff(n).saturating_sub(4); - let score = (shared * 4).saturating_sub(extra * 3 + gap * 2) as u32; - if score == 0 { - continue; - } - if best.len() < SCAN_STEMS { - best.push((score, stem)); - } else if let Some(min) = best.iter_mut().min_by_key(|b| b.0) - && score > min.0 - { - *min = (score, stem); - } - } - } - best.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(b.1))); - let related = |a: char, b: char| fold(a, map) == fold(b, map); - let limit = 4; - for (_, stem) in best { - for form in d.forms(stem) { - let lf: Vec = form.to_lowercase().chars().collect(); - if lf.len().abs_diff(chars.len()) > 2 { - continue; - } - let dist = weighted_distance(&lf, chars, &related, limit); - if dist <= limit && lf.as_slice() != chars { - col.keep(form, COST_SCAN + dist * 3); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn an_extra_trailing_letter_is_dropped_before_a_short_word_split() { - let d = AffixDict::from_hunspell("", "2\npočítač\nz\n", &[]).unwrap(); - let suggestions = suggest_in(&d, "počítačz", 8, &Tuning::DEFAULT); - assert_eq!(suggestions.first().map(String::as_str), Some("počítač"), "{suggestions:?}"); - assert!(suggestions.contains(&"počítač z".into()), "{suggestions:?}"); - } - - /// A candidate offered again at a lower cost (a REP confusion after a MAP diacritic drop) - /// ranks at the lower one. - #[test] - fn a_cheaper_later_offer_wins() { - let d = AffixDict::from_hunspell("", "1\ncafe\n", &[]).unwrap(); - let mut col = Collector { d: &d, input: "cafè".into(), tested: HashMap::new(), found: HashMap::new(), seq: 0 }; - col.offer("cafe", COST_DROP_DIACRITIC); - col.offer("cafe", COST_REP); - col.offer("cafe", COST_SCAN); - assert_eq!(col.found.get("cafe").map(|e| e.0), Some(COST_REP)); - } - - #[test] - fn distance_weighs_map_slips_half() { - let map = vec![vec!['a', 'ą'], vec!['o', 'ó', 'u']]; - let related = |a: char, b: char| fold(a, &map) == fold(b, &map); - let c = |s: &str| s.chars().collect::>(); - assert_eq!(weighted_distance(&c("ktury"), &c("który"), &related, 9), 1); - assert_eq!(weighted_distance(&c("kot"), &c("kto"), &related, 9), 2); - assert_eq!(weighted_distance(&c("kot"), &c("kotek"), &related, 9), 4); - assert_eq!(weighted_distance(&c("kot"), &c("pies"), &related, 2), 3); - assert_eq!(weighted_distance(&c(""), &c("ab"), &related, 9), 4); - } - - /// A KEEPCASE word is offered in its dictionary case when the word's own case would be flagged - /// (`Kmm` → `km`, never `Km`), and a capitalised KEEPCASE word gets its dictionary form. - #[test] - fn keepcase_suggestions_are_not_recased_into_errors() { - let d = AffixDict::from_hunspell("KEEPCASE K\n", "2\nkm/K\npies\n", &[]).unwrap(); - assert!(!d.check("Km") && !d.check("KM")); - for (word, want) in [("Kmm", "km"), ("KMM", "km"), ("Km", "km")] { - let got = suggest_in(&d, word, 4, &Tuning::DEFAULT); - assert!(got.iter().all(|s| s.split(' ').all(|p| d.check(p))), "{word}: {got:?}"); - assert_eq!(got.first().map(String::as_str), Some(want), "{word}: {got:?}"); - } - assert_eq!(suggest_in(&d, "Pies", 4, &Tuning::DEFAULT), Vec::::new()); - assert_eq!(suggest_in(&d, "Piesx", 4, &Tuning::DEFAULT).first().map(String::as_str), Some("Pies")); - } - - /// A language's tables come after its dictionary's, a TRY alphabet stands in for a missing TRY - /// line, and a split rule decides which missing spaces are offered. - #[test] - fn tuning_adds_to_the_dictionary_tables() { - let d = AffixDict::from_hunspell("", "3\nčaj\nkot\nna\n", &[]).unwrap(); - let tuned = Tuning { map: &["cč"], alphabet: "t", rep: &[("xy", "kot")], ..Tuning::DEFAULT }; - assert!(!suggest_in(&d, "caj", 4, &Tuning::DEFAULT).contains(&"čaj".into())); - assert!(suggest_in(&d, "caj", 4, &tuned).contains(&"čaj".into()), "the language's MAP group"); - assert!(suggest_in(&d, "xy", 4, &tuned).contains(&"kot".into()), "the language's REP pair"); - assert!(suggest_in(&d, "ko", 4, &tuned).contains(&"kot".into()), "the language's TRY letters"); - let near = Tuning { split: Split::Near { leading: &["na"], trailing: &[] }, ..Tuning::DEFAULT }; - assert!(suggest_in(&d, "nakot", 4, &near).contains(&"na kot".into())); - assert!(!suggest_in(&d, "kotna", 4, &near).contains(&"kot na".into()), "no stray short half"); - assert!(suggest_in(&d, "kotna", 4, &Tuning::DEFAULT).contains(&"kot na".into())); - } -} diff --git a/crates/proof/src/bg.rs b/crates/proof/src/bg.rs deleted file mode 100644 index 6b456d8c9..000000000 --- a/crates/proof/src/bg.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Bulgarian hyphenation by rule ([`ProofLang::Bg`](crate::ProofLang::Bg)). -//! -//! Bulgarian spelling splits words at syllables, and its rules are regular enough to apply -//! directly, so no pattern file or word list is needed (and none is borrowed): -//! - two vowels next to each other may be split (`на-у-ка`); -//! - one consonant between vowels goes with the next syllable (`ма-ма`, `ро-за`); -//! - of two or more consonants between vowels, the first stays with the syllable before and the -//! rest go with the next (`сес-тра`, `въз-дух`, `ком-пю-тър`) — the spelling rules allow any -//! split of a consonant group, and this is the usual one — except that a consonant followed by -//! `р` or `л` starts the next syllable together with it (`до-бре`, `ко-пре`, `та-бли-ца`); -//! - `й` before a consonant stays with the vowel before it (`строй-ка`, `май-ка`); -//! - `ьо` is never split, and the consonant before `ь` goes with it (`шо-фьор`); -//! - `дж` and `дз`, one sound each, are never split (`хо-джа`). -//! -//! A single letter left at the end or start of a line is ruled out by the paragraph's -//! [`Limits`](crate::hyphen::Limits) (`Бъл-га-рия`, not `Бъл-га-ри-я`). - -fn is_vowel(c: char) -> bool { - matches!(c, 'а' | 'е' | 'и' | 'о' | 'у' | 'ъ' | 'ю' | 'я' | 'ѝ') -} - -/// Consonants that may start a syllable before `р` or `л` (`бр`, `пл`, `тр`, `гл` …). -fn is_obstruent(c: char) -> bool { - matches!(c, 'б' | 'в' | 'г' | 'д' | 'з' | 'к' | 'п' | 'с' | 'т' | 'ф' | 'х') -} - -/// A lowercase Bulgarian letter (the words [`points`] splits). -pub(crate) fn is_letter(c: char) -> bool { - matches!(c, 'а'..='я' | 'ѝ') -} - -/// Break points of one lowercase Bulgarian word (char indices; a hyphen goes before that char). -/// Empty for anything that is not entirely Bulgarian letters. -pub fn points(word: &[char]) -> Vec { - if word.len() < 2 || !word.iter().all(|&c| is_letter(c)) { - return Vec::new(); - } - let vowels: Vec = (0..word.len()).filter(|&i| is_vowel(word[i])).collect(); - let mut out = Vec::new(); - for pair in vowels.windows(2) { - let (v1, v2) = (pair[0], pair[1]); - let mut pt = match v2 - v1 - 1 { - 0 | 1 => v1 + 1, - _ => v1 + 2, - }; - // A consonant with `р` or `л` after it starts a syllable together with it. - if v2 - v1 > 2 && matches!(word[v2 - 1], 'р' | 'л') && is_obstruent(word[v2 - 2]) { - pt = v2 - 2; - } - // `дж`, `дз`: one sound, so both go to the next syllable. - if pt > v1 + 1 && word[pt - 1] == 'д' && matches!(word[pt], 'ж' | 'з') { - pt -= 1; - } - // `ь` keeps the consonant before it (`фьо`). - if pt < v2 && word[pt] == 'ь' && pt > v1 + 1 { - pt -= 1; - } - if pt > v1 && pt <= v2 && word[pt - 1] != 'ь' { - out.push(pt); - } - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - - fn split(w: &str) -> String { - let chars: Vec = w.chars().collect(); - let pts = points(&chars); - let mut s = String::new(); - for (i, c) in chars.iter().enumerate() { - if pts.contains(&i) { - s.push('-'); - } - s.push(*c); - } - s - } - - #[test] - fn syllables_follow_the_bulgarian_rules() { - for (word, expected) in [ - ("мама", "ма-ма"), - ("наука", "на-у-ка"), - ("сестра", "сес-тра"), - ("въздух", "въз-дух"), - ("компютър", "ком-пю-тър"), - ("стройка", "строй-ка"), - ("майка", "май-ка"), - ("шофьор", "шо-фьор"), - ("ходжа", "хо-джа"), - ("здравей", "здра-вей"), - ("плевен", "пле-вен"), - ("българия", "бъл-га-ри-я"), - ("сричкопренасяне", "срич-ко-пре-на-ся-не"), - ("добре", "до-бре"), - ("таблица", "та-бли-ца"), - ("отговор", "от-го-вор"), - ] { - assert_eq!(split(word), expected, "{word}"); - } - } - - #[test] - fn non_bulgarian_letters_are_left_alone() { - assert!(points(&"word".chars().collect::>()).is_empty()); - assert!(points(&"съобщение1".chars().collect::>()).is_empty()); - assert!(points(&"объём".chars().collect::>()).is_empty(), "Russian ё is not a Bulgarian letter"); - } -} diff --git a/crates/proof/src/cs/grammar.rs b/crates/proof/src/cs/grammar.rs deleted file mode 100644 index 26a14fcdf..000000000 --- a/crates/proof/src/cs/grammar.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Generic repeated-word and spacing checks with Czech messages. -//! No Czech-specific grammar, comma or sentence-capital rules are inferred. - -use crate::{Issue, IssueKind, ProofLang, words}; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang: ProofLang::Cs } -} - -pub fn check(text: &str) -> Vec { - let mut issues = Vec::new(); - repeated_words(text, &words(text), &mut issues); - spaces(text, &mut issues); - issues.sort_by_key(|i| (i.start, i.end)); - issues -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() && gap.chars().all(char::is_whitespace) && w0.chars().all(char::is_alphabetic) && w0.to_lowercase() == w1.to_lowercase() { - v.push(issue(*a0, *b1, format!("Opakované slovo: „{w1}“"), vec![w0.to_string()])); - } - } -} - -fn spaces(text: &str, v: &mut Vec) { - for (i, c) in text.char_indices() { - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - // A ratio/score keeps the spaces in digit-space-colon-space-digit. - let ratio = p == ':' - && text.get(..i).and_then(|before| before.chars().next_back()).is_some_and(|c| c.is_ascii_digit()) - && after == Some(' ') - && it.next().is_some_and(|c| c.is_ascii_digit()); - let flag = !ratio - && match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '”' | '’' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Mezera před interpunkcí".into(), vec![p.to_string()])); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Dvojitá mezera".into(), vec![" ".into()])); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn repeated_words_and_spaces_have_czech_messages() { - let text = "Město město , škola."; - let issues = check(text); - assert_eq!(issues.len(), 3, "{issues:?}"); - assert_eq!(issues[0].message, "Opakované slovo: „město“"); - assert_eq!(issues[0].suggestions, ["Město"]); - assert!(issues.iter().any(|i| i.message == "Mezera před interpunkcí" && &text[i.start..i.end] == " ,")); - assert!(issues.iter().any(|i| i.message == "Dvojitá mezera")); - assert!(issues.iter().all(|i| i.lang == ProofLang::Cs && i.kind == IssueKind::Grammar)); - for text in ["město. škola", "Dr. Novák", "A apple", "Město, město!", "Úsměv :) Číslo .5 a tečky ..."] { - assert!(check(text).is_empty(), "{text}"); - } - } - - #[test] - fn ratios_and_scores_keep_their_spaces() { - for text in ["3 : 1", "1 : 2", "Skóre 12 : 10.", "Poměr 1 : 100."] { - assert!(check(text).is_empty(), "{text}: {:?}", check(text)); - } - for text in ["Slovo : další", "3 : slovo", "slovo : 1", "3 :"] { - assert!(check(text).iter().any(|i| i.message == "Mezera před interpunkcí"), "{text}"); - } - } - - proptest::proptest! { - #[test] - fn never_panics_and_ranges_are_valid(s in "\\PC{0,80}") { - for issue in check(&s) { - proptest::prop_assert!(issue.start <= issue.end && issue.end <= s.len()); - proptest::prop_assert!(s.is_char_boundary(issue.start) && s.is_char_boundary(issue.end)); - } - } - } -} diff --git a/crates/proof/src/cs/mod.rs b/crates/proof/src/cs/mod.rs deleted file mode 100644 index 9f8bbd0a7..000000000 --- a/crates/proof/src/cs/mod.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Czech proofing using the installed Hunspell `cs_CZ` dictionary (never bundled). -//! Suggestions use its runtime MAP/REP/TRY/KEY tables. Generic grammar checks have Czech -//! messages; hyphenation keeps the existing fallback, without Czech pattern data. - -pub mod grammar; -pub(crate) mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// File names of a Czech Hunspell dictionary. -const NAMES: &[&str] = &["cs_CZ", "cs-CZ", "cs"]; - -/// The Czech dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Czech"); - -/// The Czech dictionary: the installed Hunspell `cs_CZ` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available (Czech spelling is off -/// then; see [`has_dictionary`]). -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Use this Czech dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to -/// UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is a Czech dictionary available (spelling is checked)? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Is `word` spelled correctly in Czech? Numbers, single letters, short words in capitals -/// (acronyms: `ČR`, `EU`), words with digits and words in other scripts are accepted, and so -/// is every word when no Czech dictionary is installed. -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool { - let Some(w) = crate::spelling::word_to_check(d, word, opts) else { - return true; - }; - if w.contains('-') { - return w.split('-').all(|p| p.chars().count() <= 1 || known(d, p)); - } - known(d, w) -} - -/// Is one word (no hyphens) in the dictionary or the user dictionary? -fn known(d: &AffixDict, w: &str) -> bool { - d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn dictionary_lookup_accepts_czech_file_names() { - let dir = std::env::temp_dir().join(format!("wordcraft-cs-aliases-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - for name in ["cs_CZ", "cs-CZ", "cs"] { - std::fs::write(dir.join(format!("{name}.aff")), test_dict::AFF).unwrap(); - std::fs::write(dir.join(format!("{name}.dic")), test_dict::DIC).unwrap(); - let (aff, dic) = crate::hunspell::find_in(&dir, NAMES).unwrap(); - let d = crate::hunspell::load(&aff, &dic).unwrap(); - assert!(is_correct_with(&d, "městě")); - std::fs::remove_file(aff).unwrap(); - std::fs::remove_file(dic).unwrap(); - } - std::fs::remove_dir(dir).unwrap(); - } - - #[test] - fn czech_words_affixes_chains_case_and_diacritics() { - test_dict::install(); - let d = dictionary(); - assert!(has_dictionary()); - assert_eq!(d.len(), 20); - assert_eq!(d.rule_count(), 8); - for w in [ - "město", - "města", - "městě", - "neměsta", - "rychle", - "rychlejší", - "učitel", - "učitelé", - "Praha", - "Praze", - "Město", - "POČÍTAČ", - "příliš", - "žluťoučký", - "kůň", - "úpěl", - "ďábelské", - "ódy", - "děkujeme", - "zpráva", - "zprava", - "škola-počítač", - ] { - assert!(is_correct(w), "{w}"); - } - for w in ["městu", "městoě", "rychleejší", "učiteléé", "praha", "mĚsto", "pocitac", "dekujem", "škola-pocitac"] { - assert!(!is_correct(w), "{w}"); - } - for c in "ěščřžýáíéůú".chars() { - assert!(test_dict::DIC.contains(c), "UTF-8 fixture covers {c}"); - } - } - - #[test] - fn forbidden_words_are_rejected_in_every_case() { - test_dict::install(); - for w in ["zqzakázané", "Zqzakázané", "ZQZAKÁZANÉ"] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn missing_dictionary_and_ignored_inputs_are_quiet() { - let empty = AffixDict::default(); - assert!(is_correct_with(&empty, "pocitac")); - assert!(install_dictionary("FLAG long\n", "1\nstrom\n").is_err()); - let d = AffixDict::from_hunspell(test_dict::AFF, test_dict::DIC, &[]).unwrap(); - for w in ["", "a", "2026", "ČR", "článek2", "日本語"] { - assert!(is_correct_with(&d, w), "{w}"); - } - } - - #[test] - fn user_dictionary_applies() { - test_dict::install(); - assert!(!is_correct("zqčeskévlastníslovo")); - crate::add_word("Zqčeskévlastníslovo"); - assert!(is_correct("zqčeskévlastníslovo")); - } - - #[test] - fn czech_dispatch_messages_options_and_mixed_spans() { - test_dict::install(); - assert!(crate::has_dictionary(Some("cs-CZ"))); - assert!(crate::is_correct_for("městě", Some("cze"))); - let text = "Město pocitac"; - let issues = crate::check_text(text, &[(0..text.len(), crate::ProofLang::Cs)]); - assert_eq!(issues.len(), 1, "{issues:?}"); - assert_eq!(&text[issues[0].start..issues[0].end], "pocitac"); - assert_eq!(issues[0].message, "Možná pravopisná chyba"); - assert_eq!(issues[0].lang, crate::ProofLang::Cs); - assert!(crate::suggest_in("pocitac", 8, crate::ProofLang::Cs).contains(&"počítač".into())); - let text = "Město this is is wrong"; - let issues = crate::check_text(text, &[(0..6, crate::ProofLang::Cs), (7..text.len(), crate::ProofLang::En)]); - assert!(issues.iter().any(|i| i.lang == crate::ProofLang::En && i.message.starts_with("Repeated word"))); - assert!(issues.iter().all(|i| i.start >= 7)); - let opts = crate::Options { ignore_uppercase: false, ..Default::default() }; - assert!(!crate::is_correct_in_with("ZQZAKÁZANÉ", crate::ProofLang::Cs, &opts)); - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Czech checks - /// those words against the dictionary instead of accepting them. - #[test] - fn proofing_options_reach_the_czech_check() { - test_dict::install(); - let lang = crate::ProofLang::Cs; - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("ŠKULA", lang, &defaults), "short capitals are acronyms by default"); - assert!(!crate::is_correct_in_with("ŠKULA", lang, &check_caps)); - assert!(crate::is_correct_in_with("ŠKOLA", lang, &check_caps)); - assert!(crate::is_correct_in_with("škula2", lang, &defaults)); - assert!(!crate::is_correct_in_with("škula2", lang, &check_digits)); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("ŠKULA"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/cs/suggest.rs b/crates/proof/src/cs/suggest.rs deleted file mode 100644 index 05eba74a8..000000000 --- a/crates/proof/src/cs/suggest.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Czech suggestions: the shared ranking ([`crate::affix_suggest`]) with the installed -//! dictionary's own MAP/REP/TRY/KEY tables and nothing Czech hard-coded. - -use crate::affix::AffixDict; -use crate::affix_suggest::Tuning; - -/// Spelling suggestions for a Czech word, best first, at most `max`, in the word's letter case. -pub fn suggest(word: &str, max: usize) -> Vec { - suggest_in(&super::dictionary(), word, max) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize) -> Vec { - crate::affix_suggest::suggest_in(d, word, max, &Tuning::DEFAULT) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn suggestions_are_fast_enough() { - crate::cs::test_dict::install(); - let _ = suggest("město", 1); - let t = std::time::Instant::now(); - for word in ["zqčeskévelmidlouhéslovo", "pocitacz", "nejrychllejší"] { - let _ = suggest(word, 6); - } - let ms = t.elapsed().as_secs_f64() * 1000.0; - eprintln!("3 Czech suggestion calls: {ms:.0} ms"); - assert!(ms < 20_000.0, "{ms} ms"); - } - - #[test] - fn nosuggest_stems_and_forms_are_never_offered() { - crate::affix::tests::assert_nosuggest_is_honoured(suggest_in); - } - - #[test] - fn czech_suggestions_use_runtime_tables_and_case() { - crate::cs::test_dict::install(); - for (word, want) in [ - ("mesto", "město"), - ("mňe", "mě"), - ("xůž", "nůž"), - ("škloa", "škola"), - ("pocitac", "počítač"), - ("Mesto", "Město"), - ("praha", "Praha"), - ("toškola", "to škola"), - ] { - assert!(suggest(word, 8).contains(&want.into()), "{word}: {:?}", suggest(word, 8)); - } - // Each table changes the answer without a hard-coded Czech alphabet or confusion list. - for (table, wrong, right) in - [("MAP 1\nMAP cč\n", "caj", "čaj"), ("REP 1\nREP xy město\n", "xy", "město"), ("TRY š\n", "kola", "škola"), ("KEY xn\n", "xůž", "nůž")] - { - let dic = format!("1\n{right}\n"); - let d = AffixDict::from_hunspell(table, &dic, &[]).unwrap(); - assert!(suggest_in(&d, wrong, 4).contains(&right.into()), "{table}"); - let without = AffixDict::from_hunspell("", &dic, &[]).unwrap(); - assert!(!suggest_in(&without, wrong, 4).contains(&right.into()), "must depend on {table}"); - } - } - - #[test] - fn suggestions_stay_bounded_and_skip_forbidden_words() { - crate::cs::test_dict::install(); - for w in ["", "dvě slova", &"ě".repeat(41)] { - assert!(suggest(w, 4).is_empty()); - } - assert!(suggest("mesto", 0).is_empty()); - assert!(suggest_in(&AffixDict::default(), "mesto", 4).is_empty()); - assert!(suggest("město", 4).len() <= 4); - assert!(!suggest("město", 4).contains(&"město".into())); - assert!(!suggest("zqzakázane", 8).contains(&"zqzakázané".into())); - } -} diff --git a/crates/proof/src/dict.rs b/crates/proof/src/dict.rs deleted file mode 100644 index 81eb0de1b..000000000 --- a/crates/proof/src/dict.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Hyphenation exception dictionary: lowercase words with their break points. -//! -//! On-disk format (`assets/hyphenation/en-us.dic`): raw-deflate of -//! `"DCHYPH1\n"` followed by one line per word, sorted by the plain word's bytes. Each line is -//! front-coded against the previous one: a byte with the shared prefix length (in bytes of the -//! hyphenated form), then the rest of the hyphenated form (`-` at every break), then `\n`. - -/// Sorted word list with a break bitmask per word (bit `i` = hyphen before char `i`). -#[derive(Clone, Debug, Default)] -pub struct Dictionary { - words: String, - offs: Vec, - masks: Vec, -} - -const MAGIC: &[u8] = b"DCHYPH1\n"; -/// Largest inflated dictionary, and most bytes of words kept from one (the bundled US English one -/// is about 1 MB). -const MAX_BYTES: usize = 64 << 20; -/// Most words read from one dictionary (the bundled one has about 160k). -const MAX_ENTRIES: usize = 4_000_000; - -impl Dictionary { - /// Build from `(lowercase word, break mask)` pairs (sorted and de-duplicated here; the first - /// entry of a duplicate wins). - pub fn from_entries(mut entries: Vec<(String, u64)>) -> Dictionary { - entries.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes())); - entries.dedup_by(|b, a| a.0 == b.0); - let mut d = Dictionary { words: String::new(), offs: vec![0], masks: Vec::with_capacity(entries.len()) }; - for (w, m) in entries { - d.words.push_str(&w); - d.offs.push(d.words.len() as u32); - d.masks.push(m); - } - d - } - - pub fn len(&self) -> usize { - self.masks.len() - } - - pub fn is_empty(&self) -> bool { - self.masks.is_empty() - } - - fn word(&self, i: usize) -> &str { - &self.words[self.offs[i] as usize..self.offs[i + 1] as usize] - } - - /// Break mask of an exact (already lowercased) word. - pub fn get(&self, word: &str) -> Option { - let (mut lo, mut hi) = (0usize, self.len()); - while lo < hi { - let mid = (lo + hi) / 2; - match self.word(mid).as_bytes().cmp(word.as_bytes()) { - std::cmp::Ordering::Less => lo = mid + 1, - std::cmp::Ordering::Greater => hi = mid, - std::cmp::Ordering::Equal => return Some(self.masks[mid]), - } - } - None - } - - /// Iterate `(word, mask)`. - pub fn iter(&self) -> impl Iterator + '_ { - (0..self.len()).map(|i| (self.word(i), self.masks[i])) - } - - /// Serialize (deflate-compressed). - pub fn to_bytes(&self) -> Vec { - let mut raw = MAGIC.to_vec(); - let mut prev: Vec = Vec::new(); - for (w, m) in self.iter() { - let h = hyphenated(w, m).into_bytes(); - let p = prev.iter().zip(&h).take_while(|(a, b)| a == b).count().min(255); - raw.push(p as u8); - raw.extend_from_slice(&h[p..]); - raw.push(b'\n'); - prev = h; - } - miniz_oxide::deflate::compress_to_vec(&raw, 10) - } - - /// Deserialize data written by [`Dictionary::to_bytes`]. Data that inflates past - /// [`MAX_BYTES`], or holds more than [`MAX_ENTRIES`] words or [`MAX_BYTES`] of them, is refused. - pub fn from_bytes(data: &[u8]) -> Result { - Dictionary::from_bytes_within(data, MAX_BYTES, MAX_ENTRIES) - } - - /// [`Dictionary::from_bytes`] with the limits given. - fn from_bytes_within(data: &[u8], max_bytes: usize, max_entries: usize) -> Result { - let raw = miniz_oxide::inflate::decompress_to_vec_with_limit(data, max_bytes) - .map_err(|e| format!("hyphenation dictionary: inflate failed: {:?}", e.status))?; - let body = raw.strip_prefix(MAGIC).ok_or("hyphenation dictionary: bad header")?; - let mut d = Dictionary { words: String::with_capacity(body.len()), offs: vec![0], masks: Vec::new() }; - let mut cur: Vec = Vec::new(); - let mut i = 0; - while i < body.len() { - let p = body[i] as usize; - let end = body[i + 1..].iter().position(|&b| b == b'\n').map(|e| i + 1 + e).ok_or("hyphenation dictionary: truncated")?; - if p > cur.len() { - return Err("hyphenation dictionary: bad prefix".into()); - } - cur.truncate(p); - cur.extend_from_slice(&body[i + 1..end]); - i = end + 1; - let s = std::str::from_utf8(&cur).map_err(|_| "hyphenation dictionary: bad utf-8")?; - let (w, m) = parse_hyphenated(s); - // Front coding lets a short line repeat a long prefix: bound what is kept, not only - // what is read. - if d.masks.len() >= max_entries || d.words.len() + w.len() > max_bytes { - return Err("hyphenation dictionary: too large".into()); - } - d.words.push_str(&w); - d.offs.push(d.words.len() as u32); - d.masks.push(m); - } - Ok(d) - } -} - -/// `word` with `-` before every char whose bit is set in `mask`. -pub fn hyphenated(word: &str, mask: u64) -> String { - let mut s = String::with_capacity(word.len() + 8); - for (i, c) in word.chars().enumerate() { - if i > 0 && i < 64 && mask & (1 << i) != 0 { - s.push('-'); - } - s.push(c); - } - s -} - -/// Inverse of [`hyphenated`]. -pub fn parse_hyphenated(s: &str) -> (String, u64) { - let mut w = String::with_capacity(s.len()); - let mut m = 0u64; - let mut n = 0; - for c in s.chars() { - if c == '-' { - if n > 0 && n < 64 { - m |= 1 << n; - } - } else { - w.push(c); - n += 1; - } - } - (w, m) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn roundtrip() { - let e: Vec<(String, u64)> = ["ty-pog-ra-phy", "cat", "hap-pen", "o'clock", "naïve-té", "naïve"].iter().map(|s| parse_hyphenated(s)).collect(); - let d = Dictionary::from_entries(e); - let back = Dictionary::from_bytes(&d.to_bytes()).unwrap(); - assert_eq!(back.len(), 6); - assert_eq!(back.get("typography").map(|m| hyphenated("typography", m)), Some("ty-pog-ra-phy".to_string())); - assert_eq!(back.get("cat"), Some(0)); - assert_eq!(back.get("naïveté").map(|m| hyphenated("naïveté", m)), Some("naïve-té".to_string())); - assert!(back.get("dog").is_none()); - } - - #[test] - fn oversized_data_is_refused() { - const LIMIT: usize = 1 << 16; - let within = |raw: &[u8]| Dictionary::from_bytes_within(&miniz_oxide::deflate::compress_to_vec(raw, 6), LIMIT, 1000); - // Data that inflates past the limit is refused before anything is decoded. - let mut raw = MAGIC.to_vec(); - raw.resize(LIMIT + 1, b'a'); - assert!(within(&raw).unwrap_err().contains("inflate failed")); - // Short lines that each repeat a long prefix: small to inflate, but the words they spell - // out would be ~100x larger. Kept storage is bounded too. - let mut raw = MAGIC.to_vec(); - raw.push(0); - raw.extend_from_slice(&[b'a'; 255]); - raw.push(b'\n'); - for _ in 0..(LIMIT / 255 + 2) { - raw.extend_from_slice(&[255, b'\n']); - } - assert!(raw.len() < LIMIT); - assert!(within(&raw).unwrap_err().contains("too large")); - // So is the number of words. - let words: Vec<(String, u64)> = (0..1001).map(|i| (format!("w{i}"), 0)).collect(); - let raw = miniz_oxide::inflate::decompress_to_vec(&Dictionary::from_entries(words).to_bytes()).unwrap(); - assert!(within(&raw).unwrap_err().contains("too large")); - // The real limits leave the bundled dictionary whole. - assert!(Dictionary::en_us().len() > 100_000); - } -} diff --git a/crates/proof/src/en_variants.rs b/crates/proof/src/en_variants.rs deleted file mode 100644 index 56b9f26f5..000000000 --- a/crates/proof/src/en_variants.rs +++ /dev/null @@ -1,277 +0,0 @@ -//! American and British spellings of the same English word (#1281, #1045): which forms each -//! English ([`ProofLang::En`], [`ProofLang::EnCa`], [`ProofLang::EnGb`]) accepts, which it flags, -//! and what it suggests instead (`colour` → `color` in American English). -//! -//! The pairs come from `assets/spelling/en-variants.txt`, our own list of base forms with a -//! policy per English. Inflections are generated here by rule for the regular patterns (-or/-our, -//! -er/-re, -ize/-ise, -ense/-ence…), so `colours`, `centred` and `organisation` follow their -//! base forms. A form is right when the shared English word list knows it or knows its other -//! spelling (`realisation` is right in British English because `realization` is a word), so the -//! rules never make a misspelling right. - -use std::collections::HashMap; -use std::sync::OnceLock; - -use crate::ProofLang; - -/// Our list of American/British pairs: `POLICY AMERICAN BRITISH` per line, `#` comments. -const VARIANTS: &str = include_str!("../../../assets/spelling/en-variants.txt"); - -/// What one English does with a pair's two spellings. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum Policy { - /// Only the American spelling is right. - American, - /// Only the British spelling is right. - British, - /// Both are right. - Both, -} - -impl Policy { - fn from_char(c: char) -> Option { - match c { - 'u' => Some(Policy::American), - 'b' => Some(Policy::British), - '*' => Some(Policy::Both), - _ => None, - } - } -} - -/// One line of the list: the policies for American, Canadian and British-spelling English, and -/// the two base forms. -struct Pair { - policy: [Policy; 3], - american: &'static str, - british: &'static str, -} - -/// The lines of [`VARIANTS`]; malformed lines are skipped (a test keeps the file clean). -fn pairs() -> &'static [Pair] { - static P: OnceLock> = OnceLock::new(); - P.get_or_init(|| VARIANTS.lines().filter_map(parse_line).collect()) -} - -fn parse_line(line: &'static str) -> Option { - let line = line.trim(); - if line.is_empty() || line.starts_with('#') { - return None; - } - let mut parts = line.split_whitespace(); - let (policy, american, british) = (parts.next()?, parts.next()?, parts.next()?); - if parts.next().is_some() || american == british { - return None; - } - let mut p = policy.chars().map(Policy::from_char); - let policy = [p.next()??, p.next()??, p.next()??]; - if p.next().is_some() { - return None; - } - let word = |w: &str| !w.is_empty() && w.chars().all(|c| c.is_ascii_lowercase()); - (word(american) && word(british)).then_some(Pair { policy, american, british }) -} - -/// The column of a pair's policy for `lang`; `None` for a language that isn't English. -fn column(lang: ProofLang) -> Option { - match lang { - ProofLang::En => Some(0), - ProofLang::EnCa => Some(1), - ProofLang::EnGb => Some(2), - _ => None, - } -} - -/// `word` with `suffix`: a final `e` drops before a vowel (`centre` + `ing` → `centring`), a -/// final consonant + `y` becomes `i` (`cosy` + `er` → `cosier`), `s` is the plural (`es` after a -/// sibilant, `ies` after a consonant + `y`). -fn add(word: &str, suffix: &str) -> String { - let b = word.as_bytes(); - let last = b.last().copied().unwrap_or(b'_'); - let before = b.len().checked_sub(2).and_then(|i| b.get(i)).copied().unwrap_or(b'_'); - let vowel = |c: u8| b"aeiou".contains(&c); - let head = word.get(..word.len().saturating_sub(1)).unwrap_or(word); - if suffix == "s" { - if last == b'y' && !vowel(before) { - return format!("{head}ies"); - } - if matches!(last, b's' | b'x' | b'z') || word.ends_with("ch") || word.ends_with("sh") { - return format!("{word}es"); - } - return format!("{word}s"); - } - let suffix_vowel = suffix.as_bytes().first().is_some_and(|&c| vowel(c)); - if last == b'e' && suffix_vowel { - return format!("{head}{suffix}"); - } - if last == b'y' && !vowel(before) && !suffix.starts_with('i') { - return format!("{head}i{suffix}"); - } - format!("{word}{suffix}") -} - -/// The suffixes both spellings of a pair take, by the pattern that tells them apart. -fn suffixes(american: &str, british: &str) -> &'static [&'static str] { - // Not -al, -ist or -ite: `humoral` and `humorist` are spelled so in British English too, and - // `behavioural` and `favourite` have lines of their own. - const OUR: &[&str] = &["s", "ed", "ing", "er", "ers", "ful", "fully", "less", "able", "ably"]; - const RE: &[&str] = &["s", "ed", "ing"]; - const IZE: &[&str] = &["s", "ed", "ing", "er", "ers", "ation", "ations", "ational", "able"]; - const E: &[&str] = &["s", "ed", "ing", "er", "ers"]; - const Y: &[&str] = &["s", "er", "est", "ly", "ness"]; - const PLURAL: &[&str] = &["s"]; - let stem = |w: &str, n: usize| w.get(..w.len().saturating_sub(n)).map(str::to_string); - if american.ends_with("or") && british.ends_with("our") && stem(american, 2) == stem(british, 3) { - return OUR; - } - if american.ends_with("er") && british.ends_with("re") && stem(american, 2) == stem(british, 2) { - return RE; - } - // One letter apart (`organize`/`organise`, `defense`/`defence`, `cozy`/`cosy`). - let differ = american.len() == british.len() && american.bytes().zip(british.bytes()).filter(|(a, b)| a != b).count() == 1; - if differ && american.ends_with('e') && british.ends_with('e') { - let ize = american.ends_with("ize") || american.ends_with("yze"); - return if ize { IZE } else { E }; - } - if differ && american.ends_with('y') && british.ends_with('y') { - return Y; - } - PLURAL -} - -/// One English's forms: those it flags (each with the form to suggest), and those it accepts -/// when the shared list knows them or their other spelling (each with that other spelling). -#[derive(Default)] -struct Forms { - foreign: HashMap, - accepted: HashMap, -} - -fn forms(lang: ProofLang) -> Option<&'static Forms> { - static F: [OnceLock; 3] = [OnceLock::new(), OnceLock::new(), OnceLock::new()]; - let col = column(lang)?; - Some(F.get(col)?.get_or_init(|| build(col))) -} - -fn build(col: usize) -> Forms { - let mut f = Forms::default(); - for pair in pairs() { - let Some(&policy) = pair.policy.get(col) else { continue }; - let mut both = vec![(pair.american.to_string(), pair.british.to_string())]; - both.extend(suffixes(pair.american, pair.british).iter().map(|s| (add(pair.american, s), add(pair.british, s)))); - for (am, br) in both { - if am == br { - continue; - } - match policy { - Policy::American => { - f.foreign.insert(br.clone(), am.clone()); - f.accepted.insert(am, br); - } - Policy::British => { - f.foreign.insert(am.clone(), br.clone()); - f.accepted.insert(br, am); - } - Policy::Both => { - f.accepted.insert(am.clone(), br.clone()); - f.accepted.insert(br, am); - } - } - } - } - // A form one pair accepts is never flagged by another, and `analyses`, `paralyses`… are also - // the plurals of `analysis`, `paralysis`… everywhere. - f.foreign.retain(|w, _| !f.accepted.contains_key(w) && !w.ends_with("yses")); - f -} - -/// `lower` without a possessive `'s`. -fn without_possessive(lower: &str) -> &str { - lower.strip_suffix("'s").filter(|s| !s.is_empty()).unwrap_or(lower) -} - -/// The spelling `lang` wants instead of `lower` (a lower-case word, `'` apostrophes), when -/// `lower` is the other dialect's spelling of a word (`colour` in American English: `color`). -/// A possessive keeps its `'s` (`colour's` → `color's`). -pub fn preferred(lower: &str, lang: ProofLang) -> Option { - let f = forms(lang)?; - let base = without_possessive(lower); - let native = f.foreign.get(base)?; - Some(if base.len() < lower.len() { format!("{native}'s") } else { native.clone() }) -} - -/// Is `lower` right in `lang` because the shared word list knows its other spelling (`known` -/// says what the list knows)? `realisation` in British English, `organization` in all three. -pub fn known_variant(lower: &str, lang: ProofLang, known: impl Fn(&str) -> bool) -> bool { - let Some(f) = forms(lang) else { return false }; - let base = without_possessive(lower); - f.accepted.get(base).is_some_and(|other| known(other)) -} - -/// Is `lower` the other dialect's spelling in `lang` (flagged even though it is a word)? -pub fn is_foreign(lower: &str, lang: ProofLang) -> bool { - forms(lang).is_some_and(|f| f.foreign.contains_key(without_possessive(lower))) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn every_line_parses() { - let lines = VARIANTS.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')).count(); - assert_eq!(pairs().len(), lines, "a malformed line in en-variants.txt"); - assert!(pairs().len() > 250); - } - - #[test] - fn suffixes_follow_the_pattern() { - assert_eq!(add("centre", "ing"), "centring"); - assert_eq!(add("centre", "ed"), "centred"); - assert_eq!(add("center", "ed"), "centered"); - assert_eq!(add("organise", "ation"), "organisation"); - assert_eq!(add("cosy", "er"), "cosier"); - assert_eq!(add("cosy", "s"), "cosies"); - assert_eq!(add("armoury", "s"), "armouries"); - assert_eq!(add("analyse", "s"), "analyses"); - assert_eq!(add("colour", "ful"), "colourful"); - assert_eq!(add("", "s"), "s"); - assert_eq!(suffixes("color", "colour").len(), 10); - assert_eq!(suffixes("center", "centre"), ["s", "ed", "ing"]); - assert!(suffixes("organize", "organise").contains(&"ation")); - assert_eq!(suffixes("program", "programme"), ["s"], "no -ed: programmed is American too"); - } - - #[test] - fn no_form_is_both_flagged_and_accepted() { - for lang in [ProofLang::En, ProofLang::EnCa, ProofLang::EnGb] { - let Some(f) = forms(lang) else { panic!("{lang:?}") }; - for (w, native) in &f.foreign { - assert!(!f.accepted.contains_key(w), "{lang:?}: {w}"); - assert!(!f.foreign.contains_key(native), "{lang:?}: suggests {native}, itself flagged"); - } - } - // Words both dialects use alike are never flagged. - for w in [ - "programmed", - "tired", - "tires", - "curbs", - "humorous", - "honorary", - "humorist", - "humoral", - "meter", - "license", - "for", - "tour", - "analyses", - "paralyses", - ] { - for lang in [ProofLang::En, ProofLang::EnCa, ProofLang::EnGb] { - assert!(!is_foreign(w, lang), "{lang:?}: {w}"); - } - } - assert!(forms(ProofLang::Pl).is_none() && !is_foreign("color", ProofLang::Other)); - } -} diff --git a/crates/proof/src/es/grammar.rs b/crates/proof/src/es/grammar.rs deleted file mode 100644 index 614a1f07b..000000000 --- a/crates/proof/src/es/grammar.rs +++ /dev/null @@ -1,391 +0,0 @@ -//! Spanish grammar checks with Spanish messages: the generic repeated-word and spacing checks, -//! and the opening question and exclamation marks Spanish writes before a question or an -//! exclamation (`¿Vienes?`, `¡Qué bien!`): a closing `?` or `!` whose sentence has no opening mark -//! is flagged, with the opening mark offered at the sentence's start, and so is a space after an -//! opening mark (`¿ Vienes?`). Either opening mark may pair with either closing one (`¡Qué -//! dices?`), and `(?)` and `(!)`, which mark doubt or surprise, stand alone. No other -//! Spanish-specific grammar, comma or sentence-capital rules are inferred. - -use unicode_normalization::UnicodeNormalization; - -use crate::{Issue, IssueKind, ProofLang, words}; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec, lang: ProofLang) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang } -} - -/// Grammar issues in a paragraph of Spanish text; `lang` is the variety (they explain the same in -/// every one). -pub fn check(text: &str, lang: ProofLang) -> Vec { - let mut issues = Vec::new(); - repeated_words(text, &words(text), lang, &mut issues); - spaces(text, lang, &mut issues); - opening_marks(text, lang, &mut issues); - issues.sort_by_key(|i| (i.start, i.end)); - issues -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], lang: ProofLang, v: &mut Vec) { - // Letters and the combining marks of a decomposed word (`tambie\u{301}n`); a composed and a - // decomposed spelling of one word are the same word. - let letters = |w: &str| w.chars().all(|c| c.is_alphabetic() || unicode_normalization::char::is_combining_mark(c)); - let folded = |w: &str| w.nfc().collect::().to_lowercase(); - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() && gap.chars().all(char::is_whitespace) && letters(w0) && folded(w0) == folded(w1) { - v.push(issue(*a0, *b1, format!("Palabra repetida: «{w1}»"), vec![w0.to_string()], lang)); - } - } -} - -fn spaces(text: &str, lang: ProofLang, v: &mut Vec) { - for (i, c) in text.char_indices() { - // A space after an opening mark: `¿ Vienes?`. - if matches!(c, '¿' | '¡') - && text.get(i + c.len_utf8()..).is_some_and(|r| r.starts_with(' ') && r.trim_start().chars().next().is_some_and(char::is_alphanumeric)) - { - v.push(issue(i, i + c.len_utf8() + 1, "Espacio después de un signo de apertura".into(), vec![c.to_string()], lang)); - continue; - } - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - // A ratio/score keeps the spaces in digit-space-colon-space-digit. - let ratio = p == ':' - && text.get(..i).and_then(|before| before.chars().next_back()).is_some_and(|c| c.is_ascii_digit()) - && after == Some(' ') - && it.next().is_some_and(|c| c.is_ascii_digit()); - let flag = !ratio - && match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '”' | '’' | '»' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Espacio antes de un signo de puntuación".into(), vec![p.to_string()], lang)); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Espacio doble".into(), vec![" ".into()], lang)); - } -} - -/// Does the character `b` at byte `j` end what comes before it, so a question or exclamation after -/// it starts there? Sentence ends, clause breaks, line breaks and the start of a quotation or -/// parenthesis do, but not punctuation inside a word (`3.5`, `10:30`) or inside an address -/// (`example.com`, `https:`), except a mark ending an address (`…/? Vienes mañana?`). -fn ends_clause(text: &str, addresses: &[(usize, usize)], j: usize, b: char) -> bool { - let after = j + b.len_utf8(); - if let Some((_, end)) = address_at(addresses, j) { - return matches!(b, '?' | '!' | '.') && end == after; - } - let inside_word = matches!(b, '.' | ':' | ';') && text.get(after..).and_then(|r| r.chars().next()).is_some_and(char::is_alphanumeric); - !inside_word && matches!(b, '.' | '?' | '!' | '…' | ':' | ';' | '\n' | '\r' | '\t' | '«' | '“' | '"' | '(' | '[' | '—') -} - -/// The one of the `addresses` ([`address_ranges`]) that byte `j` is inside, if any. The ranges come -/// in text order and never overlap, so this is a binary search, O(log m) for m addresses: a paragraph -/// of thousands of addresses costs O(n log m) when every character before a question mark asks -/// (#3562 review), not the O(n m) of a scan per character. -fn address_at(addresses: &[(usize, usize)], j: usize) -> Option<(usize, usize)> { - let after_start = addresses.partition_point(|&(s, _)| s <= j); - let (s, e) = *addresses.get(after_start.checked_sub(1)?)?; - (s <= j && j < e).then_some((s, e)) -} - -/// The byte ranges of the whitespace-delimited tokens of `text` that are web or e-mail addresses -/// (`https://example.com/?`, `www.example.com`, `ana@example.com`), as spelling skips them. -fn address_ranges(text: &str) -> Vec<(usize, usize)> { - let mut out = Vec::new(); - let mut start = None; - // A final space ends the last token. - for (i, c) in text.char_indices().chain(std::iter::once((text.len(), ' '))) { - if !c.is_whitespace() { - start = start.or(Some(i)); - } else if let Some(s) = start.take() - && text.get(s..i).is_some_and(crate::is_address) - { - out.push((s, i)); - } - } - out -} - -/// A closing `?` or `!` with no opening `¿` or `¡` before it. Openers are counted left to right, like -/// brackets, and each closing mark takes one back (either opener pairs with either closer: -/// `¡Qué dices?`), so what sits between them doesn't matter: quotes, parentheses, dashes, an -/// ellipsis or an abbreviation's full stop (`¿Viene el Sr. García?`, `¿Leíste «Rayuela»?`) all belong -/// to the question. Only a closing mark with nothing open is flagged, and then the sentence it -/// closes starts after the last clause break ([`ends_clause`]). Marks in web and e-mail addresses -/// aren't sentence punctuation, and neither is punctuation inside an address or a word -/// (`example.com`, `3.5`, `10:30`) a sentence's start. -fn opening_marks(text: &str, lang: ProofLang, v: &mut Vec) { - let addresses = address_ranges(text); - // Openers not closed yet: a run of them (`¡¡`) counts once, like a run of closers (`!!`). - let mut open = 0usize; - for (i, c) in text.char_indices() { - let before = text.get(..i).unwrap_or(""); - let prev = before.chars().next_back(); - let (opener, message) = match c { - '¿' | '¡' => { - if !matches!(prev, Some('¿' | '¡')) { - open += 1; - } - continue; - } - '?' => ('¿', "Falta el signo de interrogación de apertura (¿)"), - '!' => ('¡', "Falta el signo de exclamación de apertura (¡)"), - _ => continue, - }; - let next = text.get(i + 1..).and_then(|r| r.chars().next()); - // `?!` and `!!!` are handled at their first mark; `(?)` stands alone; `?q=`, `a!b` and a - // mark ending an address (`https://example.com/?`) aren't sentence ends. - if matches!(prev, Some('?' | '!')) - || (prev == Some('(') && next == Some(')')) - || next.is_some_and(char::is_alphanumeric) - || address_at(&addresses, i).is_some() - { - continue; - } - if open > 0 { - open -= 1; - continue; - } - let from = before.char_indices().rev().find(|&(j, b)| matches!(b, '¿' | '¡') || ends_clause(text, &addresses, j, b)); - let start = from.map_or(0, |(j, b)| j + b.len_utf8()); - let Some(sentence) = text.get(start..i) else { continue }; - let lead = sentence.len() - sentence.trim_start().len(); - let start = start + lead; - let Some(sentence) = text.get(start..i + 1) else { continue }; - if !sentence.chars().any(char::is_alphabetic) { - continue; - } - v.push(issue(start, i + 1, message.into(), vec![format!("{opener}{sentence}")], lang)); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn messages(text: &str) -> Vec<(String, String)> { - check(text, ProofLang::Es).into_iter().map(|i| (text[i.start..i.end].to_string(), i.message)).collect() - } - - /// The missing-opening-mark issues of `text`: the flagged text and the suggestions offered. - fn missing(text: &str) -> Vec<(String, Vec)> { - check(text, ProofLang::Es) - .into_iter() - .filter(|i| i.message.starts_with("Falta")) - .map(|i| (text[i.start..i.end].to_string(), i.suggestions)) - .collect() - } - - #[test] - fn repeated_words_and_spaces_have_spanish_messages() { - let text = "Es es la casa , dijo."; - let issues = check(text, ProofLang::EsMx); - assert_eq!(issues.len(), 3, "{issues:?}"); - assert_eq!(issues[0].message, "Palabra repetida: «es»"); - assert_eq!(issues[0].suggestions, ["Es"]); - assert!(issues.iter().any(|i| i.message == "Espacio antes de un signo de puntuación" && &text[i.start..i.end] == " ,")); - assert!(issues.iter().any(|i| i.message == "Espacio doble")); - assert!(issues.iter().all(|i| i.lang == ProofLang::EsMx && i.kind == IssueKind::Grammar)); - for text in ["La casa. Es grande", "Sr. García", "No, no lo sé", "Hola :) número .5 y puntos ...", "3 : 1", "teórico-práctico"] { - assert!(check(text, ProofLang::Es).is_empty(), "{text}: {:?}", check(text, ProofLang::Es)); - } - } - - #[test] - fn questions_and_exclamations_need_their_opening_mark() { - assert_eq!(messages("Vienes mañana?"), [("Vienes mañana?".into(), "Falta el signo de interrogación de apertura (¿)".into())]); - assert_eq!(check("Vienes mañana?", ProofLang::Es)[0].suggestions, ["¿Vienes mañana?"]); - assert_eq!(messages("Llegó. Qué bien!"), [("Qué bien!".into(), "Falta el signo de exclamación de apertura (¡)".into())]); - assert_eq!(messages("Le preguntó: vienes?")[0].0, "vienes?"); - assert_eq!(messages("Dijo «vienes?»")[0].0, "vienes?"); - assert_eq!(messages("Hola!!!").len(), 1); - assert_eq!(messages("Qué?!").len(), 1); - for text in [ - "¿Vienes mañana?", - "¡Qué bien!", - "Si vienes, ¿me llamas?", - "¡Qué dices?", - "¿Qué?! ¡No!", - "Nació en 1950 (?) en Montevideo.", - "Lo dijo (!) en público.", - "Ver https://example.com/?q=1 y a!b", - "¿Qué? ¿Cómo?", - "2 + 2 = ?", - "", - "?", - ] { - assert!(messages(text).iter().all(|(_, m)| !m.starts_with("Falta")), "{text}: {:?}", messages(text)); - } - } - - /// Maintainer review on #3562: a URL ending in `/?` isn't a question, and the dots and colons - /// of an address or a word (`example.com`, `https:`, `10:30`) don't start the sentence the - /// missing `¿` is offered for; ordinary questions are still checked. - #[test] - fn addresses_are_not_questions_or_sentence_starts() { - for text in [ - "Ver https://example.com/?", - "Ver https://example.com/? ¿Vienes?", - "Ver http://example.com/buscar/? y www.example.com/?", - "Escribí a ana@example.com!", - "¿Viste https://example.com/?", - ] { - assert!(missing(text).is_empty(), "{text}: {:?}", missing(text)); - } - assert_eq!( - missing("Mirá https://example.com/ y decime si vienes?"), - [("Mirá https://example.com/ y decime si vienes?".to_string(), vec!["¿Mirá https://example.com/ y decime si vienes?".to_string()])] - ); - assert_eq!(missing("Mirá example.com y decime si vienes?")[0].0, "Mirá example.com y decime si vienes?"); - assert_eq!(missing("Son las 10:30, vienes?")[0].0, "Son las 10:30, vienes?"); - assert_eq!(missing("Ver https://example.com/? Vienes mañana?"), [("Vienes mañana?".to_string(), vec!["¿Vienes mañana?".to_string()])]); - assert_eq!(missing("Llegó. Vienes?")[0].0, "Vienes?", "a full stop before a space still ends a sentence"); - } - - /// Maintainer review on #3562: a correctly opened question or exclamation is never flagged, - /// whatever sits between its marks: quotes, an abbreviation's full stop, an ellipsis, a dash - /// aside, parentheses, nested questions, a line break. - #[test] - fn correctly_opened_questions_are_not_flagged() { - for text in [ - "¿Leíste «Rayuela»?", - "¿Viene el Sr. García?", - "¿Y si... no viene?", - "¿Vendrás —preguntó Juan— mañana?", - "¿Qué significa (aquí) la palabra?", - "¿Has leído \"Rayuela\" de Cortázar?", - "¿Leíste “Rayuela”?", - "¿Es 'Rayuela' o 'Los premios'?", - "¿Y tú, qué opinas… vienes?", - "¿Dijo «¿qué?» o no?", - "¿Viene la Dra. López, es decir, la jefa (la de 2.5 años)?", - "¿Dónde: aquí o allí?", - "¿Cuánto cuesta: 3,50 o a las 10:30?", - "¿Vienes a las 10:30; o mejor mañana?", - "¡Qué dices, Dr. Pérez!", - "¡Qué dices, Dr. Pérez? ¡Basta!", - "¡¡Hola!! ¿Qué tal?", - "¡¡¡Basta!!!", - "¿Vienes?!", - "¿Qué?! ¡No!", - "¿Vienes?\n¿O no?", - "¿Vienes\nmañana?", - "Ana dijo: «¿Vienes?» y yo: «Sí».", - "—¿Vienes? —preguntó Juan.", - "¿Viste https://example.com/? Dime.", - "¿Es verdad (!) o no?", - "Nació en 1950 (?) en Montevideo.", - ] { - assert!(missing(text).is_empty(), "{text:?}: {:?}", missing(text)); - } - } - - /// A closing mark with nothing open is still flagged, after a sentence or a clause that closed - /// an earlier question, and with the sentence it closes offered with its opening mark. - #[test] - fn closing_marks_with_nothing_open_are_still_flagged() { - for (text, flagged, offered) in [ - ("Vienes mañana?", "Vienes mañana?", "¿Vienes mañana?"), - ("Llegó. Vienes?", "Vienes?", "¿Vienes?"), - ("Le preguntó: vienes?", "vienes?", "¿vienes?"), - ("Dijo «vienes?»", "vienes?", "¿vienes?"), - ("Qué bien!", "Qué bien!", "¡Qué bien!"), - ("¿Vienes? No sé qué hacer?", "No sé qué hacer?", "¿No sé qué hacer?"), - ("¿Viene el Sr. García? Llegó tarde!", "Llegó tarde!", "¡Llegó tarde!"), - ("¡¡Hola!! Qué bien!", "Qué bien!", "¡Qué bien!"), - ("¿Qué?! No sé qué decir!", "No sé qué decir!", "¡No sé qué decir!"), - ] { - let found = missing(text); - assert_eq!(found.len(), 1, "{text:?}: {found:?}"); - assert_eq!((found[0].0.as_str(), found[0].1.first().map(String::as_str)), (flagged, Some(offered)), "{text:?}"); - } - } - - #[test] - fn address_lookup_finds_the_range_a_byte_is_in() { - let ranges = [(2, 5), (7, 8), (10, 20)]; - let inside = |j| address_at(&ranges, j); - assert_eq!([0, 1].map(inside), [None, None], "before the first"); - assert_eq!([2, 3, 4].map(inside), [Some((2, 5)); 3], "the start is inside, the end is not"); - assert_eq!([5, 6].map(inside), [None, None], "between ranges"); - assert_eq!([7, 10, 19].map(inside), [Some((7, 8)), Some((10, 20)), Some((10, 20))]); - assert_eq!([8, 9, 20, 99].map(inside), [None; 4], "after a range, and after the last"); - assert_eq!(address_at(&[], 3), None); - } - - /// Maintainer review on #3562: a paragraph of thousands of addresses followed by a question was - /// checked in quadratic time (every character before the question scanned every address). - /// About 99 KB (under the 100,000-byte paragraph cap) took hundreds of milliseconds; it is now - /// O(n log m): about 1 to 5 ms in a debug build, against about 300 ms for the scan, so the - /// bound below leaves room on a loaded machine and still catches a return to the quadratic - /// lookup. - #[test] - fn many_addresses_before_a_question_are_checked_quickly() { - for (unit, ending, whole_paragraph) in - [("https://x/ ", "Vienes?", true), ("https://x/? ", "Vienes?", false), ("www.x.com ", "Qué bien!", true)] - { - // As many as fit in about 99 KB: 9,000 of the first, as in the review. - let text = format!("{}{ending}", unit.repeat(99_000 / unit.len())); - assert!(text.len() < 100_000, "{}", text.len()); - let started = std::time::Instant::now(); - let issues = check(&text, ProofLang::Es); - let took = started.elapsed(); - assert!(took < std::time::Duration::from_millis(75), "{unit:?} took {took:?}"); - let missing: Vec<&Issue> = issues.iter().filter(|i| i.message.starts_with("Falta")).collect(); - assert_eq!(missing.len(), 1, "{unit:?}: {missing:?}"); - // The mark's sentence is the whole paragraph when the addresses don't end with a mark - // of their own, and only the last question when they do. - let want_start = if whole_paragraph { 0 } else { text.len() - ending.len() }; - assert_eq!((missing[0].start, missing[0].end), (want_start, text.len()), "{unit:?}"); - } - } - - /// A word repeated once composed and once decomposed is still repeated. - #[test] - fn repeated_words_match_across_normalization_forms() { - let text = "también tambie\u{301}n"; - let issues = check(text, ProofLang::Es); - assert_eq!(issues.len(), 1, "{issues:?}"); - assert_eq!((issues[0].start, issues[0].end), (0, text.len())); - assert!(issues[0].message.starts_with("Palabra repetida")); - } - - #[test] - fn a_space_after_an_opening_mark_is_flagged() { - let text = "¿ Vienes? ¡ Hola!"; - let issues = check(text, ProofLang::EsUy); - let spaced: Vec<&str> = - issues.iter().filter(|i| i.message == "Espacio después de un signo de apertura").map(|i| &text[i.start..i.end]).collect(); - assert_eq!(spaced, ["¿ ", "¡ "], "{issues:?}"); - assert!(issues.iter().all(|i| !i.message.starts_with("Falta")), "{issues:?}"); - assert!(check("¿ ", ProofLang::Es).is_empty()); - } - - proptest::proptest! { - #[test] - fn never_panics_and_ranges_are_valid(s in "\\PC{0,80}|[¿¡?!a-z .,(«»):]{0,40}") { - for issue in check(&s, ProofLang::Es) { - proptest::prop_assert!(issue.start <= issue.end && issue.end <= s.len()); - proptest::prop_assert!(s.is_char_boundary(issue.start) && s.is_char_boundary(issue.end)); - } - } - } -} diff --git a/crates/proof/src/es/mod.rs b/crates/proof/src/es/mod.rs deleted file mode 100644 index 5263bd1d9..000000000 --- a/crates/proof/src/es/mod.rs +++ /dev/null @@ -1,315 +0,0 @@ -//! Spanish proofing (#3360, #3553) using the installed Hunspell dictionaries (never bundled; -//! LibreOffice ships them as its `dict-es` extension, Debian and Ubuntu as `hunspell-es`). Each -//! variety reads its own dictionary: Spain ([`ProofLang::Es`]) `es_ES`, Mexico -//! ([`ProofLang::EsMx`]) `es_MX`, Latin America ([`ProofLang::Es419`]) `es_419` and Uruguay -//! ([`ProofLang::EsUy`]) `es_UY`, each falling back to a nearby or generic one. Uruguayan Spanish -//! also accepts the `vos` forms of every verb in the dictionary ([`voseo`]: `tenés`, `vení`, -//! `decime`). Suggestions use the dictionary's own MAP/REP/TRY/KEY tables (the regional -//! dictionaries' REP tables carry their differences, such as `vosotros` forms in Mexico). -//! Grammar is limited to generic repeated-word and spacing checks and the Spanish opening -//! question and exclamation marks, with Spanish messages; hyphenation keeps the English fallback -//! (no Spanish patterns are bundled). - -pub mod grammar; -mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; -pub mod voseo; - -use std::sync::Arc; - -use crate::ProofLang; -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// The dictionary of each variety, found on first use (see [`DictSlot`]). -pub(crate) static SPAIN_DICT: DictSlot = DictSlot::new(SPAIN, "Spanish (Spain)"); -pub(crate) static MEXICO_DICT: DictSlot = DictSlot::new(MEXICO, "Spanish (Mexico)"); -pub(crate) static LATIN_AMERICA_DICT: DictSlot = DictSlot::new(LATIN_AMERICA, "Spanish (Latin America)"); -pub(crate) static URUGUAY_DICT: DictSlot = DictSlot::new(URUGUAY, "Spanish (Uruguay)"); - -/// File names of a Spanish Hunspell dictionary for Spain, most specific first (`es_ANY` is the -/// generic one RLA-ES publishes). -const SPAIN: &[&str] = &["es_ES", "es-ES", "es_ANY", "es"]; -/// For Mexico: its own, then a Latin American or generic one. -const MEXICO: &[&str] = &["es_MX", "es-MX", "es_419", "es-419", "es_ANY", "es"]; -/// For Latin America: its own or a generic one, then the United States' and Mexico's, which -/// LibreOffice ships (it has no `es_419`) and which use Latin American spelling, then -/// Argentina's, so `es-AR` text is checked when that is the only one installed. -const LATIN_AMERICA: &[&str] = &["es_419", "es-419", "es_ANY", "es", "es_US", "es_MX", "es_AR", "es-AR"]; -/// For Uruguay: its own, then Argentina's (the same Rioplatense Spanish), then a Latin American -/// or generic one. -const URUGUAY: &[&str] = &["es_UY", "es-UY", "es_AR", "es-AR", "es_419", "es-419", "es_ANY", "es"]; - -/// `lang`'s variety's dictionary; Spain's for any other language. -fn slot(lang: ProofLang) -> &'static DictSlot { - match lang { - ProofLang::EsMx => &MEXICO_DICT, - ProofLang::Es419 => &LATIN_AMERICA_DICT, - ProofLang::EsUy => &URUGUAY_DICT, - _ => &SPAIN_DICT, - } -} - -/// The Hunspell name each variety's dictionary is known by (`es_ES`, `es_MX`, `es_419`, -/// `es_UY`): the first of its file names, used for the status-bar hint and load errors. -pub fn dictionary_name(lang: ProofLang) -> &'static str { - slot(lang).name() -} - -/// The Spanish dictionary of `lang`'s variety: the installed one (read on first use), one given -/// with [`install_dictionary`], or an empty one when none is available (spelling is off then; -/// see [`has_dictionary`]). -pub fn dictionary(lang: ProofLang) -> Arc { - slot(lang).get() -} - -/// Use this dictionary for `lang`'s variety from now on (Hunspell `.aff` and `.dic` text, -/// decoded to UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(lang: ProofLang, aff: &str, dic: &str) -> Result<(), String> { - slot(lang).install(aff, dic) -} - -/// Is a dictionary available for `lang`'s variety (spelling is checked)? -pub fn has_dictionary(lang: ProofLang) -> bool { - slot(lang).has_dictionary() -} - -/// Is `word` spelled correctly in `lang`'s Spanish? Numbers, single letters, short words in -/// capitals (acronyms: `ONU`, `CDMX`), words with digits and words in other scripts are accepted, -/// and so is every word when no dictionary is installed for the variety. Hyphenated compounds -/// are checked part by part (`teórico-práctico`, `franco-mexicano`). In Uruguayan Spanish the -/// `vos` forms of the dictionary's verbs are correct too ([`voseo`]). -pub fn is_correct(word: &str, lang: ProofLang) -> bool { - is_correct_with(&dictionary(lang), word, lang) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits. -pub fn is_correct_with_options(word: &str, lang: ProofLang, opts: &crate::Options) -> bool { - check(&dictionary(lang), word, lang, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str, lang: ProofLang) -> bool { - check(d, word, lang, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, lang: ProofLang, opts: &crate::Options) -> bool { - let Some(w) = crate::spelling::word_to_check(d, word, opts) else { - return true; - }; - if w.contains('-') { - return known(d, w, lang) || w.split('-').all(|p| p.chars().count() <= 1 || known(d, p, lang)); - } - known(d, w, lang) -} - -/// Is one word in the dictionary or the user dictionary, or (in Uruguayan Spanish) the `vos` -/// form of one of the dictionary's verbs? -fn known(d: &AffixDict, w: &str, lang: ProofLang) -> bool { - d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) || (lang == ProofLang::EsUy && voseo::is_vos_form(w, |inf| d.check(inf))) -} - -#[cfg(test)] -mod tests { - use super::*; - - const VARIETIES: [ProofLang; 4] = [ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy]; - - #[test] - fn each_variety_looks_for_its_own_dictionary_first() { - let dir = std::env::temp_dir().join(format!("wordcraft-es-aliases-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let (es, mx, la, uy) = (ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy); - // Alone in the folder, each file name is found by the varieties that list it. - for (name, found_by) in [ - ("es_ES", &[es][..]), - ("es-ES", &[es]), - ("es_MX", &[mx, la]), - ("es-MX", &[mx]), - ("es_419", &[mx, la, uy]), - ("es-419", &[mx, la, uy]), - ("es_US", &[la]), - ("es_UY", &[uy]), - ("es-UY", &[uy]), - ("es_AR", &[la, uy]), - ("es-AR", &[la, uy]), - ("es_ANY", &VARIETIES), - ("es", &VARIETIES), - ] { - std::fs::write(dir.join(format!("{name}.aff")), test_dict::AFF).unwrap(); - std::fs::write(dir.join(format!("{name}.dic")), test_dict::DIC).unwrap(); - for lang in VARIETIES { - let found = crate::hunspell::find_in(&dir, slot(lang).names()); - assert_eq!(found.is_some(), found_by.contains(&lang), "{name}: {lang:?}"); - if let Some((aff, dic)) = found { - assert!(is_correct_with(&crate::hunspell::load(&aff, &dic).unwrap(), "canción", lang)); - } - } - std::fs::remove_file(dir.join(format!("{name}.aff"))).unwrap(); - std::fs::remove_file(dir.join(format!("{name}.dic"))).unwrap(); - } - // With them all, each variety reads its own. - for name in ["es_ES", "es_MX", "es_419", "es_UY", "es_AR", "es_ANY"] { - std::fs::write(dir.join(format!("{name}.aff")), test_dict::AFF).unwrap(); - std::fs::write(dir.join(format!("{name}.dic")), test_dict::DIC).unwrap(); - } - for (lang, name) in [(es, "es_ES"), (mx, "es_MX"), (la, "es_419"), (uy, "es_UY")] { - assert_eq!(crate::hunspell::find_in(&dir, slot(lang).names()).map(|(_, dic)| dic), Some(dir.join(format!("{name}.dic")))); - assert_eq!(dictionary_name(lang), name); - } - std::fs::remove_dir_all(dir).unwrap(); - } - - #[test] - fn spanish_words_affixes_case_and_hyphens() { - test_dict::install(); - let d = dictionary(ProofLang::Es); - assert!(has_dictionary(ProofLang::Es)); - assert!(d.rule_count() > 0); - for w in [ - "canción", - "canciones", - "prueba", - "pruebas", - "corrector", - "correctores", - "también", - "México", - "Canción", - "CANCIÓN", - "teórico-práctico", - "franco-mexicano", - "ONU", - "Año", - "niño", - "pingüino", - ] { - assert!(is_correct(w, ProofLang::Es), "{w}"); - } - for w in ["prueva", "corector", "tambien", "mexico", "cancion", "teórico-prático", "cAnción", "nino"] { - assert!(!is_correct(w, ProofLang::Es), "{w}"); - } - } - - #[test] - fn varieties_keep_their_own_dictionaries() { - test_dict::install(); - // The Americas' fixture lacks `vosotros` forms and has American words; Spain's is the other way round. - assert!(is_correct("habéis", ProofLang::Es) && !is_correct("habéis", ProofLang::EsMx)); - assert!(is_correct("platicar", ProofLang::EsMx) && !is_correct("platicar", ProofLang::Es)); - assert!(is_correct("platicar", ProofLang::Es419) && is_correct("computadora", ProofLang::Es419)); - assert!(!is_correct("habéis", ProofLang::Es419) && !is_correct("habéis", ProofLang::EsUy)); - assert!(is_correct("ordenador", ProofLang::Es) && !is_correct("ordenador", ProofLang::EsMx)); - } - - /// Uruguayan Spanish accepts the `vos` forms of the dictionary's verbs, which the other - /// varieties flag. - #[test] - fn uruguayan_spanish_accepts_vos_forms() { - test_dict::install(); - for w in ["tenés", "vení", "Vení", "decime", "decímelo", "andate", "mirá", "hablás", "querés", "venís", "fijate"] { - assert!(is_correct(w, ProofLang::EsUy), "{w}"); - assert!(!is_correct(w, ProofLang::Es419) && !is_correct(w, ProofLang::Es), "{w}"); - } - for w in ["tenéz", "venì", "decimelo", "decíme", "zqbailá"] { - assert!(!is_correct(w, ProofLang::EsUy), "{w}"); - } - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Spanish checks - /// those words against the dictionary instead of accepting them (CodeRabbit review on #3562). - #[test] - fn proofing_options_reach_the_spanish_check() { - test_dict::install(); - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - for lang in VARIETIES { - assert!(crate::is_correct_in_with("PRUEVA", lang, &defaults), "{lang:?}: short capitals are acronyms by default"); - assert!(!crate::is_correct_in_with("PRUEVA", lang, &check_caps), "{lang:?}"); - assert!(crate::is_correct_in_with("PRUEBA", lang, &check_caps), "{lang:?}"); - assert!(crate::is_correct_in_with("prueva2", lang, &defaults), "{lang:?}"); - assert!(!crate::is_correct_in_with("prueva2", lang, &check_digits), "{lang:?}"); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "{lang:?}: numbers alone stay correct"); - } - assert!(is_correct("PRUEVA", ProofLang::Es), "direct checks keep the defaults"); - } - - /// Maintainer review on #3562: a word typed decomposed (NFD: `e` + U+0301) is checked like its - /// composed form (NFC) and keeps its own byte range, accents and all. - #[test] - fn decomposed_accents_are_checked_like_composed_ones() { - test_dict::install(); - for lang in VARIETIES { - for text in ["esto también", "esto tambie\u{301}n", "una cancio\u{301}n y pingu\u{308}ino", "CANCIO\u{301}N", "nin\u{303}o"] { - let issues = crate::check_text(text, &[(0..text.len(), lang)]); - assert!(issues.is_empty(), "{lang:?} {text:?}: {issues:?}"); - } - // A misspelling that ends in a decomposed accent is flagged whole, at its own bytes. - let text = "esto es una prueva tambie\u{301}nn"; - let issues = crate::check_text(text, &[(0..text.len(), lang)]); - let flagged: Vec<(&str, usize, usize)> = issues.iter().map(|i| (&text[i.start..i.end], i.start, i.end)).collect(); - assert_eq!(flagged, [("prueva", 12, 18), ("tambie\u{301}nn", 19, text.len())], "{lang:?}"); - assert!(crate::suggest_in("tambie\u{301}nn", 8, lang).contains(&"también".into()), "{lang:?}"); - } - } - - #[test] - fn forbidden_words_are_rejected_in_every_case() { - test_dict::install(); - for w in ["zqprohibida", "Zqprohibida", "ZQPROHIBIDA"] { - assert!(!is_correct(w, ProofLang::Es), "{w}"); - } - } - - #[test] - fn missing_dictionary_and_ignored_inputs_are_quiet() { - let empty = AffixDict::default(); - assert!(is_correct_with(&empty, "prueva", ProofLang::Es)); - assert!(install_dictionary(ProofLang::Es, "FLAG long\n", "1\ncasa\n").is_err()); - let d = AffixDict::from_hunspell(test_dict::AFF, test_dict::DIC, &[]).unwrap(); - for w in ["", "a", "2026", "EE", "1.º", "日本語", "-", "--"] { - assert!(is_correct_with(&d, w, ProofLang::EsUy), "{w}"); - } - } - - #[test] - fn user_dictionary_applies() { - test_dict::install(); - assert!(!is_correct("zqpalabrapropia", ProofLang::EsMx)); - crate::add_word("Zqpalabrapropia"); - assert!(is_correct("zqpalabrapropia", ProofLang::EsMx)); - } - - #[test] - fn spanish_dispatch_messages_options_and_mixed_spans() { - test_dict::install(); - for tag in ["es-ES", "es-MX", "es-419", "es-AR", "es-UY", "spa"] { - assert!(crate::has_dictionary(Some(tag)), "{tag}"); - } - assert!(crate::is_correct_for("canción", Some("es-MX"))); - assert!(crate::is_correct_for("tenés", Some("es-UY")) && !crate::is_correct_for("tenés", Some("es-MX"))); - // The sentence from #3553. - let text = "esto es una prueva del corector"; - for lang in VARIETIES { - let issues = crate::check_text(text, &[(0..text.len(), lang)]); - let flagged: Vec<&str> = issues.iter().map(|i| &text[i.start..i.end]).collect(); - assert_eq!(flagged, ["prueva", "corector"], "{lang:?}: {issues:?}"); - assert!(issues.iter().all(|i| i.message == "Posible error ortográfico" && i.lang == lang), "{issues:?}"); - assert!(crate::suggest_in("prueva", 8, lang).contains(&"prueba".into()), "{lang:?}"); - assert!(crate::suggest_in("corector", 8, lang).contains(&"corrector".into()), "{lang:?}"); - } - let text = "Canción this is is wrong"; - let issues = crate::check_text(text, &[(0..8, ProofLang::Es419), (9..text.len(), ProofLang::En)]); - assert!(issues.iter().any(|i| i.lang == ProofLang::En && i.message.starts_with("Repeated word"))); - assert!(issues.iter().all(|i| i.start >= 9)); - let opts = crate::Options { ignore_uppercase: false, ..Default::default() }; - assert!(!crate::is_correct_in_with("ZQPROHIBIDA", ProofLang::Es, &opts)); - } -} diff --git a/crates/proof/src/es/suggest.rs b/crates/proof/src/es/suggest.rs deleted file mode 100644 index 0568ae870..000000000 --- a/crates/proof/src/es/suggest.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Spanish suggestions. The ranking is the shared dictionary-driven one -//! ([`crate::affix_suggest`]: case, MAP — the Spanish dictionaries map each vowel to -//! its accented forms and `n` to `ñ`, so `tambien` → `también` comes first —, REP, missing spaces, -//! KEY, TRY edits and a stem scan); every table comes from the variety's installed dictionary. -//! In Uruguayan Spanish a `vos` form typed without its accent is offered with it first -//! (`veni` → `vení`, `tenes` → `tenés`; [`super::voseo`]). - -use crate::ProofLang; -use crate::affix::{AffixDict, Casing, capitalize, casing}; -use crate::affix_suggest::{MAX_LETTERS, Tuning}; - -/// Spelling suggestions for a word in `lang`'s Spanish, best first, at most `max`, in the word's -/// case. -pub fn suggest(word: &str, max: usize, lang: ProofLang) -> Vec { - suggest_in(&super::dictionary(lang), word, max, lang) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize, lang: ProofLang) -> Vec { - let mut out = Vec::new(); - let word = word.trim(); - if lang == ProofLang::EsUy - && !d.is_empty() - && word.chars().count() <= MAX_LETTERS - && let Some(v) = super::voseo::accented(&word.to_lowercase(), |inf| d.check(inf)) - { - out.push(match casing(word) { - Casing::Upper => v.to_uppercase(), - Casing::Initial => capitalize(&v), - _ => v, - }); - } - for s in crate::affix_suggest::suggest_in(d, word, max, &Tuning::DEFAULT) { - if !out.contains(&s) { - out.push(s); - } - } - out.truncate(max); - out -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn spanish_suggestions_use_runtime_tables_and_case() { - crate::es::test_dict::install(); - for lang in [ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy] { - for (word, want) in [ - ("tambien", "también"), - ("cancion", "canción"), - ("Cancion", "Canción"), - ("mexico", "México"), - ("nino", "niño"), - ("pinguino", "pingüino"), - ("prueva", "prueba"), - ("corector", "corrector"), - ("estoes", "esto es"), - ] { - assert!(suggest(word, 8, lang).contains(&want.into()), "{lang:?} {word}: {:?}", suggest(word, 8, lang)); - } - } - // The first suggestion for a missing accent is the accented word. - assert_eq!(suggest("tambien", 4, ProofLang::Es).first().map(String::as_str), Some("también")); - } - - #[test] - fn uruguayan_vos_forms_get_their_accent_back_first() { - crate::es::test_dict::install(); - for (word, want) in [("veni", "vení"), ("tenes", "tenés"), ("Veni", "Vení"), ("TENES", "TENÉS"), ("queres", "querés")] { - assert_eq!(suggest(word, 4, ProofLang::EsUy).first().map(String::as_str), Some(want), "{word}"); - assert!(!suggest(word, 8, ProofLang::Es419).contains(&want.into()), "{word}: no vos forms outside Uruguay"); - } - } - - #[test] - fn suggestions_stay_bounded_and_skip_forbidden_words() { - crate::es::test_dict::install(); - for lang in [ProofLang::Es, ProofLang::EsUy] { - for w in ["", "dos palabras", &"a".repeat(41)] { - assert!(suggest(w, 4, lang).is_empty(), "{w}"); - } - assert!(suggest("prueva", 0, lang).is_empty()); - assert!(suggest("veni", 0, lang).is_empty()); - assert!(suggest_in(&AffixDict::default(), "veni", 4, lang).is_empty()); - assert!(suggest("prueba", 4, lang).len() <= 4); - assert!(!suggest("prueba", 4, lang).contains(&"prueba".into())); - assert!(!suggest("zqprohibda", 8, lang).contains(&"zqprohibida".into())); - } - } - - #[test] - fn nosuggest_stems_and_forms_are_never_offered() { - crate::affix::tests::assert_nosuggest_is_honoured(|d, w, max| suggest_in(d, w, max, ProofLang::Es)); - } -} diff --git a/crates/proof/src/es/test_dict.rs b/crates/proof/src/es/test_dict.rs deleted file mode 100644 index 6b3e6b1f5..000000000 --- a/crates/proof/src/es/test_dict.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Hand-written synthetic Spanish test data. No words, rules or suggestion tables were copied -//! from a system dictionary. This is a small fixture, not a redistributable Spanish dictionary. -//! Spain's word list has `vosotros` forms and Spain's words; the Americas' list (Mexico, Latin -//! America and Uruguay) has theirs instead, so the tests can tell the varieties' dictionaries apart. - -use crate::ProofLang; - -pub const AFF: &str = "SET UTF-8 -FLAG UTF-8 -FORBIDDENWORD F -TRY aeosrnidlctumpbgvyqhfzjñáéíóúüxkw -KEY qwertyuiop|asdfghjklñ|zxcvbnm -MAP 6 -MAP aá -MAP eé -MAP ií -MAP oó -MAP uúü -MAP nñ -REP 2 -REP v b -REP b v -SFX S Y 2 # synthetic plurals -SFX S 0 s [aeiou] -SFX S ón ones ón -SFX E Y 1 -SFX E 0 es [^aeiou] -"; - -/// Spain. -pub const DIC: &str = "32 -hablar -tener -venir -decir -andar -mirar -querer -fijar -poner -canción/S -prueba/S -corrector/E -también -México -esto -es -una -del -de -la -el -y -niño/S -pingüino/S -año/S -teórico -práctico -mexicano -franco -habéis -ordenador/E -zqprohibida/F -"; - -/// Mexico, Latin America and Uruguay. -pub const DIC_AMERICAS: &str = "33 -hablar -tener -venir -decir -andar -mirar -querer -fijar -poner -canción/S -prueba/S -corrector/E -también -México -esto -es -una -del -de -la -el -y -niño/S -pingüino/S -año/S -teórico -práctico -mexicano -franco -platicar -computadora/S -han -zqprohibida/F -"; - -/// Install the fixture for every variety: [`DIC`] for Spain, [`DIC_AMERICAS`] for Mexico, Latin -/// America and Uruguay. -pub fn install() { - for (lang, dic) in [(ProofLang::Es, DIC), (ProofLang::EsMx, DIC_AMERICAS), (ProofLang::Es419, DIC_AMERICAS), (ProofLang::EsUy, DIC_AMERICAS)] { - if let Err(e) = super::install_dictionary(lang, AFF, dic) { - panic!("test dictionary: {e}"); - } - } -} diff --git a/crates/proof/src/es/voseo.rs b/crates/proof/src/es/voseo.rs deleted file mode 100644 index d9753e129..000000000 --- a/crates/proof/src/es/voseo.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! The `vos` forms of verbs in Uruguayan Spanish ([`crate::ProofLang::EsUy`]), which dictionaries -//! built around `tú` may lack (#3360). Rioplatense `voseo` builds them regularly from the -//! infinitive, stress on the ending, without the stem changes `tú` has: -//! -//! - the imperative drops the infinitive's `r` and accents the vowel: `hablar` → `hablá`, -//! `tener` → `tené`, `venir` → `vení`, `decir` → `decí` (`ir` uses `andá`, from `andar`); -//! - with one pronoun after it the accent goes (`decime`, `fijate`, `andate`, `poneme`), with two -//! it comes back (`decímelo`, `mandámela`); `-ír` verbs keep theirs (`sonreíte`); -//! - the present indicative ends in `-ás`, `-és`, `-ís`: `hablás`, `tenés`, `venís`. -//! -//! A form is accepted only when the dictionary has its verb's infinitive, so nothing but these -//! endings is hard-coded. Other `vos` forms (`sos`) are ordinary dictionary words. - -/// Pronouns that come first when two follow an imperative (`decímelo`, `dáselo`). -const FIRST_PRONOUNS: [&str; 4] = ["me", "te", "se", "nos"]; -/// Pronouns that come second (or alone). -const SECOND_PRONOUNS: [&str; 6] = ["lo", "la", "le", "los", "las", "les"]; - -/// Shortest infinitive considered (`dar` and `ir` have no `vos` imperative of their own here, and -/// short stems would let nouns such as `bar` pass for verbs). -const MIN_INFINITIVE: usize = 4; - -/// Is `word` (one word, any case) a `vos` form of a verb whose infinitive `is_word` accepts? -pub fn is_vos_form(word: &str, is_word: impl Fn(&str) -> bool) -> bool { - let w = word.to_lowercase(); - if w.chars().count() > crate::affix::MAX_WORD_BYTES { - return false; - } - infinitives(&w).iter().any(|inf| inf.chars().count() >= MIN_INFINITIVE && is_word(inf)) -} - -/// The infinitives `w` (lower case) could be a `vos` form of, by its ending. -fn infinitives(w: &str) -> Vec { - let mut out = Vec::new(); - // Present indicative: `hablás`, `tenés`, `venís`. - if let Some(stem) = w.strip_suffix('s') { - imperative(stem, &mut out); - } - // Imperative: `hablá`, `tené`, `vení`. - imperative(w, &mut out); - // With one pronoun the accent goes: `decime`, `fijate`; `-ír` verbs keep it: `sonreíte`. - for p in FIRST_PRONOUNS.iter().chain(SECOND_PRONOUNS.iter()) { - if let Some(base) = w.strip_suffix(p) - && base.ends_with(['a', 'e', 'i', 'í']) - { - out.push(format!("{base}r")); - } - } - // With two the accent comes back: `decímelo`, `mandámela`. - for second in SECOND_PRONOUNS { - let Some(rest) = w.strip_suffix(second) else { continue }; - for first in FIRST_PRONOUNS { - if let Some(base) = rest.strip_suffix(first) { - imperative(base, &mut out); - } - } - } - out -} - -/// The infinitives of an imperative ending in an accented vowel: `hablá` → `hablar`, and for `í` -/// both `vení` → `venir` and `sonreí` → `sonreír`. -fn imperative(form: &str, out: &mut Vec) { - let mut chars = form.chars(); - let Some(last) = chars.next_back() else { return }; - let stem = chars.as_str(); - match last { - 'á' => out.push(format!("{stem}ar")), - 'é' => out.push(format!("{stem}er")), - 'í' => { - out.push(format!("{stem}ir")); - out.push(format!("{stem}ír")); - } - _ => {} - } -} - -/// The `vos` form `word` (lower case, without its accent) was most likely meant as, when the -/// writer left the accent out: `veni` → `vení`, `tenes` → `tenés`. `None` when it isn't one. -pub fn accented(word: &str, is_word: impl Fn(&str) -> bool) -> Option { - let (body, s) = match word.strip_suffix('s') { - Some(b) => (b, "s"), - None => (word, ""), - }; - let mut chars = body.chars(); - let accent = match chars.next_back()? { - 'a' => 'á', - 'e' => 'é', - 'i' => 'í', - _ => return None, - }; - let form = format!("{}{accent}{s}", chars.as_str()); - is_vos_form(&form, is_word).then_some(form) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn verbs(w: &str) -> bool { - ["hablar", "tener", "venir", "decir", "andar", "mirar", "sonreír", "fijar", "mandar", "poner", "bar", "querer"].contains(&w) - } - - #[test] - fn imperatives_present_and_pronouns_come_from_the_infinitive() { - for w in [ - "hablá", - "tené", - "vení", - "decí", - "andá", - "mirá", - "poné", - "hablás", - "tenés", - "venís", - "querés", - "decime", - "fijate", - "andate", - "poneme", - "sonreíte", - "sonreí", - "decímelo", - "mandámela", - "decinos", - "Vení", - "TENÉS", - ] { - assert!(is_vos_form(w, verbs), "{w}"); - } - // Wrong accents, verbs the dictionary lacks, and short stems (`bame` isn't `bar`). - for w in ["habla", "decíme", "decínos", "decimelo", "tenéz", "cantá", "bá", "bame", "", "s", "á", "més", "lo", "melo"] { - assert!(!is_vos_form(w, verbs), "{w}"); - } - } - - #[test] - fn missing_accents_are_restored() { - assert_eq!(accented("veni", verbs).as_deref(), Some("vení")); - assert_eq!(accented("tenes", verbs).as_deref(), Some("tenés")); - assert_eq!(accented("habla", verbs).as_deref(), Some("hablá")); - for w in ["canta", "casa", "", "s", "pan", "tenés"] { - assert_eq!(accented(w, verbs), None, "{w}"); - } - } - - proptest::proptest! { - #[test] - fn never_panics(s in "\\PC{0,40}") { - let _ = is_vos_form(&s, |_| true); - let _ = accented(&s, |_| true); - } - } -} diff --git a/crates/proof/src/hr/grammar.rs b/crates/proof/src/hr/grammar.rs deleted file mode 100644 index 517c56235..000000000 --- a/crates/proof/src/hr/grammar.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Conservative Croatian grammar and punctuation checks. -//! Handles repeated words, whitespace, capitals and duplicated punctuation; -//! intentionally avoids speculative agreement and comma rules. - -use crate::{Issue, IssueKind, ProofLang, words}; - -/// Words Croatian repeats on purpose: interjections, onomatopoeia and emphatic doubling. -const ALLOWED_REPEATS: &[&str] = - &["da", "ne", "no", "ha", "hej", "joj", "aj", "oh", "uh", "hop", "kuc", "tik", "tak", "bum", "malo", "brzo", "polako", "više", "dobro"]; -const ABBREVIATIONS: &[&str] = &[ - "npr", "itd", "itp", "tj", "tzv", "odn", "sl", "str", "br", "god", "st", "dr", "mr", "prof", "doc", "ing", "dipl", "mag", "univ", "g", "god", - "mj", "min", "sek", "čl", "st", "č", "toč", "tab", "sl", "sv", "ul", "trg", "pr", "izd", "ur", "svez", "vol", "vid", "usp", "prim", "engl", - "njem", "hrv", "lat", "tel", "mob", "e", "p", "o", "d", "m", "v", "z", "r", "pon", "uto", "sri", "čet", "pet", "sub", "ned", "sij", "velj", - "ožu", "tra", "svi", "lip", "srp", "kol", "ruj", "lis", "stu", "pro", -]; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang: ProofLang::Hr } -} - -fn capitalize(w: &str) -> String { - crate::affix::capitalize(w) -} - -/// Is `prev`, before a period, an abbreviation, an initial, a number or a Roman numeral (after -/// which a lower-case word doesn't start a sentence)? -fn abbreviation_or_number(prev: &str) -> bool { - prev.chars().count() <= 1 - || prev.chars().any(|c| c.is_ascii_digit()) - || (prev.chars().all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))) - || ABBREVIATIONS.contains(&prev.to_lowercase().as_str()) -} - -const CLOSING: [char; 8] = ['"', '”', '’', '\'', ')', '»', ']', '\u{1}']; -const OPENING: [char; 8] = ['"', '“', '«', '(', '[', '\'', '‚', '—']; - -/// Does the gap between two words end a sentence: a `.`, `!` or `?` (not an ellipsis, not after -/// an abbreviation), then space, with nothing but quotes and brackets around? -fn ends_sentence(gap: &str, prev: &str) -> bool { - let t = gap.trim_start_matches(CLOSING); - let Some(end) = t.chars().next() else { return false }; - if !matches!(end, '.' | '!' | '?') || t.starts_with("..") { - return false; - } - if end == '.' && abbreviation_or_number(prev) { - return false; - } - let rest = t.trim_start_matches(['.', '!', '?']).trim_start_matches(CLOSING); - rest.starts_with(char::is_whitespace) && rest.trim_start().chars().all(|c| OPENING.contains(&c)) -} - -/// Grammar issues in a paragraph of Croatian text, in order. -pub fn check(text: &str) -> Vec { - check_with(text, true) -} - -/// [`check`] where `capital_first` says whether the text's first word must start a sentence with a capital -/// (false for a list item, which may be a fragment). -pub fn check_with(text: &str, capital_first: bool) -> Vec { - let ws: Vec<(usize, usize)> = words(text); - let mut v = Vec::new(); - repeated_words(text, &ws, &mut v); - spaces(text, &mut v); - sentence_capitals(text, &ws, capital_first, &mut v); - doubled_punctuation(text, &mut v); - v.sort_by_key(|i| (i.start, i.end)); - v -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() - && gap.chars().all(char::is_whitespace) - && w0.chars().all(char::is_alphabetic) - && w0.to_lowercase() == w1.to_lowercase() - && !ALLOWED_REPEATS.contains(&w0.to_lowercase().as_str()) - { - v.push(issue(*a0, *b1, format!("Ponovljena riječ: \"{w1}\""), vec![w0.to_string()])); - } - } -} - -fn spaces(text: &str, v: &mut Vec) { - for (i, c) in text.char_indices() { - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - let flag = match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '”' | '’' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Razmak ispred interpunkcijskog znaka".into(), vec![p.to_string()])); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Dvostruki razmak".into(), vec![" ".into()])); - } -} - -fn sentence_capitals(text: &str, ws: &[(usize, usize)], capital_first: bool, v: &mut Vec) { - // List items (`– maçãs,`) may start in lower case. - let list_item = text.trim_end().ends_with([',', ';']); - for (k, &(a, b)) in ws.iter().enumerate() { - let Some(w) = text.get(a..b) else { continue }; - let starts = if k == 0 { - let before = text.get(..a).unwrap_or(""); - capital_first && !list_item && before.chars().all(|c| c.is_whitespace() || OPENING.contains(&c)) - } else { - let Some(&(pa, pb)) = ws.get(k - 1) else { continue }; - ends_sentence(text.get(pb..a).unwrap_or(""), text.get(pa..pb).unwrap_or("")) - }; - if starts && w.chars().next().is_some_and(char::is_lowercase) && w.chars().all(char::is_alphabetic) { - v.push(issue(a, b, "Rečenica treba započeti velikim slovom".into(), vec![capitalize(w)])); - } - } -} - -fn doubled_punctuation(text: &str, v: &mut Vec) { - let bytes = text.as_bytes(); - let mut i = 0; - while let Some(&b) = bytes.get(i) { - if !matches!(b, b',' | b'.' | b';') { - i += 1; - continue; - } - let run = bytes.get(i..).map_or(0, |r| r.iter().take_while(|&&x| x == b).count()); - let flag = match b { - b'.' => run == 2, - _ => run >= 2, - }; - if flag { - let end = i + run; - let suggestions: Vec = match b { - b'.' => vec![".".into(), "…".into()], - _ => vec![char::from(b).to_string()], - }; - v.push(issue(i, end, "Ponovljen interpunkcijski znak".into(), suggestions)); - } - i += run.max(1); - } -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn checks_croatian_punctuation() { - let t = "Ovo ovo je primjer , s dva razmaka.."; - let issues = check(t); - assert!(issues.iter().any(|i| i.message.starts_with("Ponovljena riječ"))); - assert!(issues.iter().any(|i| i.message == "Dvostruki razmak")); - assert!(issues.iter().all(|i| i.lang == ProofLang::Hr)); - } - #[test] - fn abbreviations_do_not_trigger_capitals() { - let issues = check("To je npr. sasvim dobar primjer."); - assert!(!issues.iter().any(|i| i.message == "Rečenica treba započeti velikim slovom")); - } -} diff --git a/crates/proof/src/hr/mod.rs b/crates/proof/src/hr/mod.rs deleted file mode 100644 index 53edf0674..000000000 --- a/crates/proof/src/hr/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Croatian spelling and conservative grammar support. -//! Uses an installed hr_HR Hunspell .aff/.dic pair (not bundled). -//! The engine decodes ISO-8859-2 dictionaries at load time. -//! Suggestions prioritize č/ć, đ, š, ž and common one-character mistakes. -//! Hyphenation remains the host engine's fallback until Croatian patterns are supplied. - -pub mod grammar; -mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// File names of a Croatian Hunspell dictionary. -const NAMES: &[&str] = &["hr_HR", "hr-HR", "hr"]; - -/// The Croatian dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Croatian"); - -/// The Croatian dictionary: the installed Hunspell `hr_HR` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available (Croatian spelling is off -/// then; see [`has_dictionary`]). -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Use this Croatian dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to -/// UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is a Croatian dictionary available (spelling is checked)? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Is `word` spelled correctly in Croatian? Numbers, single letters, short words in capitals -/// (acronyms: `HRT`, `RH`), words with digits and words in other scripts are accepted, and so -/// is every word when no Croatian dictionary is installed. -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool { - let Some(w) = crate::spelling::word_to_check(d, word, opts) else { - return true; - }; - if w.contains('-') { - return w.split('-').all(|p| p.chars().count() <= 1 || known(d, p)); - } - known(d, w) -} - -/// Is one word (no hyphens) in the dictionary or the user dictionary? -fn known(d: &AffixDict, w: &str) -> bool { - d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_dictionary_loads() { - test_dict::install(); - let d = dictionary(); - assert!(has_dictionary()); - assert!(d.len() > 15); - assert!(d.rule_count() > 0); - assert!(!d.map().is_empty() && !d.rep().is_empty() && !d.try_chars().is_empty()); - } - - #[test] - fn correct_words_and_affixes() { - test_dict::install(); - for w in ["kuća", "kuće", "računalo", "čovjek", "riječ", "dijete", "Hrvatska", "čokolada", "škola", "škole", "HRT", "2026"] { - assert!(is_correct(w), "{w}"); - } - } - - #[test] - fn misspellings_are_flagged() { - test_dict::install(); - for w in ["kuca", "racunalo", "covjek", "rijec", "dijeteh", "skolla"] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn missing_dictionary_is_silent() { - assert!(is_correct_with(&AffixDict::default(), "nepostojećariječ")); - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Croatian checks - /// those words against the dictionary instead of accepting them. - #[test] - fn proofing_options_reach_the_croatian_check() { - test_dict::install(); - let lang = crate::ProofLang::Hr; - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("ŠKOLLA", lang, &defaults), "short capitals are acronyms by default"); - assert!(!crate::is_correct_in_with("ŠKOLLA", lang, &check_caps)); - assert!(crate::is_correct_in_with("ŠKOLA", lang, &check_caps)); - assert!(crate::is_correct_in_with("školla2", lang, &defaults)); - assert!(!crate::is_correct_in_with("školla2", lang, &check_digits)); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("ŠKOLLA"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/hr/suggest.rs b/crates/proof/src/hr/suggest.rs deleted file mode 100644 index d8c19b99b..000000000 --- a/crates/proof/src/hr/suggest.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Croatian spelling suggestions, best first: the shared ranking ([`crate::affix_suggest`]) with -//! the words Croatian runs into their neighbours (`neznam` → `ne znam`), the letter groups of -//! Croatian diacritics and letters to try, for a dictionary without MAP or TRY lines. - -use crate::affix::AffixDict; -use crate::affix_suggest::{Split, Tuning}; - -/// Short words often run into the next one (`ne znam`, `na primjer`, `da li`). -const LEADING_WORDS: &[&str] = &[ - "a", "ali", "i", "ili", "da", "do", "od", "na", "ne", "ni", "o", "po", "pod", "pri", "sa", "s", "se", "su", "u", "uz", "za", "iz", "je", "će", - "bi", "bih", "što", "koji", "koja", "koje", "kao", "kad", "dok", "bez", "pred", "nad", -]; -const TRAILING_WORDS: &[&str] = - &["je", "su", "se", "ga", "mi", "ti", "mu", "joj", "ih", "im", "li", "će", "ću", "bi", "bih", "i", "a", "u", "na", "za", "od", "do"]; - -/// What Croatian adds to its dictionary's tables. -const TUNING: Tuning = Tuning { - split: Split::Near { leading: LEADING_WORDS, trailing: TRAILING_WORDS }, - alphabet: "aeiourntsjkvlmdpbgczčćšžđfh", - map: &["cčć", "dđ", "sš", "zž"], - ..Tuning::DEFAULT -}; - -/// Spelling suggestions for a Croatian word, best first, at most `max`, in the word's letter case. -pub fn suggest(word: &str, max: usize) -> Vec { - suggest_in(&super::dictionary(), word, max) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize) -> Vec { - crate::affix_suggest::suggest_in(d, word, max, &TUNING) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn nosuggest_stems_and_forms_are_never_offered() { - crate::affix::tests::assert_nosuggest_is_honoured(suggest_in); - } - /// A dictionary without MAP lines still gets the Croatian diacritic groups: `kuca` → `kuća` - /// first, before a typo that costs more (`kuha`). - #[test] - fn diacritics_without_map_lines() { - let d = AffixDict::from_hunspell("", "2\nkuća\nkuha\n", &[]).unwrap(); - assert_eq!(suggest_in(&d, "kuca", 1), ["kuća"]); - } - #[test] - fn croatian_suggestions() { - crate::hr::test_dict::install(); - for (wrong, right) in [("kuca", "kuća"), ("racunalo", "računalo"), ("covjek", "čovjek"), ("rijec", "riječ"), ("skolla", "škola")] { - let result = suggest(wrong, 10); - assert!(result.iter().any(|s| s == right), "{wrong} -> {result:?}, expected {right}"); - } - } - #[test] - fn bounded() { - crate::hr::test_dict::install(); - assert!(suggest("", 5).is_empty()); - assert!(suggest("kuca", 0).is_empty()); - assert!(suggest(&"a".repeat(41), 5).is_empty()); - } -} diff --git a/crates/proof/src/hr/test_dict.rs b/crates/proof/src/hr/test_dict.rs deleted file mode 100644 index 038069e83..000000000 --- a/crates/proof/src/hr/test_dict.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Small synthetic UTF-8 Croatian Hunspell dictionary for deterministic tests. -pub const AFF: &str = "SET UTF-8 -TRY abcčćdđefghijklmnoprsštuvzž -MAP 5 -MAP cčć -MAP dđ -MAP sš -MAP zž -MAP iije -REP 3 -REP c č -REP dj đ -REP je ije -SFX A Y 2 -SFX A a e a -SFX A a i a -"; -pub const DIC: &str = "18 -kuća/A -računalo -čovjek -riječ -dijete -Hrvatska -čokolada -škola/A -more -sunce -dobar -hrvatski -jezik -tekst -pogreška -pravopis -provjera -računala -"; -pub fn install() { - if let Err(e) = super::install_dictionary(AFF, DIC) { - panic!("test dictionary: {e}"); - } -} diff --git a/crates/proof/src/hunspell.rs b/crates/proof/src/hunspell.rs deleted file mode 100644 index 1afd903cd..000000000 --- a/crates/proof/src/hunspell.rs +++ /dev/null @@ -1,706 +0,0 @@ -//! Hunspell dictionaries installed on the system, read at runtime. -//! -//! WordCraft doesn't bundle large spelling dictionaries. For languages with an affix dictionary -//! (Polish, Russian), it reads the standard Hunspell `.aff`/`.dic` pair installed by the operating system, -//! a package manager or LibreOffice, with [`crate::affix::AffixDict::from_hunspell`]. When none is -//! installed the language has no spelling check (no marks, no errors), like Word without the -//! language's proofing tools. -//! -//! Search order: the directory in `WORDCRAFT_DICTIONARIES` (or one set with -//! [`set_dictionary_dir`]), then the usual system locations. On the web there are none. - -use std::path::{Path, PathBuf}; -use std::sync::RwLock; - -use crate::affix::AffixDict; - -/// Largest `.aff` or `.dic` file read (real ones are a few MB). -const MAX_FILE: u64 = 64 << 20; - -/// An extra directory searched first (a setting), set by the front end. -static EXTRA_DIR: RwLock> = RwLock::new(None); - -/// Why an installed dictionary failed to load, by `.dic` name (`pt_BR`): the file and the reason. -/// Without it a file that is there but unreadable looks the same as no file (#2830). -static LOAD_ERRORS: RwLock> = RwLock::new(Vec::new()); - -/// Remember (or, for `Ok`, forget) why the dictionary `key` found at `dic` failed to load, and -/// hand the result back unchanged. -pub fn note_load(key: &str, dic: &Path, result: Result) -> Result { - let mut errors = LOAD_ERRORS.write().unwrap_or_else(std::sync::PoisonError::into_inner); - errors.retain(|(k, _, _)| k != key); - if let Err(e) = &result { - errors.push((key.to_string(), dic.to_path_buf(), e.clone())); - } - result -} - -/// Forget the load error of the dictionary `key` (none found, or it is about to be searched for again). -pub fn clear_load_error(key: &str) { - LOAD_ERRORS.write().unwrap_or_else(std::sync::PoisonError::into_inner).retain(|(k, _, _)| k != key); -} - -/// The `.dic` file and the reason of the last failed load of the dictionary `key`, if any. -pub fn load_error(key: &str) -> Option<(PathBuf, String)> { - LOAD_ERRORS.read().unwrap_or_else(std::sync::PoisonError::into_inner).iter().find(|(k, _, _)| k == key).map(|(_, p, e)| (p.clone(), e.clone())) -} - -/// Search `dir` before the system locations (a "dictionaries folder" setting); `None` clears it. -/// Takes effect for dictionaries not loaded yet. -pub fn set_dictionary_dir(dir: Option) { - *EXTRA_DIR.write().unwrap_or_else(std::sync::PoisonError::into_inner) = dir; - LOAD_ERRORS.write().unwrap_or_else(std::sync::PoisonError::into_inner).clear(); // they belong to the old search path -} - -/// The LibreOffice extension directories a Hunspell dictionary name may live in: LibreOffice's -/// IDs are per dictionary — Polish is `dict-pl`, Brazilian Portuguese `dict-pt-BR` — so both -/// the primary-language folder and the dashed full name are tried. -fn lo_ext_dirs(name: &str) -> Vec { - let primary = name.split(['-', '_']).next().unwrap_or(name); - let dashed = name.replace('_', "-"); - let first = format!("dict-{primary}"); - let full = format!("dict-{dashed}"); - if full == first { vec![first] } else { vec![first, full] } -} - -/// Directories that may hold Hunspell dictionaries, most specific first. `lo_exts`, when a -/// language is being searched for, adds LibreOffice's extension folders for it (`dict-pt-BR`). -pub fn search_dirs(lo_exts: Option<&[String]>) -> Vec { - let mut dirs: Vec = Vec::new(); - if let Some(d) = EXTRA_DIR.read().unwrap_or_else(std::sync::PoisonError::into_inner).clone() { - dirs.push(d); - } - if cfg!(target_arch = "wasm32") { - return dirs; - } - if let Some(d) = std::env::var_os("WORDCRAFT_DICTIONARIES").filter(|v| !v.is_empty()) { - dirs.push(PathBuf::from(d)); - } - // LibreOffice keeps each language under `…/share/extensions/`. - let lo_dirs = - |prefix: &str| -> Vec { lo_exts.map_or_else(Vec::new, |exts| exts.iter().map(|e| PathBuf::from(prefix).join(e)).collect()) }; - let home = std::env::var_os("HOME").filter(|v| !v.is_empty()).map(PathBuf::from); - if cfg!(windows) { - for var in ["LOCALAPPDATA", "APPDATA"] { - if let Some(base) = std::env::var_os(var).filter(|v| !v.is_empty()).map(PathBuf::from) { - dirs.push(base.join("WordCraft").join("dictionaries")); - dirs.push(base.join("hunspell")); - } - } - for var in ["ProgramFiles", "ProgramFiles(x86)"] { - if let Some(base) = std::env::var_os(var).filter(|v| !v.is_empty()).map(PathBuf::from) { - dirs.extend(lo_dirs(&base.join("LibreOffice").join("share").join("extensions").to_string_lossy())); - } - } - // If LibreOffice is installed on another drive (e.g. D:\Program Files\LibreOffice), check drives C..Z - for drive in b'C'..=b'Z' { - let drive_char = drive as char; - for pf in ["Program Files", "Program Files (x86)"] { - let lo_base = PathBuf::from(format!(r"{drive_char}:\{pf}\LibreOffice\share\extensions")); - if lo_base.is_dir() { - dirs.extend(lo_dirs(&lo_base.to_string_lossy())); - } - } - } - } else if cfg!(target_os = "macos") { - if let Some(h) = &home { - dirs.push(h.join("Library/Spelling")); - } - dirs.push(PathBuf::from("/Library/Spelling")); - dirs.push(PathBuf::from("/opt/homebrew/share/hunspell")); - dirs.push(PathBuf::from("/usr/local/share/hunspell")); - dirs.extend(lo_dirs("/Applications/LibreOffice.app/Contents/Resources/extensions")); - } else { - if let Some(h) = &home { - dirs.push(h.join(".local/share/hunspell")); - } - for d in ["/usr/share/hunspell", "/usr/share/myspell", "/usr/share/myspell/dicts", "/usr/local/share/hunspell", "/usr/local/share/myspell"] { - dirs.push(PathBuf::from(d)); - } - for d in ["/usr/lib/libreoffice/share/extensions", "/opt/libreoffice/share/extensions"] { - dirs.extend(lo_dirs(d)); - } - } - dirs -} - -/// The first `.aff`/`.dic` pair found for any of `names` (`pl_PL`, `pl`), searching -/// [`search_dirs`] (including LibreOffice's extension folder for the first name). -pub fn find(names: &[&str]) -> Option<(PathBuf, PathBuf)> { - let lo = names.first().copied().map(lo_ext_dirs); - search_dirs(lo.as_deref()).into_iter().find_map(|dir| find_in(&dir, names)) -} - -/// The first `.aff`/`.dic` pair for any of `names` in `dir`. -pub fn find_in(dir: &Path, names: &[&str]) -> Option<(PathBuf, PathBuf)> { - names.iter().find_map(|n| { - let aff = dir.join(format!("{n}.aff")); - let dic = dir.join(format!("{n}.dic")); - (aff.is_file() && dic.is_file()).then_some((aff, dic)) - }) -} - -/// Read a dictionary file: a regular file of at most [`MAX_FILE`] bytes. The read itself is -/// bounded, so a file that grows after its size is checked (or a filesystem that reports a wrong -/// size) can't make it read more (#3661). -pub(crate) fn read_capped(p: &Path) -> Result, String> { - use std::io::Read; - let err = |e: std::io::Error| format!("{}: {e}", p.display()); - let f = std::fs::File::open(p).map_err(err)?; - let meta = f.metadata().map_err(err)?; - if !meta.is_file() { - return Err(format!("{}: not a regular file", p.display())); - } - if meta.len() > MAX_FILE { - return Err(format!("{}: too large ({} bytes)", p.display(), meta.len())); - } - let mut buf = Vec::new(); - f.take(MAX_FILE + 1).read_to_end(&mut buf).map_err(err)?; - if buf.len() as u64 > MAX_FILE { - return Err(format!("{}: too large", p.display())); - } - Ok(buf) -} - -/// Read a Hunspell pair from disk (size-capped), decoded per the `.aff`'s `SET` line. -pub fn load(aff: &Path, dic: &Path) -> Result { - let (aff_text, dic_text) = decode(&read_capped(aff)?, &read_capped(dic)?)?; - let source = format!("Hunspell dictionary {}", dic.display()); - AffixDict::from_hunspell(&aff_text, &dic_text, &[source.as_str()]) -} - -/// ISO 8859-1: every byte is the code point of the same value (the original VERO pt_BR uses it). -fn decode_latin1(bytes: &[u8]) -> String { - bytes.iter().map(|&b| char::from(b)).collect() -} - -/// ISO 8859-9 (Latin-5, Turkish): ISO 8859-1 with Ğ İ Ş ğ ı ş in place of Ð Ý Þ ð ý þ. The -/// encoding of older Turkish dictionaries (`SET ISO8859-9`). -fn decode_latin5(bytes: &[u8]) -> String { - bytes - .iter() - .map(|&b| match b { - 0xd0 => 'Ğ', - 0xdd => 'İ', - 0xde => 'Ş', - 0xf0 => 'ğ', - 0xfd => 'ı', - 0xfe => 'ş', - _ => char::from(b), - }) - .collect() -} - -/// ISO 8859-2 code points 0xA0..=0xFF (0x00..=0x9F map to themselves). -const ISO_8859_2_HIGH: &str = "\u{a0}Ą˘Ł¤ĽŚ§¨ŠŞŤŹ\u{ad}ŽŻ°ą˛ł´ľśˇ¸šşťź˝žżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙"; - -fn decode_latin2(bytes: &[u8]) -> String { - let high: Vec = ISO_8859_2_HIGH.chars().collect(); - bytes.iter().map(|&b| if b < 0xa0 { char::from(b) } else { high.get(usize::from(b - 0xa0)).copied().unwrap_or('\u{fffd}') }).collect() -} - -/// KOI8-R code points 0x80..=0xFF (0x00..=0x7F are ASCII): the encoding of older Russian -/// dictionaries. -const KOI8_R_HIGH: &str = - "─│┌┐└┘├┤┬┴┼▀▄█▌▐░▒▓⌠■∙√≈≤≥\u{a0}⌡°²·÷═║╒ё╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡Ё╢╣╤╥╦╧╨╩╪╫╬©юабцдефгхийклмнопярстужвьызшэщчъЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ"; - -/// Windows-1251 code points 0x80..=0xFF (0x98 is unassigned): the other one Russian dictionaries -/// use. -const CP1251_HIGH: &str = "ЂЃ‚ѓ„…†‡€‰Љ‹ЊЌЋЏђ‘’“”•–—\u{fffd}™љ›њќћџ\u{a0}ЎўЈ¤Ґ¦§Ё©Є«¬\u{ad}®Ї°±Ііґµ¶·ё№є»јЅѕїАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя"; - -/// Decode a single-byte encoding whose bytes 0x80..=0xFF are `high` (ASCII below). -fn decode_high(bytes: &[u8], high: &str) -> String { - let high: Vec = high.chars().collect(); - bytes.iter().map(|&b| if b < 0x80 { char::from(b) } else { high.get(usize::from(b - 0x80)).copied().unwrap_or('\u{fffd}') }).collect() -} - -/// Decode an `.aff`/`.dic` pair to UTF-8 from the encoding the `.aff`'s `SET` line names -/// (UTF-8 when there is none; ISO 8859-2 is the other one Polish dictionaries use, ISO 8859-1 the -/// one the original Portuguese VERO uses, ISO 8859-9 the one older Turkish dictionaries use, KOI8-R -/// and Windows-1251 the ones older Russian dictionaries use). -/// -/// Under a UTF-8 `SET` with single-byte flags (no `FLAG` line, or `FLAG DEFAULT`), flags are bytes, -/// as in Hunspell: the Hungarian dictionary writes its affix flags and flag aliases as bytes above -/// 0x7F that aren't UTF-8. Those fields are read byte by byte (each byte becomes U+0000..U+00FF, -/// one flag per byte); the words, affixes and conditions around them are UTF-8. With any other -/// `FLAG` (`UTF-8`, `num`, `long`) flags are text and the whole file is UTF-8: `SFX 1234` keeps -/// its number (the Turkish dictionary's `FLAG num`). -pub fn decode(aff: &[u8], dic: &[u8]) -> Result<(String, String), String> { - let (set, flag) = directives(aff); - let set = set.unwrap_or_else(|| "UTF-8".into()); - match set.as_str() { - "UTF-8" | "UTF8" => { - if flag.as_deref().is_some_and(|f| f != "DEFAULT") { - let text = |b: &[u8], what: &str| String::from_utf8(b.to_vec()).map_err(|e| format!("{what}: not UTF-8: {e}")); - return Ok((text(aff, ".aff")?, text(dic, ".dic")?)); - } - Ok((decode_utf8_aff(aff)?, decode_utf8_dic(dic)?)) - } - "ISO8859-1" | "ISO-8859-1" => Ok((decode_latin1(aff), decode_latin1(dic))), - "ISO8859-2" | "ISO-8859-2" => Ok((decode_latin2(aff), decode_latin2(dic))), - "ISO8859-9" | "ISO-8859-9" => Ok((decode_latin5(aff), decode_latin5(dic))), - "KOI8-R" | "KOI8R" => Ok((decode_high(aff, KOI8_R_HIGH), decode_high(dic, KOI8_R_HIGH))), - "CP1251" | "WINDOWS-1251" | "MICROSOFT-CP1251" => Ok((decode_high(aff, CP1251_HIGH), decode_high(dic, CP1251_HIGH))), - other => Err(format!("unsupported encoding {other}")), - } -} - -/// The first arguments of the `.aff`'s first `SET` and `FLAG` lines, in capitals, found in one pass -/// over the whole file: a `FLAG UTF-8` after the rules still decides how the `.dic` flags are read, -/// as in Hunspell. Only the first argument counts: `FLAG UTF-8 # Unicode flags` is UTF-8. -fn directives(aff: &[u8]) -> (Option, Option) { - let (mut set, mut flag) = (None, None); - for l in aff.split(|&b| b == b'\n') { - if set.is_some() && flag.is_some() { - break; - } - let l = l.strip_prefix("\u{feff}".as_bytes()).unwrap_or(l); - let slot = match l.first() { - Some(b'S') if set.is_none() => (&mut set, &b"SET"[..]), - Some(b'F') if flag.is_none() => (&mut flag, &b"FLAG"[..]), - _ => continue, - }; - let arg = l - .strip_prefix(slot.1) - .filter(|rest| rest.first().is_some_and(|b| matches!(b, b' ' | b'\t'))) - .and_then(|rest| rest.split(|b| matches!(b, b' ' | b'\t' | b'\r')).find(|f| !f.is_empty())); - if let Some(a) = arg { - *slot.0 = Some(String::from_utf8_lossy(a).to_ascii_uppercase()); - } - } - (set, flag) -} - -/// Directives whose one argument is a flag. -const FLAG_DIRECTIVES: &[&str] = &[ - "CIRCUMFIX", - "COMPOUNDBEGIN", - "COMPOUNDEND", - "COMPOUNDFLAG", - "COMPOUNDFORBIDFLAG", - "COMPOUNDLAST", - "COMPOUNDFIRST", - "COMPOUNDMIDDLE", - "COMPOUNDPERMITFLAG", - "COMPOUNDROOT", - "FORBIDDENWORD", - "FORCEUCASE", - "GENERATE", - "HU_KOTOHANGZO", - "KEEPCASE", - "LEMMA_PRESENT", - "NEEDAFFIX", - "NOSUGGEST", - "ONLYINCOMPOUND", - "ONLYROOT", - "PSEUDOROOT", - "SUBSTANDARD", - "WARN", -]; - -/// Directives that only describe the dictionary; a non-UTF-8 byte in them (the Hungarian -/// dictionary's `NAME` is ISO 8859-2) is replaced instead of failing the load. -const METADATA_DIRECTIVES: &[&str] = &["NAME", "HOME", "VERSION", "LANG", "AM"]; - -/// `text/flags`: the text as UTF-8 and the flags byte by byte (`\/` is a literal slash). `what` -/// names the line in an error. -fn text_and_flags(field: &[u8], what: impl Fn() -> String) -> Result { - let mut slash = None; - let mut i = 0; - while let Some(&b) = field.get(i) { - if b == b'\\' && field.get(i + 1) == Some(&b'/') { - i += 2; - continue; - } - if b == b'/' { - slash = Some(i); - break; - } - i += 1; - } - let (text, flags) = match slash { - Some(k) => (field.get(..k).unwrap_or_default(), field.get(k..).unwrap_or_default()), - None => (field, &[][..]), - }; - let mut out = std::str::from_utf8(text).map_err(|e| format!("{}: not UTF-8: {e}", what()))?.to_string(); - out.push_str(&decode_latin1(flags)); - Ok(out) -} - -/// Split a line into fields on spaces and tabs, keeping the separators (so a line rebuilt from -/// its parts is the original). -fn fields(line: &[u8]) -> Vec<&[u8]> { - let mut out = Vec::new(); - let mut start = 0; - let mut in_sep = line.first().is_some_and(|b| matches!(b, b' ' | b'\t')); - for (i, b) in line.iter().enumerate() { - let sep = matches!(b, b' ' | b'\t'); - if sep != in_sep { - out.push(line.get(start..i).unwrap_or_default()); - start = i; - in_sep = sep; - } - } - out.push(line.get(start..).unwrap_or_default()); - out -} - -/// A UTF-8 `.aff` with single-byte flags (see [`decode`]). -fn decode_utf8_aff(aff: &[u8]) -> Result { - let mut out = String::with_capacity(aff.len()); - for (n, line) in aff.split(|&b| b == b'\n').enumerate() { - if n > 0 { - out.push('\n'); - } - if let Ok(s) = std::str::from_utf8(line) - && s.is_ascii() - { - out.push_str(s); - continue; - } - let parts = fields(line); - // Field index among the non-separator parts. - let words: Vec = - parts.iter().enumerate().filter(|(_, p)| !p.first().is_some_and(|b| matches!(b, b' ' | b'\t'))).map(|(i, _)| i).collect(); - let dir = words - .first() - .and_then(|&i| parts.get(i)) - .map(|p| String::from_utf8_lossy(p).trim_start_matches('\u{feff}').to_string()) - .unwrap_or_default(); - let what = || format!(".aff line {}", n + 1); - if dir.starts_with('#') || METADATA_DIRECTIVES.contains(&dir.as_str()) { - out.push_str(&String::from_utf8_lossy(line)); - continue; - } - // A trailing `# comment` (the Hungarian `AF` lines number themselves) is never a word or - // a flag; from there on the line is kept as text. It can only start after the directive's - // own fields, since a field such as an affix condition may itself begin with `#`. - let field_text = |k: usize| words.get(k).and_then(|&w| parts.get(w)).map(|p| String::from_utf8_lossy(p).into_owned()); - let first_free = comment_field(&dir, field_text); - let comment_at = words.iter().skip(first_free).position(|&w| parts.get(w).is_some_and(|f| f.first() == Some(&b'#'))).map(|k| k + first_free); - // The field index of the next non-separator part. - let mut next_field = 0; - for (i, p) in parts.iter().enumerate() { - let field = (!p.first().is_some_and(|b| matches!(b, b' ' | b'\t'))).then(|| { - next_field += 1; - next_field - 1 - }); - let in_comment = comment_at.is_some_and(|c| words.get(c).is_some_and(|&w| i >= w)); - let decoded = match (dir.as_str(), field) { - _ if in_comment => String::from_utf8_lossy(p).into_owned(), - ("AF" | "COMPOUNDRULE" | "SYLLABLENUM", Some(1)) => decode_latin1(p), - ("PFX" | "SFX", Some(3)) | ("CHECKCOMPOUNDPATTERN", Some(1 | 2)) => text_and_flags(p, what)?, - // A field that names one flag keeps its first byte, as Hunspell's `decode_flag` - // does: `SFX é` is the flag 0xC3, the first of the two flags `kert/é` carries. - ("PFX" | "SFX", Some(1)) => decode_latin1(p.get(..1).unwrap_or_default()), - (d, Some(1)) if FLAG_DIRECTIVES.contains(&d) => decode_latin1(p.get(..1).unwrap_or_default()), - _ => std::str::from_utf8(p).map_err(|e| format!("{}: not UTF-8: {e}", what()))?.to_string(), - }; - out.push_str(&decoded); - } - } - Ok(out) -} - -/// The first field of an `.aff` line where a trailing `# comment` may start: after the -/// directive's own fields. A table's count line (`REP 12`, `PFX A Y 3`) has fewer fields than its -/// rows (`REP a b`, `PFX A 0 x .`). `field(k)` is the line's `k`-th field. -fn comment_field(dir: &str, field: impl Fn(usize) -> Option) -> usize { - let number = |k: usize| field(k).is_some_and(|f| f.parse::().is_ok()); - match dir { - "PFX" | "SFX" if field(2).is_some_and(|f| f == "Y" || f == "N") && number(3) => 4, - "PFX" | "SFX" => 5, - "REP" | "ICONV" | "OCONV" | "PHONE" | "MAP" | "BREAK" | "AF" | "AM" | "COMPOUNDRULE" | "CHECKCOMPOUNDPATTERN" - if number(1) && field(2).is_none_or(|f| f.starts_with('#')) => - { - 2 - } - "REP" | "ICONV" | "OCONV" | "PHONE" => 3, - "CHECKCOMPOUNDPATTERN" => 4, - _ => 2, - } -} - -/// Where the `word/flags` entry of a `.dic` line ends, as Hunspell reads it: at a tab or at the -/// first morphological field (a space or tab, two characters and `:`, as in ` po:noun`), whichever -/// comes first, so an entry may hold spaces (`ad hoc`); with neither, at the end of the line -/// (`kot/B 12` has the flags `B 12`, as in Hunspell). -pub(crate) fn dic_entry_end(line: &[u8]) -> usize { - let blank = |b: &u8| matches!(b, b' ' | b'\t'); - let morph = line.windows(4).position(|w| matches!(w, [a, b, c, b':'] if blank(a) && !blank(b) && !blank(c))); - let tab = line.iter().position(|&b| b == b'\t'); - let end = match (morph, tab) { - (Some(m), Some(t)) => m.min(t), - (m, t) => match m.or(t) { - Some(e) => e, - None => line.len(), - }, - }; - // Blanks before the morphological field or the line end aren't part of the entry. - line.get(..end).map_or(end, |e| e.iter().rposition(|b| !blank(b)).map_or(0, |k| k + 1)) -} - -/// A UTF-8 `.dic` with single-byte flags: the word is UTF-8, the flags after `/` are bytes. -fn decode_utf8_dic(dic: &[u8]) -> Result { - let mut out = String::with_capacity(dic.len()); - for (n, line) in dic.split(|&b| b == b'\n').enumerate() { - if n > 0 { - out.push('\n'); - } - if let Ok(s) = std::str::from_utf8(line) - && s.split_once('/').is_none_or(|(_, flags)| flags.is_ascii()) - { - out.push_str(s); - continue; - } - let what = || format!(".dic line {}", n + 1); - // The first field is `word/flags` (it may hold spaces, see [`dic_entry_end`]); - // morphological fields after it are UTF-8 text. - let end = dic_entry_end(line); - out.push_str(&text_and_flags(line.get(..end).unwrap_or_default(), what)?); - let rest = line.get(end..).unwrap_or_default(); - out.push_str(std::str::from_utf8(rest).map_err(|e| format!("{}: not UTF-8: {e}", what()))?); - } - Ok(out) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn decodes_latin2_and_utf8() { - let aff = b"SET ISO8859-2\nTRY a\n"; - let (a, d) = decode(aff, b"2\n\xbf\xf3\xb3w\n").unwrap(); - assert!(a.contains("TRY a")); - assert_eq!(d, "2\nżółw\n"); - let (_, d) = decode("SET UTF-8\n".as_bytes(), "żółw".as_bytes()).unwrap(); - assert_eq!(d, "żółw"); - let (_, d) = decode(b"SET ISO8859-9\n", b"\xd0\xdd\xde\xf0\xfd\xfe\xd6\xe7").unwrap(); - assert_eq!(d, "ĞİŞğışÖç"); - assert!(decode(b"SET SHIFT_JIS\n", b"").is_err()); - assert!(decode(b"SET UTF-8\n", b"\xff\xfe").is_err()); - } - - /// #2830: a dictionary that is there but can't be read leaves its reason behind; a good - /// load or no file clears it. - #[test] - fn load_error_is_remembered_until_the_next_good_load() { - let key = "test_unreadable_2830"; - assert_eq!(load_error(key), None); - let dic = Path::new("/dicts/test.dic"); - assert!(note_load(key, dic, decode(b"SET SHIFT_JIS\n", b"")).is_err()); - let (file, reason) = load_error(key).expect("error kept"); - assert_eq!(file, dic); - assert_eq!(reason, "unsupported encoding SHIFT_JIS"); - assert!(note_load(key, dic, decode(b"SET UTF-8\n", b"")).is_ok()); - assert_eq!(load_error(key), None); - let _ = note_load::<()>(key, dic, Err("too large".into())); - clear_load_error(key); - assert_eq!(load_error(key), None); - } - - #[test] - fn decodes_latin1() { - // `orçamento esperávamos` in ISO 8859-1: ç = 0xE7, á = 0xE1. - let (a, d) = decode(b"SET ISO8859-1\nTRY \xe7\n", b"1\nor\xe7amento/A\nesper\xe1vamos\n").unwrap(); - assert!(a.contains("TRY ç"), "{a}"); - assert_eq!(d, "1\norçamento/A\nesperávamos\n"); - assert!(decode(b"SET ISO-8859-1\n", b"\xe7").is_ok()); - } - - #[test] - fn utf8_dictionaries_keep_single_byte_flags() { - // The Hungarian dictionary is `SET UTF-8` but writes flags as single bytes above 0x7F, - // which aren't UTF-8; each byte is one flag. Its `NAME` line is ISO 8859-2. - let aff = b"SET UTF-8\nNAME Magyar helyes\xedr\xe1si\nAF 2\nAF \xd5a # 1\nAF \xe9 # 2\nSFX \xd5 Y 1\nSFX \xd5 0 ok/2 . 7\nSFX \xe9 Y 1\nSFX \xe9 0 ban .\nSFX a Y 1\nSFX a 0 at .\n"; - let dic = "2\nház/1\t1\nkert/\u{e9}\n".as_bytes(); - let (a, d) = decode(aff, dic).unwrap(); - assert!(a.contains("AF \u{d5}a # 1") && a.contains("AF \u{e9} # 2") && a.contains("SFX \u{d5} 0 ok/2 . 7")); - assert!(d.contains("kert/\u{c3}\u{a9}"), "a UTF-8 é in the flags is two one-byte flags"); - let dict = crate::affix::AffixDict::from_hunspell(&a, &d, &[]).unwrap(); - assert!(dict.check("ház") && dict.check("házok") && dict.check("házokban") && dict.check("házat")); - assert!(!dict.check("házban")); - // `FLAG UTF-8` keeps the whole file UTF-8, and words must still be UTF-8. - let (_, d) = decode(b"SET UTF-8\nFLAG UTF-8\n", "kert/\u{e9}\n".as_bytes()).unwrap(); - assert_eq!(d, "kert/\u{e9}\n"); - assert!(decode(b"SET UTF-8\nSFX a Y 1\nSFX a 0 \xe9 .\n", b"").is_err(), "an affix must be UTF-8"); - assert!(decode(b"SET UTF-8\n", b"1\nh\xe1z/a\n").is_err(), "a stem must be UTF-8"); - } - - #[test] - fn single_flag_fields_keep_their_first_byte() { - // Without `FLAG UTF-8`, a UTF-8 `é` (0xC3 0xA9) in a field that names one flag is the - // flag 0xC3; in a `.dic` flag list it is the two flags 0xC3 and 0xA9. - let aff = "SET UTF-8\nKEEPCASE é\nSFX é Y 1\nSFX é 0 ok .\nSYLLABLENUM é\n".as_bytes(); - let (a, d) = decode(aff, "2\nkert/é\nNATO/é\n".as_bytes()).unwrap(); - assert!(a.contains("KEEPCASE \u{c3}\n") && a.contains("SFX \u{c3} Y 1") && a.contains("SFX \u{c3} 0 ok ."), "{a}"); - assert!(a.contains("SYLLABLENUM \u{c3}\u{a9}"), "SYLLABLENUM lists flags: {a}"); - assert!(d.contains("kert/\u{c3}\u{a9}")); - let dict = crate::affix::AffixDict::from_hunspell(&a, &d, &[]).unwrap(); - assert!(dict.check("kert") && dict.check("kertok")); - assert!(dict.check("NATO") && !dict.check("Nato"), "KEEPCASE é reaches NATO/é"); - } - - #[test] - fn numeric_and_long_flags_stay_text() { - // `FLAG num` (the Turkish dictionary) and `FLAG long`: no byte-wise flag reading, so a - // number with a non-ASCII rule or comment keeps every digit. - let aff = "SET UTF-8\nFLAG num\nFORBIDDENWORD 6000 # tiltott ő\nSFX 1234 Y 1\nSFX 1234 0 ları .\n"; - let (a, d) = decode(aff.as_bytes(), "1\nkitap/1234\n".as_bytes()).unwrap(); - assert_eq!(a, aff); - assert_eq!(d, "1\nkitap/1234\n"); - let tr = crate::tr::dict::TrDict::from_hunspell(&a, &d).unwrap(); - assert!(tr.contains("kitapları") && tr.contains("kitap")); - let long = "SET UTF-8\nFLAG long\nSFX Aa Y 1\nSFX Aa 0 ős .\n"; - assert_eq!(decode(long.as_bytes(), "1\nkő/Aa\n".as_bytes()).unwrap().0, long); - assert!(decode(b"SET UTF-8\nFLAG num\n", b"1\nh\xe1z/1\n").is_err(), "the .dic must be UTF-8"); - // `FLAG DEFAULT` is the single-byte default. - let (_, d) = decode(b"SET UTF-8\nFLAG default\n", b"1\nkert/\xe9\n").unwrap(); - assert_eq!(d, "1\nkert/\u{e9}\n"); - } - - #[test] - fn flag_utf8_counts_anywhere_in_the_aff() { - // A `FLAG UTF-8` after a long rule table still makes the `.dic` flags Unicode. - let mut aff = b"SET UTF-8\nSFX a Y 400\n".to_vec(); - for _ in 0..400 { - aff.extend_from_slice(b"SFX a 0 aaaaaaaaaaaaaaaaaaaaaaaa .\n"); - } - aff.extend_from_slice(b"FLAG UTF-8\r\n"); - assert!(aff.len() > 8192); - let (_, d) = decode(&aff, "kert/\u{e9}\n".as_bytes()).unwrap(); - assert_eq!(d, "kert/\u{e9}\n"); - } - - #[test] - fn a_comment_after_flag_utf8_keeps_unicode_flags() { - let (_, d) = decode(b"SET UTF-8\nFLAG UTF-8 # Unicode flags\n", "kert/\u{e9}\n".as_bytes()).unwrap(); - assert_eq!(d, "kert/\u{e9}\n"); - let (_, d) = decode(b"SET\tUTF-8 # comment\nFLAG\t\tUTF-8\n", "kert/\u{e9}\n".as_bytes()).unwrap(); - assert_eq!(d, "kert/\u{e9}\n", "repeated spaces and tabs separate the argument"); - } - - #[test] - fn decodes_russian_single_byte_encodings() { - // `ёлка` in KOI8-R (0xA3 0xCC 0xCB 0xC1) and in Windows-1251 (0xB8 0xEB 0xEA 0xE0). - let (a, d) = decode(b"SET KOI8-R\nTRY \xcf\xc5\n", b"1\n\xa3\xcc\xcb\xc1/A\n").unwrap(); - assert!(a.contains("TRY ое"), "{a}"); - assert_eq!(d, "1\nёлка/A\n"); - let (_, d) = decode(b"SET microsoft-cp1251\n", b"1\n\xb8\xeb\xea\xe0 \xc4\xee\xec\n").unwrap(); - assert_eq!(d, "1\nёлка Дом\n"); - assert_eq!(decode_high(&[0x98], CP1251_HIGH), "\u{fffd}"); - } - - #[test] - fn comments_start_after_the_directive_fields() { - let f = |line: &'static str| move |k: usize| line.split_whitespace().nth(k).map(str::to_string); - assert_eq!(comment_field("PFX", f("PFX A Y 3 # count")), 4); - assert_eq!(comment_field("SFX", f("SFX A 0 # .")), 5, "a rule's condition may be `#`"); - assert_eq!(comment_field("AF", f("AF 1234 # aliases")), 2); - assert_eq!(comment_field("AF", f("AF Aa # 1")), 2); - assert_eq!(comment_field("REP", f("REP 12")), 2); - assert_eq!(comment_field("REP", f("REP # x")), 3); - assert_eq!(comment_field("CHECKCOMPOUNDPATTERN", f("CHECKCOMPOUNDPATTERN a b c")), 4); - assert_eq!(comment_field("KEEPCASE", f("KEEPCASE k")), 2); - // A byte flag after an append text that begins with `#` stays a flag, not a comment. - let (a, _) = decode(b"SET UTF-8\nSFX \xd5 Y 1 # header\nSFX \xd5 0 #/\xe9 . # rule\n", b"").unwrap(); - assert!(a.contains("SFX \u{d5} 0 #/\u{e9} . # rule"), "{a}"); - } - - #[test] - fn dic_entries_end_where_hunspell_ends_them() { - let end = |l: &str| l.get(..dic_entry_end(l.as_bytes())).unwrap().to_string(); - assert_eq!(end("word/A po:noun\tts:x"), "word/A", "the morphological field comes before the tab"); - assert_eq!(end("ad hoc/B\tpo:adv"), "ad hoc/B", "a tab ends a phrase"); - assert_eq!(end("ad hoc/B po:adv"), "ad hoc/B"); - assert_eq!(end("word/A 123"), "word/A 123", "neither: the whole line, as in Hunspell"); - assert_eq!(end("ad hoc/B "), "ad hoc/B"); - assert_eq!(end("word"), "word"); - assert_eq!(end("a:b/A"), "a:b/A", "a colon inside the word isn't a field"); - // Single-byte flags of a phrase entry are read byte by byte, then the UTF-8 morphology. - let (_, d) = decode(b"SET UTF-8\n", b"1\nad hoc/\xe9\tpo:adv\n").unwrap(); - assert_eq!(d, "1\nad hoc/\u{e9}\tpo:adv\n"); - // Without a tab or a morphological field the whole line is the entry: its byte flags - // don't fail the load (the Hungarian dictionary has such lines: `tök jó/396`). - let (_, d) = decode(b"SET UTF-8\n", b"1\nad hoc/\xe9\n").unwrap(); - assert_eq!(d, "1\nad hoc/\u{e9}\n"); - } - - #[test] - fn libreoffice_extension_dirs_follow_the_language() { - // LibreOffice's IDs are per dictionary: Polish is `dict-pl`, Brazilian Portuguese - // `dict-pt-BR`, so both the primary-language folder and the dashed full name are tried. - assert_eq!(super::lo_ext_dirs("cs_CZ"), ["dict-cs", "dict-cs-CZ"]); - assert_eq!(super::lo_ext_dirs("id_ID"), ["dict-id", "dict-id-ID"]); - assert_eq!(super::lo_ext_dirs("pl_PL"), ["dict-pl", "dict-pl-PL"]); - assert_eq!(super::lo_ext_dirs("pt_BR"), ["dict-pt", "dict-pt-BR"]); - assert_eq!(super::lo_ext_dirs("de-DE"), ["dict-de", "dict-de-DE"]); - let dirs = search_dirs(Some(&super::lo_ext_dirs("pt_BR"))); - assert!(dirs.iter().any(|d| d.ends_with("dict-pt-BR")), "{dirs:?}"); - assert!(search_dirs(None).iter().all(|d| !d.to_string_lossy().contains("dict-")), "no language: no LibreOffice guess"); - } - - #[test] - fn finds_and_loads_a_pair_in_a_directory() { - let dir = std::env::temp_dir().join(format!("wordcraft-hunspell-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - assert!(find_in(&dir, &["xx_XX"]).is_none(), "no files: nothing found"); - std::fs::write(dir.join("xx_XX.aff"), "SET UTF-8\nSFX A Y 1\nSFX A 0 y .\n").unwrap(); - assert!(find_in(&dir, &["xx_XX"]).is_none(), "an .aff alone isn't a dictionary"); - std::fs::write(dir.join("xx_XX.dic"), "1\nkot/A\n").unwrap(); - let (aff, dic) = find_in(&dir, &["yy", "xx_XX"]).unwrap(); - let d = load(&aff, &dic).unwrap(); - assert!(d.check("kot") && d.check("koty") && !d.check("kotx")); - assert!(load(&dir.join("missing.aff"), &dic).is_err()); - let _ = std::fs::remove_dir_all(&dir); - } - - #[test] - fn reads_are_bounded_and_regular_files_only() { - let dir = std::env::temp_dir().join(format!("wordcraft-hunspell-cap-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let aff = dir.join("xx.aff"); - std::fs::write(&aff, "SET UTF-8\n").unwrap(); - // One byte over the cap (sparse: nothing is written) is refused without reading it. - let big = dir.join("big.dic"); - std::fs::File::create(&big).unwrap().set_len(MAX_FILE + 1).unwrap(); - assert!(load(&aff, &big).unwrap_err().contains("too large")); - // A directory, and on Unix an endless device, are not dictionary files. (No clock: a - // reader that read `/dev/zero` would never return, so the error itself is the proof.) - assert!(load(&aff, &dir).unwrap_err().contains("not a regular file")); - #[cfg(unix)] - assert!(load(&aff, Path::new("/dev/zero")).unwrap_err().contains("not a regular file")); - // Turkish reads through the same reader. - let (tr_aff, tr_dic) = (dir.join("tr_TR.aff"), dir.join("tr_TR.dic")); - std::fs::write(&tr_aff, "SET UTF-8\nSFX 1 Y 1\nSFX 1 0 lar .\n").unwrap(); - std::fs::write(&tr_dic, "1\nkitap/1\n").unwrap(); - assert!(crate::tr::load(&tr_aff, &tr_dic).unwrap().contains("kitaplar")); - assert!(crate::tr::load(&tr_aff, &big).unwrap_err().contains("too large")); - let _ = std::fs::remove_dir_all(&dir); - } - - /// A Turkish dictionary in ISO 8859-9, the encoding older tr_TR dictionaries use, read from - /// disk through the capped reader and decoded per its `SET` line: its words with Turkish - /// letters (ğ ı ş İ, and ç ö ü from Latin-1) are found, as stems and with a suffix. Before the - /// Turkish dictionary went through this reader it was read as UTF-8, which these bytes are not. - #[test] - fn a_turkish_dictionary_in_iso_8859_9_loads_from_disk() { - let dir = std::env::temp_dir().join(format!("wordcraft-hunspell-latin5-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let (aff, dic) = (dir.join("tr_TR.aff"), dir.join("tr_TR.dic")); - std::fs::write(&aff, b"SET ISO8859-9\nSFX 1 Y 2\nSFX 1 0 lar .\nSFX 1 0 \xfd .\nSFX 2 Y 1\nSFX 2 0 ler .\n").unwrap(); - // ağaç/1 (-lar, -ı), ışık, şişe/2 (-ler), İzmir, öğrenci/2, güneş. - let words: &[u8] = b"6\na\xf0a\xe7/1\n\xfd\xfe\xfdk\n\xfei\xfee/2\n\xddzmir\n\xf6\xf0renci/2\ng\xfcne\xfe\n"; - std::fs::write(&dic, words).unwrap(); - assert!(String::from_utf8(words.to_vec()).is_err(), "the .dic is not UTF-8"); - let d = crate::tr::load(&aff, &dic).unwrap(); - for w in ["ağaç", "ağaçlar", "ağaçı", "ışık", "şişe", "şişeler", "İzmir", "öğrenci", "öğrenciler", "güneş"] { - assert!(d.contains(w), "{w}"); - } - // The six letters ISO 8859-9 puts in place of ISO 8859-1's aren't read as those. - assert!(!d.contains("aðaç") && !d.contains("ýþýk") && !d.contains("Ýzmir")); - assert!(!d.contains("ağaçs") && !d.contains("ışıklar") && !d.contains("şişelar"), "only the rules' suffixes, on the stems that take them"); - let _ = std::fs::remove_dir_all(&dir); - } -} diff --git a/crates/proof/src/hyphen.rs b/crates/proof/src/hyphen.rs deleted file mode 100644 index b203ff1a2..000000000 --- a/crates/proof/src/hyphen.rs +++ /dev/null @@ -1,920 +0,0 @@ -//! Hyphenation, per proofing language ([`ProofLang`]). -//! -//! **English** (and languages without patterns of their own, as before languages were told -//! apart), lookup order for each word: -//! 1. the **dictionary** ([`Dictionary::en_us`]) — the public-domain Moby Hyphenator II word list -//! by Grady Ward (~160k words), stored compactly in `assets/hyphenation/en-us.dic`; simple -//! inflections (`'s`, `-s`, `-es`, `-ed`, `-d`) inherit the breaks of their stem; -//! 2. **Liang patterns** ([`Patterns::en_us`]) for words not in the dictionary — our own pattern -//! set, trained from the same list by DesignCraft's patgen (generated by DesignCraft's `hyphgen` example); -//! 3. a small syllable **heuristic** for words outside the pattern alphabet. -//! -//! **Polish**: the exception words, then the TeX Polish patterns ([`Patterns::pl`], by Hanna -//! Kołodziejska, Bogusław Jackowski and Marek Ryćko, MIT licence option), then a Polish syllable -//! heuristic for words with letters outside the Polish alphabet (`é`, `ü`…). -//! -//! **Bulgarian**: the Bulgarian syllable rules ([`crate::bg`]); no pattern file or word list. -//! -//! **German**: the TeX German patterns for the reformed spelling ([`Patterns::de`]), also for -//! Swiss text, whose `ss` the patterns split like any double consonant (`Stras-se`). -//! -//! Then the paragraph's limits ([`Limits`]) are applied. - -pub use crate::dict; - -pub use crate::patterns; - -use std::sync::OnceLock; - -pub use crate::lang::ProofLang; -pub use dict::Dictionary; -pub use patterns::Patterns; - -static EN_US_DIC: &[u8] = include_bytes!("../../../assets/hyphenation/en-us.dic"); -static EN_US_PAT: &str = include_str!("../../../assets/hyphenation/en-us.pat"); -/// hyph-utf8's `hyph-pl.tex`, unmodified (its licence header is in the file). -static PL_PAT: &str = include_str!("../../../assets/hyphenation/pl.pat"); -/// hyph-utf8's `hyph-pt.tex`, unmodified (its BSD 3-clause header is in the file). -static PT_PAT: &str = include_str!("../../../assets/hyphenation/pt.pat"); -/// hyph-utf8's `hyph-it.tex`, unmodified (offered under MIT or LPPL; WordCraft uses MIT, whose -/// notice is in the file's header). -static IT_PAT: &str = include_str!("../../../assets/hyphenation/it.pat"); -/// hyph-utf8's `hyph-de-1996.tex` (reformed spelling), unmodified (MIT; the notice is in the -/// file's header). -static DE_PAT: &str = include_str!("../../../assets/hyphenation/de.pat"); - -impl Dictionary { - /// The bundled US English dictionary (decoded on first use). - pub fn en_us() -> &'static Dictionary { - static D: OnceLock = OnceLock::new(); - D.get_or_init(|| { - if EN_US_DIC.is_empty() { - return Dictionary::default(); - } - Dictionary::from_bytes(EN_US_DIC).unwrap_or_else(|e| { - log::error!("{e}"); - Dictionary::default() - }) - }) - } -} - -impl Patterns { - /// The bundled US English patterns (parsed on first use). - pub fn en_us() -> &'static Patterns { - static P: OnceLock = OnceLock::new(); - P.get_or_init(|| Patterns::parse(EN_US_PAT)) - } - - /// The bundled Polish patterns and exception words (parsed on first use). - pub fn pl() -> &'static Patterns { - static P: OnceLock = OnceLock::new(); - P.get_or_init(|| Patterns::parse(PL_PAT)) - } - - /// The bundled Portuguese patterns and exception words (parsed on first use). - pub fn pt() -> &'static Patterns { - static P: OnceLock = OnceLock::new(); - P.get_or_init(|| Patterns::parse(PT_PAT)) - } - - /// The bundled Italian patterns (parsed on first use; the file has no exception words). - pub fn it() -> &'static Patterns { - static P: OnceLock = OnceLock::new(); - P.get_or_init(|| Patterns::parse(IT_PAT)) - } - - /// The bundled German patterns (parsed on first use; the file has no exception words). - pub fn de() -> &'static Patterns { - static P: OnceLock = OnceLock::new(); - P.get_or_init(|| Patterns::parse(DE_PAT)) - } -} - -/// Hyphenation limits from the paragraph's Hyphenation settings. -#[derive(Clone, Copy, Debug)] -pub struct Limits { - pub min_word: usize, - pub after_first: usize, - pub before_last: usize, - pub capitalized: bool, -} - -impl Default for Limits { - fn default() -> Self { - Limits { min_word: 5, after_first: 2, before_last: 2, capitalized: true } - } -} - -/// Where a word's break points came from. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Source { - Dictionary, - Patterns, - Heuristic, - /// A language's syllable rules (Bulgarian, [`crate::bg`]). - Rules, - None, -} - -fn is_apostrophe(c: char) -> bool { - matches!(c, '\'' | '’' | 'ʼ') -} - -fn is_word_char(c: char) -> bool { - c.is_alphabetic() || is_apostrophe(c) -} - -/// Unrestricted break points of one lowercase core (letters and `'` only, no leading/trailing `'`). -fn core_breaks(core: &[char], lang: ProofLang) -> (Vec, Source) { - let n = core.len(); - if n < 2 { - return (vec![], Source::None); - } - if lang == ProofLang::Pl { - return core_breaks_pl(core); - } - // Bulgarian words follow the Bulgarian syllable rules; Latin-script words in a Bulgarian run - // (`WordCraft`, `email`) are hyphenated as English, as in any other language. - if lang == ProofLang::Bg && core.iter().all(|&c| crate::bg::is_letter(c)) { - return (crate::bg::points(core), Source::Rules); - } - if lang == ProofLang::Tr && core.iter().all(|&c| crate::tr::hyphen::is_letter(c)) { - return (crate::tr::hyphen::points(core), Source::Rules); - } - match lang { - ProofLang::Pt => return core_breaks_patterns(Patterns::pt(), core), - ProofLang::It => return core_breaks_patterns(Patterns::it(), core), - ProofLang::De => return core_breaks_patterns(Patterns::de(), core), - _ => {} - } - if lang == ProofLang::Ru && core.iter().all(|&c| crate::ru::is_cyrillic(c)) { - return (crate::ru::hyphen::points(core), Source::Heuristic); - } - let dict = Dictionary::en_us(); - let s: String = core.iter().collect(); - let from_mask = |m: u64, len: usize| (1..len.min(64)).filter(|&i| m & (1 << i) != 0).collect::>(); - if let Some(m) = dict.get(&s) { - return (from_mask(m, n), Source::Dictionary); - } - // Inflections inherit the stem's breaks (the suffix adds no break of its own). - for suf in ["'s", "s", "es", "ed", "d"] { - if let Some(stem) = s.strip_suffix(suf) - && stem.chars().count() >= 2 - && let Some(m) = dict.get(stem) - { - return (from_mask(m, stem.chars().count()), Source::Dictionary); - } - } - let pats = Patterns::en_us(); - if !pats.is_empty() - && let Some(p) = pats.points(core) - { - return (p, Source::Patterns); - } - (heuristic_points(core), Source::Heuristic) -} - -/// Polish: exception words, then the patterns, then the Polish syllable heuristic. -fn core_breaks_pl(core: &[char]) -> (Vec, Source) { - let pats = Patterns::pl(); - let s: String = core.iter().collect(); - if let Some(p) = pats.exception(&s) { - return (p.to_vec(), Source::Dictionary); - } - if !pats.is_empty() - && let Some(p) = pats.points(core) - { - return (p, Source::Patterns); - } - (heuristic_points_pl(core), Source::Heuristic) -} - -/// [`core_breaks_pl`] for a language with bundled patterns (Portuguese, Italian, German): its -/// exception words, the patterns, then the generic heuristic. -fn core_breaks_patterns(pats: &Patterns, core: &[char]) -> (Vec, Source) { - let s: String = core.iter().collect(); - if let Some(p) = pats.exception(&s) { - return (p.to_vec(), Source::Dictionary); - } - if !pats.is_empty() - && let Some(p) = pats.points(core) - { - return (p, Source::Patterns); - } - (heuristic_points(core), Source::Heuristic) -} - -/// Unrestricted break points of an English `word` ([`word_breaks_in`] with [`ProofLang::En`]). -pub fn word_breaks(word: &str) -> (Vec, Source) { - word_breaks_in(word, ProofLang::En) -} - -/// Unrestricted break points of `word` in `lang` (char indices; a hyphen goes before that char), -/// handling case, apostrophes (`don't`, `’s`), surrounding punctuation and compounds (each letter -/// run is hyphenated separately). No break is ever placed next to an apostrophe or non-letter. -pub fn word_breaks_in(word: &str, lang: ProofLang) -> (Vec, Source) { - let chars: Vec = word.chars().collect(); - let mut out = Vec::new(); - let mut src = Source::None; - let mut i = 0; - while i < chars.len() { - if !is_word_char(chars[i]) { - i += 1; - continue; - } - let mut j = i; - while j < chars.len() && is_word_char(chars[j]) { - j += 1; - } - let (mut a, mut b) = (i, j); - while a < b && is_apostrophe(chars[a]) { - a += 1; - } - while b > a && is_apostrophe(chars[b - 1]) { - b -= 1; - } - let core: Vec = chars[a..b].iter().map(|&c| if is_apostrophe(c) { '\'' } else { c.to_lowercase().next().unwrap_or(c) }).collect(); - let (pts, s) = core_breaks(&core, lang); - if src == Source::None { - src = s; - } - out.extend(pts.into_iter().filter(|&p| p > 0 && p < core.len() && core[p - 1] != '\'' && core[p] != '\'').map(|p| a + p)); - i = j; - } - (out, src) -} - -/// Allowed hyphenation points in an English `word` ([`hyphen_points_in`] with [`ProofLang::En`]). -pub fn hyphen_points(word: &str, lim: &Limits) -> Vec { - hyphen_points_in(word, lim, ProofLang::En) -} - -/// Allowed hyphenation points in `word`, a word in `lang`, as char indices (a hyphen goes before -/// that char), with the paragraph limits applied to each letter run. -pub fn hyphen_points_in(word: &str, lim: &Limits, lang: ProofLang) -> Vec { - let chars: Vec = word.chars().collect(); - let (pts, _) = word_breaks_in(word, lang); - if pts.is_empty() { - return pts; - } - let min_before = lim.after_first.max(1); - let min_after = lim.before_last.max(1); - // Counted once, so each point's checks take constant time however long the word: letters and - // uppercase letters before each char, and the letter run (trimmed of apostrophes) of each char. - let (mut letters, mut upper) = (Vec::with_capacity(chars.len() + 1), Vec::with_capacity(chars.len() + 1)); - let (mut l, mut u) = (0usize, 0usize); - for c in &chars { - letters.push(l); - upper.push(u); - if c.is_alphabetic() { - l += 1; - u += usize::from(c.is_uppercase()); - } - } - letters.push(l); - upper.push(u); - let mut run_of: Vec<(usize, usize)> = vec![(0, 0); chars.len()]; - let mut i = 0; - while i < chars.len() { - let mut j = i; - while chars.get(j).is_some_and(|c| is_word_char(*c)) { - j += 1; - } - let (mut a, mut b) = (i, j); - while a < b && chars.get(a).is_some_and(|c| is_apostrophe(*c)) { - a += 1; - } - while b > a && chars.get(b - 1).is_some_and(|c| is_apostrophe(*c)) { - b -= 1; - } - for r in run_of.get_mut(i..j).into_iter().flatten() { - *r = (a, b); - } - i = j.max(i + 1); - } - let count = |v: &[usize], a: usize, b: usize| v.get(b).zip(v.get(a)).map_or(0, |(y, x)| y.saturating_sub(*x)); - pts.into_iter() - .filter(|&p| { - // A point lies between two letters of one run; the char after it gives the run. - let Some(&(a, b)) = run_of.get(p) else { return false }; - let n = count(&letters, a, b); - if n < lim.min_word.max(2) { - return false; - } - if n > 1 && count(&upper, a, b) == n { - return false; - } - if !lim.capitalized && chars.get(a).is_some_and(|c| c.is_uppercase()) { - return false; - } - count(&letters, a, p) >= min_before && count(&letters, p, b) >= min_after - }) - .collect() -} - -/// `word` with `-` at every hyphenation point (tests and diagnostics). -pub fn hyphenate_word(word: &str, lim: &Limits) -> String { - hyphenate_word_in(word, lim, ProofLang::En) -} - -/// `word`, a word in `lang`, with `-` at every hyphenation point (tests and diagnostics). -pub fn hyphenate_word_in(word: &str, lim: &Limits, lang: ProofLang) -> String { - let mut pts = hyphen_points_in(word, lim, lang); - pts.sort_unstable(); - // Walk the points alongside the chars rather than searching them for each char. - let mut next = pts.into_iter().peekable(); - let mut s = String::with_capacity(word.len() + 8); - for (i, ch) in word.chars().enumerate() { - if next.next_if_eq(&i).is_some() { - while next.next_if_eq(&i).is_some() {} - s.push('-'); - } - s.push(ch); - } - s -} - -// ---------- heuristic fallback (words outside the pattern alphabet) ---------- - -/// Letters with a diacritic that are consonants (`ł`, `ż`, `ç`, `ñ`…); any other non-ASCII letter -/// counts as a vowel (`é`, `ø`, `ą`…), and so do letters of other scripts, which keeps the -/// heuristic from breaking them. -const NON_ASCII_CONSONANTS: &str = "łńśćźżçčďđğģķļľĺňñņřŕšşșťţțžßŋðþ"; - -fn is_vowel(c: char) -> bool { - let l = c.to_lowercase().next().unwrap_or(c); - if l.is_ascii() { matches!(l, 'a' | 'e' | 'i' | 'o' | 'u' | 'y') } else { l.is_alphabetic() && !NON_ASCII_CONSONANTS.contains(l) } -} - -fn onset_pair(a: char, b: char) -> bool { - let (a, b) = (a.to_ascii_lowercase(), b.to_ascii_lowercase()); - matches!( - (a, b), - ('c' | 's' | 't' | 'p' | 'w' | 'g', 'h') - | ('b' | 'c' | 'd' | 'f' | 'g' | 'k' | 'p' | 't', 'r') - | ('b' | 'c' | 'f' | 'g' | 'k' | 'p' | 's', 'l') - | ('q', 'u') - | ('s', 't' | 'p' | 'c' | 'k') - ) -} - -/// Syllable heuristic: V-CV ("ty-po"), VC-CV ("hap-pen"), keeping onset clusters ("gra-phy"). -fn heuristic_points(c: &[char]) -> Vec { - let n = c.len(); - if n < 4 || !c.iter().all(|c| c.is_alphabetic()) { - return vec![]; - } - let v: Vec = c.iter().map(|&x| is_vowel(x)).collect(); - // Both parts need a sounded vowel: a trailing silent `e`, `ed` or `es` doesn't count. The part - // after `i` is `c[i..end]`, `end` before that ending; computed once, so each point is O(1). - let end = |i: usize| { - if n - i >= 2 && (c.ends_with(&['e', 'd']) || c.ends_with(&['e', 's'])) { - n - 2 - } else if c.ends_with(&['e']) { - n - 1 - } else { - n - } - }; - // UTF-8 bytes before each char, and the first vowel at or after each char. - let mut bytes = Vec::with_capacity(n + 1); - let mut acc = 0usize; - for ch in c { - bytes.push(acc); - acc += ch.len_utf8(); - } - bytes.push(acc); - let mut next_vowel = vec![n; n + 1]; - for i in (0..n).rev() { - next_vowel[i] = if v[i] { i } else { next_vowel[i + 1] }; - } - let mut out = vec![]; - let mut vowel_before = false; - for i in 1..n { - vowel_before |= v[i - 1]; - let ok = if v[i - 1] && !v[i] { - (i + 1 < n && v[i + 1]) || (i + 2 < n && onset_pair(c[i], c[i + 1]) && v[i + 2]) - } else if !v[i - 1] && !v[i] { - i >= 2 && v[i - 2] && i + 1 < n && v[i + 1] && !onset_pair(c[i - 1], c[i]) && c[i - 1] != c[i] || (c[i - 1] == c[i] && i >= 2 && v[i - 2]) - } else { - false - }; - let e = end(i); - if ok && next_vowel[i] < e && bytes[e] - bytes[i] > 1 && vowel_before { - out.push(i); - } - } - out -} - -/// Polish digraphs, one sound each: never split. -fn is_digraph_pl(a: char, b: char) -> bool { - matches!((a, b), ('c', 'h' | 'z') | ('d', 'z' | 'ź' | 'ż') | ('r' | 's', 'z')) -} - -/// Polish syllable heuristic, for words with letters outside the Polish pattern alphabet (`é`, -/// `ü`…): one break between each pair of syllable nuclei — after a leading sonorant (`kon-ty`, -/// `Pol-ska`) or inside a doubled consonant (`Pöp-pel`), else before the consonant cluster -/// (`sio-stra`). Digraphs (`ch`, `cz`, `dz`, `dź`, `dż`, `rz`, `sz`) stay whole, and an `i` that -/// only softens the consonant before a vowel (`nia`, `sio`) stays with that consonant. -fn heuristic_points_pl(c: &[char]) -> Vec { - let n = c.len(); - if n < 4 || !c.iter().all(|c| c.is_alphabetic()) { - return vec![]; - } - let v: Vec = c.iter().map(|&x| is_vowel(x)).collect(); - let at = |i: usize| v.get(i).copied().unwrap_or(false); - // A syllable nucleus: a vowel, except a softening `i` (after a consonant, before a vowel). - let nucleus: Vec = (0..n).map(|i| at(i) && !(c.get(i) == Some(&'i') && i > 0 && !at(i - 1) && at(i + 1))).collect(); - let nuclei: Vec = (0..n).filter(|&i| nucleus.get(i).copied().unwrap_or(false)).collect(); - let mut out = vec![]; - for pair in nuclei.windows(2) { - let [a, b] = pair else { continue }; - let (start, end) = (a + 1, *b); - if start >= end { - continue; // adjacent vowels (na-u-ka): no break inside a vowel pair here - } - // Consonant units of the cluster: digraphs and a softening `i` join their consonant. - let mut units: Vec = Vec::new(); - let mut i = start; - while i < end { - units.push(i); - i += if i + 1 < end && c.get(i).zip(c.get(i + 1)).is_some_and(|(x, y)| is_digraph_pl(*x, *y)) { 2 } else { 1 }; - if i < end && c.get(i) == Some(&'i') { - i += 1; - } - } - let sonorant = |i: usize| matches!(c.get(i), Some('l' | 'ł' | 'm' | 'n' | 'ń' | 'r' | 'j')); - let first_is_single = units.get(1).is_some_and(|&second| second == start + 1); - // A doubled consonant splits (`lek-ki`, `Pöp-pel`), as does a cluster after a sonorant. - let doubled = c.get(start).is_some_and(|x| c.get(start + 1) == Some(x)); - out.push(if units.len() >= 2 && first_is_single && (sonorant(start) || doubled) { start + 1 } else { start }); - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - - fn h(w: &str) -> String { - hyphenate_word(w, &Limits::default()) - } - - #[test] - fn bundled_data_loads() { - assert!(Dictionary::en_us().len() > 150_000, "{}", Dictionary::en_us().len()); - assert!(Patterns::en_us().len() > 1000, "{}", Patterns::en_us().len()); - } - - #[test] - fn dictionary_words() { - assert_eq!(h("happen"), "hap-pen"); - assert_eq!(h("typography"), "ty-pog-ra-phy"); - assert_eq!(h("composition"), "com-po-si-tion"); - assert_eq!(h("hyphenation"), "hy-phen-a-tion"); - assert_eq!(h("international"), "in-ter-na-tion-al"); - assert_eq!(h("cat"), "cat"); - assert!(!h("changed").contains('-')); - assert_eq!(h("NASA"), "NASA"); - assert_eq!(word_breaks("publication").1, Source::Dictionary); - } - - #[test] - fn case_apostrophes_and_punctuation() { - assert_eq!(h("Typography"), "Ty-pog-ra-phy"); - assert_eq!(h("TYPOGRAPHY"), "TYPOGRAPHY"); - assert_eq!(h("typography,"), "ty-pog-ra-phy,"); - assert_eq!(h("“typography”"), "“ty-pog-ra-phy”"); - assert_eq!(h("typography's"), "ty-pog-ra-phy's"); - assert_eq!(h("typography’s"), "ty-pog-ra-phy’s"); - assert_eq!(h("'typography'"), "'ty-pog-ra-phy'"); - // Compounds: each part separately, never next to the hyphen. - assert_eq!(h("typography-composition"), "ty-pog-ra-phy-com-po-si-tion"); - } - - #[test] - fn inflections_inherit_the_stem() { - assert_eq!(h("ripples"), "rip-ples"); - assert_eq!(h("gradients"), "gra-di-ents"); - assert_eq!(h("restrained"), "re-strained"); - } - - #[test] - fn patterns_cover_unknown_words() { - // Not in the word list: the trained patterns decide. - let (pts, src) = word_breaks("hyperlocalization"); - assert_eq!(src, Source::Patterns); - assert!(pts.len() >= 4, "{}", h("hyperlocalization")); - let (_, src) = word_breaks("zzyzx"); - assert_ne!(src, Source::Dictionary); - } - - #[test] - fn limits_are_respected() { - let lim = Limits { min_word: 5, after_first: 3, before_last: 3, capitalized: false }; - for w in ["typography", "hyphenation", "justification", "Paragraph", "beautiful"] { - for p in hyphen_points(w, &lim) { - assert!(p >= 3 && p <= w.chars().count() - 3, "{w} {p}"); - } - } - assert!(hyphen_points("Paragraph", &lim).is_empty()); - let lim = Limits { min_word: 12, ..Limits::default() }; - assert!(hyphen_points("typography", &lim).is_empty()); - } - - /// The CPU time `f` takes on this thread, in seconds. On Linux it is the scheduler's count of the - /// time the thread actually ran, so the time it waits while other work on a busy machine has the - /// CPU is left out; elsewhere, wall-clock time. - fn cpu_cost(f: &mut impl FnMut()) -> f64 { - let ran = || { - let s = std::fs::read_to_string("/proc/thread-self/schedstat").ok()?; - s.split_whitespace().next()?.parse::().ok() - }; - let (t, before) = (std::time::Instant::now(), ran()); - f(); - match (before, ran()) { - (Some(a), Some(b)) => b.saturating_sub(a) as f64 * 1e-9, - _ => t.elapsed().as_secs_f64(), - } - } - - /// How much more `big` costs than `small`, for `big` an input `scale` times the size of `small`'s: - /// (small, big, big / small), ~`scale` when linear and ~`scale`² when quadratic. Each is the - /// cheapest of several runs after a warm-up, the runs alternating so a busy spell slows both sizes - /// alike; `small` is timed `scale` times over per run, so both timings are long against the clock's - /// resolution. - fn cost_ratio(scale: usize, mut small: impl FnMut(), mut big: impl FnMut()) -> (f64, f64, f64) { - small(); - big(); - let mut small_scaled = || (0..scale).for_each(|_| small()); - let (mut t1, mut tk) = (f64::INFINITY, f64::INFINITY); - for _ in 0..5 { - t1 = t1.min(cpu_cost(&mut small_scaled) / scale as f64); - tk = tk.min(cpu_cost(&mut big)); - } - (t1, tk, tk / t1.max(1e-9)) - } - - /// `hyphen_points`' limit checks as first written (a rescan of the letter run per point): the - /// reference for the linear version. - fn hyphen_points_ref(word: &str, lim: &Limits) -> Vec { - let chars: Vec = word.chars().collect(); - let (pts, _) = word_breaks(word); - let min_before = lim.after_first.max(1); - let min_after = lim.before_last.max(1); - let run_of = |p: usize| { - let mut a = p; - while a > 0 && is_word_char(chars[a - 1]) { - a -= 1; - } - let mut b = p; - while b < chars.len() && is_word_char(chars[b]) { - b += 1; - } - while a < b && is_apostrophe(chars[a]) { - a += 1; - } - while b > a && is_apostrophe(chars[b - 1]) { - b -= 1; - } - (a, b) - }; - pts.into_iter() - .filter(|&p| { - let (a, b) = run_of(p); - let run = &chars[a..b]; - let letters = run.iter().filter(|c| c.is_alphabetic()).count(); - if letters < lim.min_word.max(2) { - return false; - } - if letters > 1 && run.iter().filter(|c| c.is_alphabetic()).all(|c| c.is_uppercase()) { - return false; - } - if !lim.capitalized && run[0].is_uppercase() { - return false; - } - let before = chars[a..p].iter().filter(|c| c.is_alphabetic()).count(); - let after = chars[p..b].iter().filter(|c| c.is_alphabetic()).count(); - before >= min_before && after >= min_after - }) - .collect() - } - - /// `heuristic_points` as first written (the right part rebuilt per point): the reference. - fn heuristic_ref(c: &[char]) -> Vec { - let n = c.len(); - if n < 4 || !c.iter().all(|c| c.is_alphabetic()) { - return vec![]; - } - let v: Vec = c.iter().map(|&x| is_vowel(x)).collect(); - let mut out = vec![]; - for i in 1..n { - let ok = if v[i - 1] && !v[i] { - (i + 1 < n && v[i + 1]) || (i + 2 < n && onset_pair(c[i], c[i + 1]) && v[i + 2]) - } else if !v[i - 1] && !v[i] { - i >= 2 && v[i - 2] && i + 1 < n && v[i + 1] && !onset_pair(c[i - 1], c[i]) && c[i - 1] != c[i] - || (c[i - 1] == c[i] && i >= 2 && v[i - 2]) - } else { - false - }; - let right: String = c[i..].iter().collect(); - let core = right.strip_suffix("ed").or_else(|| right.strip_suffix("es")).or_else(|| right.strip_suffix('e')).unwrap_or(&right); - if ok && core.chars().any(is_vowel) && core.len() > 1 && v[..i].iter().any(|x| *x) { - out.push(i); - } - } - out - } - - #[test] - fn linear_limits_and_heuristic_match_the_reference() { - let long = "hyphenation".repeat(40); - let words = [ - "", - "x", - "''", - "a'b'c", - "cat", - "happen", - "hyphenation", - "Typography's", - "TYPOGRAPHY", - "Typography", - "typography’s", - "'typography'", - "“typography”,", - "typography-composition", - "don't", - "rock'n'roll", - "naïveté", - "crème-brûlée", - "Ünïcödé", - "ÉTUDE", - "strengths", - "ee", - "bed", - "hopped", - "rhythmed", - "aeiouy", - "xxyyzz", - "bookkeeper", - "ʼtwas", - "mid-1990s", - "twenty-one", - "école", - "Ålesund", - "smørrebrød", - "hopes", - "hoped", - "taed", - "teeed", - &long, - ]; - for w in words { - for min_word in [2, 5, 7] { - for (after_first, before_last) in [(1, 1), (2, 2), (3, 2), (2, 4)] { - for capitalized in [true, false] { - let lim = Limits { min_word, after_first, before_last, capitalized }; - assert_eq!(hyphen_points(w, &lim), hyphen_points_ref(w, &lim), "{w:?} {lim:?}"); - } - } - } - let c: Vec = w.to_lowercase().chars().collect(); - assert_eq!(heuristic_points(&c), heuristic_ref(&c), "{w:?}"); - } - } - - #[test] - fn hyphenating_a_long_word_is_linear() { - // A word of n letters against one of 8n: linear ~8x the cost, the old rescans ~64x. One in - // the pattern alphabet, one outside it (the syllable heuristic). - let pattern = |k: usize| "hyphenation".repeat(k); - let heuristic = |k: usize| "smørrebrød".repeat(k); - assert_eq!(word_breaks(&heuristic(2)).1, Source::Heuristic); - // Each timing repeats the call so the shorter one is milliseconds, well above timer noise. - let ratios: Vec<(String, f64)> = [(pattern as fn(usize) -> String, 20), (heuristic, 40)] - .into_iter() - .map(|(word, repeat)| { - let (small, big) = (word(400), word(8 * 400)); - let time = |w: &str| (0..repeat).for_each(|_| drop(hyphen_points(w, &Limits::default()))); - let (t1, t8, ratio) = cost_ratio(8, || time(&small), || time(&big)); - let what = format!("{}…: t(n) {t1:.4} s, t(8n) {t8:.4} s, ratio {ratio:.1}", word(1).chars().take(10).collect::()); - eprintln!("{what}"); - (what, ratio) - }) - .collect(); - for (what, ratio) in ratios { - assert!(ratio < 24.0, "8x the letters, not linear: {what}"); - } - } - - #[test] - fn russian_hyphenation_and_hyphenated_output_are_linear() { - // A Russian word breaks at every syllable: each break used to be checked against all the - // earlier ones (#3657), and the hyphenated output searched every break for each char. - let word = |k: usize| "мама".repeat(k); - assert_eq!(word_breaks_in(&word(2), ProofLang::Ru).0, [2, 4, 6]); - assert_eq!(hyphenate_word_in(&word(2), &Limits::default(), ProofLang::Ru), "ма-ма-ма-ма"); - // The breaks' rescan is a cheap one per break: it takes longer words to stand out. - for (what, k, repeat, f) in [ - ("points", 1000, 3, (|w: &str| drop(hyphen_points_in(w, &Limits::default(), ProofLang::Ru))) as fn(&str)), - ("hyphenated", 400, 20, |w: &str| drop(hyphenate_word_in(w, &Limits::default(), ProofLang::Ru))), - ] { - let (small, big) = (word(k), word(8 * k)); - let time = |w: &str| (0..repeat).for_each(|_| f(w)); - let (t1, t8, ratio) = cost_ratio(8, || time(&small), || time(&big)); - eprintln!("{what}: t(n) {t1:.4} s, t(8n) {t8:.4} s, ratio {ratio:.1}"); - assert!(ratio < 24.0, "8x the letters, not linear: {what} {ratio:.1}"); - } - } - - #[test] - fn heuristic_fallback() { - let c: Vec = "happen".chars().collect(); - assert_eq!(heuristic_points(&c), vec![3]); - } - - #[test] - fn heuristic_knows_consonants_with_diacritics() { - // `ł`, `ż`, `ç` are consonants (every non-ASCII letter used to count as a vowel). - assert!(!is_vowel('ł') && !is_vowel('Ż') && !is_vowel('ç') && is_vowel('é') && is_vowel('ą') && is_vowel('ø')); - let c: Vec = "façade".chars().collect(); - assert_eq!(heuristic_points(&c), vec![2]); - } - - fn pl(w: &str) -> String { - hyphenate_word_in(w, &Limits::default(), ProofLang::Pl) - } - - #[test] - fn polish_patterns_load() { - let p = Patterns::pl(); - assert!(p.len() > 4000, "{}", p.len()); - assert_eq!(p.exception_count(), 20); - assert_eq!(p.exception("nigdy"), Some(&[2][..])); - } - - fn pt(w: &str) -> String { - hyphenate_word_in(w, &Limits::default(), ProofLang::Pt) - } - - fn it(w: &str) -> String { - hyphenate_word_in(w, &Limits::default(), ProofLang::It) - } - - #[test] - fn italian_patterns_load() { - let p = Patterns::it(); - assert!(p.len() > 300, "{}", p.len()); - // The file has no exception words. - assert_eq!(p.exception_count(), 0); - } - - #[test] - fn italian_words_hyphenate_by_italian_rules() { - for (w, want) in [ - ("famiglia", "fa-mi-glia"), - ("finestra", "fi-ne-stra"), - ("bicicletta", "bi-ci-clet-ta"), - ("passeggiata", "pas-seg-gia-ta"), - ("caffè", "caf-fè"), - ("tavolo", "ta-vo-lo"), - ("bambino", "bam-bi-no"), - ("scuola", "scuo-la"), - ("notte", "not-te"), - ("giorno", "gior-no"), - ("acqua", "ac-qua"), - // Under five letters (`Limits::min_word`): no break. - ("casa", "casa"), - ("pane", "pane"), - ("libro", "li-bro"), - ("re", "re"), - ("più", "più"), - ] { - assert_eq!(it(w), want, "{w}"); - } - assert_eq!(word_breaks_in("famiglia", ProofLang::It).1, Source::Patterns); - } - - #[test] - fn portuguese_patterns_load() { - let p = Patterns::pt(); - assert!(p.len() > 300, "{}", p.len()); - // `hard-ware`, `soft-ware` — and the file's last pattern line, a bare `1-`, which the - // reader counts as a (never-matching) exception because it contains a hyphen. - assert_eq!(p.exception_count(), 3); - assert_eq!(p.exception("hardware"), Some(&[4][..])); - assert_eq!(p.exception("software"), Some(&[4][..])); - } - - #[test] - fn portuguese_words_hyphenate_by_portuguese_rules() { - for (w, want) in [ - ("problema", "pro-ble-ma"), - ("coração", "co-ra-ção"), - ("paralelepípedo", "pa-ra-le-le-pí-pe-do"), - ("computador", "com-pu-ta-dor"), - ("escrever", "es-cre-ver"), - ("saudade", "sau-da-de"), - ("extraordinário", "ex-tra-or-di-ná-rio"), - ("automático", "au-to-má-ti-co"), - // One syllable: no break. - ("flor", "flor"), - ("bem", "bem"), - ] { - assert_eq!(pt(w), want, "{w}"); - } - } - - #[test] - fn polish_words_hyphenate_by_polish_rules() { - for (w, want) in [ - ("konstytucja", "kon-sty-tu-cja"), - ("konstytucyjnego", "kon-sty-tu-cyj-ne-go"), - ("Rzeczpospolita", "Rzecz-po-spo-li-ta"), - ("przedsiębiorstwo", "przed-się-bior-stwo"), - ("pięćdziesięciu", "pięć-dzie-się-ciu"), - ("dziewczyna", "dziew-czy-na"), - ("książka", "książ-ka"), - ("Warszawa", "War-sza-wa"), - ("szczęście", "szczę-ście"), - ("rzeczywistość", "rze-czy-wi-stość"), - ("najpiękniejszy", "naj-pięk-niej-szy"), - ("wyobraźnia", "wy-obraź-nia"), - ("gżegżółka", "gżeg-żół-ka"), - // One syllable, or a single letter left over: no break. - ("chrząszcz", "chrząszcz"), - ("źdźbło", "źdźbło"), - ("obiad", "obiad"), - // The pattern file's exception words. - ("nigdy", "ni-gdy"), - ("przynajmniej", "przy-naj-mniej"), - ("skądinąd", "skąd-inąd"), - // Compounds: each part, never next to the hyphen. - ("biało-czerwony", "bia-ło-czer-wo-ny"), - ] { - assert_eq!(pl(w), want, "{w}"); - } - assert_eq!(word_breaks_in("nigdy", ProofLang::Pl).1, Source::Dictionary); - assert_eq!(word_breaks_in("konstytucja", ProofLang::Pl).1, Source::Patterns); - } - - #[test] - fn polish_heuristic_for_foreign_letters() { - // Letters outside the Polish alphabet: the Polish syllable heuristic, digraphs kept whole. - assert_eq!(word_breaks_in("Pöppelmann", ProofLang::Pl).1, Source::Heuristic); - assert_eq!(pl("Pöppelmann"), "Pöp-pel-mann"); - assert_eq!(pl("Müllerowi"), "Mül-le-ro-wi"); - let c: Vec = "kawiarnié".chars().collect(); - assert_eq!(heuristic_points_pl(&c), vec![2, 6], "ka-wiar-nié: `wi` and `ni` soften, `rn` splits after the sonorant"); - let c: Vec = "szczérze".chars().collect(); - assert_eq!(heuristic_points_pl(&c), vec![5], "szczé-rze: `rz` stays whole"); - } - - #[test] - fn languages_without_patterns_keep_the_english_ones() { - for w in ["hyphenation", "typography", "internationalization"] { - for lang in [ProofLang::Other, ProofLang::Cs, ProofLang::Id, ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy] { - assert_eq!(hyphen_points_in(w, &Limits::default(), lang), hyphen_points(w, &Limits::default()), "{w}"); - } - } - } - - #[test] - fn german_patterns_load() { - let p = Patterns::de(); - assert_eq!(p.len(), 36_709); - // The file has no exception words, and its `\message{…}` line adds none. - assert_eq!(p.exception_count(), 0); - } - - #[test] - fn german_words_follow_the_german_patterns() { - let de = |w: &str| hyphenate_word_in(w, &Limits::default(), ProofLang::De); - assert_eq!(de("Silbentrennung"), "Sil-ben-tren-nung"); - assert_eq!(de("unterliegende"), "un-ter-lie-gen-de"); - assert_eq!(de("öffentlich-rechtlicher"), "öf-fent-lich-recht-li-cher"); - assert_eq!(de("Begründung"), "Be-grün-dung"); - assert_eq!(de("Zucker"), "Zu-cker"); - // Swiss spelling: `ss` splits like any double consonant. - assert_eq!(de("Strasse"), "Stras-se"); - assert_eq!(word_breaks_in("Unterlagen", ProofLang::De).1, Source::Patterns); - } - - #[test] - fn bulgarian_words_follow_the_bulgarian_rules() { - let bg = |w: &str| hyphenate_word_in(w, &Limits::default(), ProofLang::Bg); - assert_eq!(bg("сричкопренасяне"), "срич-ко-пре-на-ся-не"); - assert_eq!(bg("Таблица"), "Та-бли-ца"); - // A single letter is never left on a line (the paragraph limits): България, not Бъл-га-ри-я. - assert_eq!(bg("България"), "Бъл-га-рия"); - assert_eq!(bg("„шофьор“,"), "„шо-фьор“,"); - assert_eq!(word_breaks_in("таблица", ProofLang::Bg).1, Source::Rules); - // Latin words in a Bulgarian run are hyphenated as English. - for w in ["publication", "WordCraft"] { - assert_eq!(hyphen_points_in(w, &Limits::default(), ProofLang::Bg), hyphen_points(w, &Limits::default()), "{w}"); - } - } -} diff --git a/crates/proof/src/id/grammar.rs b/crates/proof/src/id/grammar.rs deleted file mode 100644 index 72ca10c91..000000000 --- a/crates/proof/src/id/grammar.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! Generic repeated-word and spacing checks with Indonesian messages. No Indonesian-specific -//! grammar rules are inferred. A word written twice with a space is flagged; since standard -//! Indonesian spelling (PUEBI) writes reduplication with a hyphen (`anak-anak`), the hyphenated -//! form is offered first, then the single word. - -use crate::{Issue, IssueKind, ProofLang, words}; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang: ProofLang::Id } -} - -pub fn check(text: &str) -> Vec { - let mut issues = Vec::new(); - repeated_words(text, &words(text), &mut issues); - spaces(text, &mut issues); - issues.sort_by_key(|i| (i.start, i.end)); - issues -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() && gap.chars().all(char::is_whitespace) && w0.chars().all(char::is_alphabetic) && w0.to_lowercase() == w1.to_lowercase() { - v.push(issue(*a0, *b1, format!("Kata berulang: “{w1}”"), vec![format!("{w0}-{w1}"), w0.to_string()])); - } - } -} - -fn spaces(text: &str, v: &mut Vec) { - for (i, c) in text.char_indices() { - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - // A ratio/score keeps the spaces in digit-space-colon-space-digit. - let ratio = p == ':' - && text.get(..i).and_then(|before| before.chars().next_back()).is_some_and(|c| c.is_ascii_digit()) - && after == Some(' ') - && it.next().is_some_and(|c| c.is_ascii_digit()); - let flag = !ratio - && match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '”' | '’' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Spasi sebelum tanda baca".into(), vec![p.to_string()])); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Spasi ganda".into(), vec![" ".into()])); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn repeated_words_and_spaces_have_indonesian_messages() { - let text = "Anak anak , sekolah."; - let issues = check(text); - assert_eq!(issues.len(), 3, "{issues:?}"); - assert_eq!(issues[0].message, "Kata berulang: “anak”"); - assert_eq!(issues[0].suggestions, ["Anak-anak", "Anak"]); - assert!(issues.iter().any(|i| i.message == "Spasi sebelum tanda baca" && &text[i.start..i.end] == " ,")); - assert!(issues.iter().any(|i| i.message == "Spasi ganda")); - assert!(issues.iter().all(|i| i.lang == ProofLang::Id && i.kind == IssueKind::Grammar)); - for text in ["anak-anak bermain", "rumah. Sekolah", "Dr. Budi", "Anak, anak!", "Senyum :) Angka .5 dan titik ...", "kupu-kupu"] { - assert!(check(text).is_empty(), "{text}: {:?}", check(text)); - } - } - - #[test] - fn ratios_and_scores_keep_their_spaces() { - for text in ["3 : 1", "Skor 2 : 1.", "Rasio 1 : 100."] { - assert!(check(text).is_empty(), "{text}: {:?}", check(text)); - } - for text in ["Kata : lain", "3 : kata", "3 :"] { - assert!(check(text).iter().any(|i| i.message == "Spasi sebelum tanda baca"), "{text}"); - } - } - - proptest::proptest! { - #[test] - fn never_panics_and_ranges_are_valid(s in "\\PC{0,80}") { - for issue in check(&s) { - proptest::prop_assert!(issue.start <= issue.end && issue.end <= s.len()); - proptest::prop_assert!(s.is_char_boundary(issue.start) && s.is_char_boundary(issue.end)); - } - } - } -} diff --git a/crates/proof/src/id/mod.rs b/crates/proof/src/id/mod.rs deleted file mode 100644 index 04248d638..000000000 --- a/crates/proof/src/id/mod.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! Indonesian proofing using the installed Hunspell `id_ID` dictionary (never bundled; Debian and -//! Ubuntu ship it as `hunspell-id`, LibreOffice as its `dict-id` extension). Suggestions use the -//! dictionary's own MAP/REP/TRY/KEY tables. Grammar is limited to generic repeated-word and -//! spacing checks with Indonesian messages; hyphenation keeps the English fallback (no Indonesian -//! patterns are bundled). - -pub mod grammar; -mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// File names of an Indonesian Hunspell dictionary (`in` is the old ISO 639 code, still used by -/// Java locales). -const NAMES: &[&str] = &["id_ID", "id-ID", "id", "in_ID"]; - -/// The Indonesian dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Indonesian"); - -/// The Indonesian dictionary: the installed Hunspell `id_ID` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available (Indonesian spelling is off -/// then; see [`has_dictionary`]). -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Use this Indonesian dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to -/// UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is an Indonesian dictionary available (spelling is checked)? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Clitics PUEBI joins with a hyphen to a capitalized word, an abbreviation or a number -/// (`se-Indonesia`, `KTP-nya`, `Jakarta-lah`); they aren't dictionary words on their own. -const CLITICS: &[&str] = &["se", "nya", "ku", "mu", "lah", "kah", "pun"]; - -/// Is `word` spelled correctly in Indonesian? Numbers, single letters, short words in capitals -/// (acronyms: `DPR`, `KTP`), words with digits and words in other scripts are accepted, and so -/// is every word when no Indonesian dictionary is installed. A hyphenated word is correct when -/// the dictionary has it whole (`kupu-kupu`, `sayur-mayur`) or has each part (`anak-anak`, -/// `rumah-rumah`); a hyphenated clitic (`KTP-nya`, `se-Indonesia`) counts as known. -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool { - let Some(w) = crate::spelling::word_to_check(d, word, opts) else { - return true; - }; - if w.contains('-') { - return known(d, w) - || w.split('-').all(|p| { - p.chars().count() <= 1 - || CLITICS.iter().any(|c| p.eq_ignore_ascii_case(c)) - || (opts.ignore_uppercase && p.chars().all(|c| !c.is_lowercase()) && p.chars().count() <= 6) - || known(d, p) - }); - } - known(d, w) -} - -/// Is one word in the dictionary or the user dictionary? -fn known(d: &AffixDict, w: &str) -> bool { - d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn dictionary_lookup_accepts_indonesian_file_names() { - let dir = std::env::temp_dir().join(format!("wordcraft-id-aliases-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - for name in ["id_ID", "id-ID", "id", "in_ID"] { - std::fs::write(dir.join(format!("{name}.aff")), test_dict::AFF).unwrap(); - std::fs::write(dir.join(format!("{name}.dic")), test_dict::DIC).unwrap(); - let (aff, dic) = crate::hunspell::find_in(&dir, NAMES).unwrap(); - let d = crate::hunspell::load(&aff, &dic).unwrap(); - assert!(is_correct_with(&d, "rumah")); - std::fs::remove_file(aff).unwrap(); - std::fs::remove_file(dic).unwrap(); - } - std::fs::remove_dir(dir).unwrap(); - } - - #[test] - fn indonesian_words_affixes_case_and_hyphens() { - test_dict::install(); - let d = dictionary(); - assert!(has_dictionary()); - assert_eq!(d.len(), 20); - assert!(d.rule_count() > 0); - for w in [ - "rumah", - "makan", - "makanan", - "dimakan", - "membaca", - "bacaan", - "dibaca", - "Jakarta", - "Rumah", - "SEKOLAH", - "anak-anak", - "rumah-rumah", - "kupu-kupu", - "KTP-nya", - "se-Indonesia", - "Jakarta-lah", - "DPR", - ] { - assert!(is_correct(w), "{w}"); - } - for w in ["rumha", "makanann", "skolah", "jakarta", "kupu", "anak-anka", "mAkan", "bukunya-xyz"] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn forbidden_words_are_rejected_in_every_case() { - test_dict::install(); - for w in ["zqterlarang", "Zqterlarang", "ZQTERLARANG"] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn missing_dictionary_and_ignored_inputs_are_quiet() { - let empty = AffixDict::default(); - assert!(is_correct_with(&empty, "rumha")); - assert!(install_dictionary("FLAG long\n", "1\nrumah\n").is_err()); - let d = AffixDict::from_hunspell(test_dict::AFF, test_dict::DIC, &[]).unwrap(); - for w in ["", "a", "2026", "RI", "ke-2", "日本語", "-", "--"] { - assert!(is_correct_with(&d, w), "{w}"); - } - } - - #[test] - fn user_dictionary_applies() { - test_dict::install(); - assert!(!is_correct("zqkatasendiri")); - crate::add_word("Zqkatasendiri"); - assert!(is_correct("zqkatasendiri")); - } - - #[test] - fn indonesian_dispatch_messages_options_and_mixed_spans() { - test_dict::install(); - assert!(crate::has_dictionary(Some("id-ID"))); - assert!(crate::is_correct_for("rumah", Some("ind"))); - let text = "Rumah sekolha"; - let issues = crate::check_text(text, &[(0..text.len(), crate::ProofLang::Id)]); - assert_eq!(issues.len(), 1, "{issues:?}"); - assert_eq!(&text[issues[0].start..issues[0].end], "sekolha"); - assert_eq!(issues[0].message, "Kemungkinan salah eja"); - assert_eq!(issues[0].lang, crate::ProofLang::Id); - assert!(crate::suggest_in("sekolha", 8, crate::ProofLang::Id).contains(&"sekolah".into())); - let text = "Rumah this is is wrong"; - let issues = crate::check_text(text, &[(0..5, crate::ProofLang::Id), (6..text.len(), crate::ProofLang::En)]); - assert!(issues.iter().any(|i| i.lang == crate::ProofLang::En && i.message.starts_with("Repeated word"))); - assert!(issues.iter().all(|i| i.start >= 6)); - let opts = crate::Options { ignore_uppercase: false, ..Default::default() }; - assert!(!crate::is_correct_in_with("ZQTERLARANG", crate::ProofLang::Id, &opts)); - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Indonesian checks - /// those words against the dictionary instead of accepting them. - #[test] - fn proofing_options_reach_the_indonesian_check() { - test_dict::install(); - let lang = crate::ProofLang::Id; - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("RUMHA", lang, &defaults), "short capitals are acronyms by default"); - assert!(!crate::is_correct_in_with("RUMHA", lang, &check_caps)); - assert!(crate::is_correct_in_with("RUMAH", lang, &check_caps)); - assert!(crate::is_correct_in_with("RUMHA-nya", lang, &defaults)); - assert!(!crate::is_correct_in_with("RUMHA-nya", lang, &check_caps), "a part in capitals is checked too"); - assert!(crate::is_correct_in_with("rumha2", lang, &defaults)); - assert!(!crate::is_correct_in_with("rumha2", lang, &check_digits)); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("RUMHA"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/id/suggest.rs b/crates/proof/src/id/suggest.rs deleted file mode 100644 index b42efde98..000000000 --- a/crates/proof/src/id/suggest.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Indonesian suggestions: the shared ranking ([`crate::affix_suggest`]: case, MAP, REP, missing -//! spaces, KEY, TRY edits and a stem scan); every table comes from the installed `id_ID` -//! dictionary, so nothing Indonesian is hard-coded here. - -use crate::affix::AffixDict; - -/// Spelling suggestions for an Indonesian word, best first, at most `max`, in the word's case. -pub fn suggest(word: &str, max: usize) -> Vec { - suggest_in(&super::dictionary(), word, max) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize) -> Vec { - crate::affix_suggest::suggest_in(d, word, max, &crate::affix_suggest::Tuning::DEFAULT) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn indonesian_suggestions_use_runtime_tables_and_case() { - crate::id::test_dict::install(); - for (word, want) in [ - ("rumha", "rumah"), - ("sekolha", "sekolah"), - ("Sekola", "Sekolah"), - ("jakarta", "Jakarta"), - ("makanna", "makanan"), - ("djalan", "jalan"), - ("dsn", "dan"), - ("sayadan", "saya dan"), - ("membcaa", "membaca"), - ] { - assert!(suggest(word, 8).contains(&want.into()), "{word}: {:?}", suggest(word, 8)); - } - } - - #[test] - fn suggestions_stay_bounded_and_skip_forbidden_words() { - crate::id::test_dict::install(); - for w in ["", "dua kata", &"a".repeat(41)] { - assert!(suggest(w, 4).is_empty()); - } - assert!(suggest("rumha", 0).is_empty()); - assert!(suggest_in(&AffixDict::default(), "rumha", 4).is_empty()); - assert!(suggest("rumah", 4).len() <= 4); - assert!(!suggest("rumah", 4).contains(&"rumah".into())); - assert!(!suggest("zqterlarag", 8).contains(&"zqterlarang".into())); - } - - #[test] - fn nosuggest_stems_and_forms_are_never_offered() { - crate::affix::tests::assert_nosuggest_is_honoured(suggest_in); - } -} diff --git a/crates/proof/src/id/test_dict.rs b/crates/proof/src/id/test_dict.rs deleted file mode 100644 index 23c8b97ef..000000000 --- a/crates/proof/src/id/test_dict.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Hand-written synthetic Indonesian test data. No words, rules or suggestion tables were copied -//! from a system dictionary. This is a small fixture, not a redistributable Indonesian dictionary. - -pub const AFF: &str = "SET UTF-8 -FLAG UTF-8 -FORBIDDENWORD F -TRY aniekrumgtslhbdopy -KEY qw|as|zx -MAP 1 -MAP eé -REP 2 -REP oe u -REP dj j -SFX A Y 1 # synthetic -an -SFX A 0 an . -PFX D Y 1 -PFX D 0 di . -PFX M Y 1 -PFX M 0 mem b -"; - -pub const DIC: &str = "20 -rumah -makan/AD -baca/ADM -sekolah -anak -buku -kupu-kupu -Jakarta -Indonesia -saya -kamu -dan -yang -ini -itu -pergi -jalan/A -besar -kecil -zqterlarang/F -"; - -pub fn install() { - if let Err(e) = super::install_dictionary(AFF, DIC) { - panic!("test dictionary: {e}"); - } -} diff --git a/crates/proof/src/it/grammar.rs b/crates/proof/src/it/grammar.rs deleted file mode 100644 index 463029369..000000000 --- a/crates/proof/src/it/grammar.rs +++ /dev/null @@ -1,472 +0,0 @@ -//! Italian grammar and punctuation checks: cheap, rule-based and tuned to stay quiet when -//! unsure (a missed mistake costs less than a wrong mark on correct text). Messages are in -//! Italian. -//! -//! - **Repeated word** (`la la`), except words Italian doubles on purpose (`piano piano`, -//! `sì sì`, interjections). -//! - **Space before punctuation** (`bene ,`) and **double spaces**; emoticons (` :)`) and -//! ellipses are left alone. -//! - **Capital letter at the start of a sentence**, knowing Italian abbreviations (`ecc.`, -//! `dott.`, `prof.`, `sig.`), initials, ordinal numbers (`1. posto`) and ellipses. -//! - **Doubled punctuation** (`,,`, `..`, `;;`). -//! - **`po'`**: `un pò` and `un po` → `un po'` (a truncation of `poco`: apostrophe, no accent). -//! - **`qual è`**: `qual'è`, `qual'era` → `qual è`, `qual era` (a truncation, never elided). -//! - **`È`**: `E'` written for the verb → `È`. -//! - **`un` / `un'`**: the apostrophe only before a feminine word. `un'altro`, `un'anno` → -//! `un altro`, `un anno` (a masculine word in `-o`; `auto` and `eco` are feminine), and -//! `un altra`, `un amica` → `un'altra`, `un'amica` (a word in `-a`, except the masculine and -//! common-gender ones: `-ista`, `-ita`, `-ota`, `-cida`, `-ma`, `-eta`, `atleta`, `acrobata`…). -//! The same for `nessun`, `ciascun`, `qualcun` and `alcun`. -//! -//! No comma rule and no English rule (such as `a`/`an`) applies to Italian text. - -use crate::{Issue, IssueKind, ProofLang, words}; - -/// Words Italian repeats on purpose: emphatic doubling, interjections, onomatopoeia. -const ALLOWED_REPEATS: &[&str] = &[ - "sì", "no", "piano", "bene", "male", "ecco", "oh", "ah", "eh", "mh", "bah", "ehi", "urrà", "evviva", "ciao", "addio", "adagio", "forte", "lento", - "chiaro", "pian", "poco", "quasi", "presto", "subito", "zitto", "via", "così", "tanto", "molto", "pure", "dai", "su", "avanti", "basta", "aiuto", -]; - -/// Indefinite articles (and the like) that elide to `un'` before a feminine word only. -const UN_FORMS: &[&str] = &["un", "nessun", "ciascun", "qualcun", "alcun"]; - -/// Feminine words in `-o` that start with a vowel (`un'auto`, `un'eco`). -const FEMININE_IN_O: &[&str] = &["auto", "autoradio", "eco", "ecografo"]; - -/// Endings of masculine or common-gender words in `-a` (`un artista`, `un idiota`, `un enigma`). -const MASCULINE_A_ENDINGS: &[&str] = &["ista", "ita", "ota", "cida", "ma", "eta", "nauta", "iatra", "arca"]; - -/// Words in `-a` after `un` that aren't feminine nouns: masculine or common-gender ones that fit -/// no ending above, and verbs and pronouns (`un ha`, `un una`). -const NOT_FEMININE_IN_A: &[&str] = &["acrobata", "autodidatta", "alfa", "omega", "ha", "una"]; - -/// Abbreviations written with a final period after which a sentence goes on in lower case -/// (lower case, without the period). -const ABBREVIATIONS: &[&str] = &[ - "ecc", - "es", - "dott", - "prof", - "profssa", - "ing", - "avv", - "sig", - "sigg", - "sigra", - "sigr", - "geom", - "rag", - "dottssa", - "avvoc", - "pag", - "pagg", - "tel", - "art", - "artt", - "fig", - "figg", - "vol", - "voll", - "ed", - "sez", - "cap", - "capp", - "par", - "n", - "num", - "vs", - "cfr", - "cfrt", - "op", - "cit", - "ibid", - "sez", - "all", - "tav", - "pag", - "ditta", - "soc", - "spa", - "srl", - "on", - "sen", - "spett", - "eg", - "ca", - "v", - "vv", - "sec", - "min", - "max", - "lun", - "mar", - "mer", - "gio", - "ven", - "sab", - "dom", - "gen", - "feb", - "mar", - "apr", - "mag", - "giu", - "lug", - "ago", - "set", - "ott", - "nov", - "dic", - "genn", - "febb", - "sett", - "dottor", - "professor", - "cav", - "commend", - "mons", - "fr", - "suor", - "s", - "ss", - "b", - "beato", -]; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang: ProofLang::It } -} - -/// Is `prev`, before a period, an abbreviation, an initial, a number or a Roman numeral (after -/// which a lower-case word doesn't start a sentence)? -fn abbreviation_or_number(prev: &str) -> bool { - prev.chars().count() <= 1 - || prev.chars().any(|c| c.is_ascii_digit()) - || (prev.chars().all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))) - || ABBREVIATIONS.contains(&prev.to_lowercase().as_str()) -} - -/// Does the gap between two words end a sentence: a `.`, `!` or `?` (not an ellipsis, not after -/// an abbreviation), then space, with nothing but quotes and brackets around? -fn ends_sentence(gap: &str, prev: &str) -> bool { - let t = gap.trim_start_matches(['"', '”', '’', '\'', ')', '»', ']', '\u{1}']); - let Some(end) = t.chars().next() else { return false }; - if !matches!(end, '.' | '!' | '?') || t.starts_with("..") { - return false; - } - if end == '.' && abbreviation_or_number(prev) { - return false; - } - let rest = t.trim_start_matches(['.', '!', '?']).trim_start_matches(['"', '”', '’', '\'', ')', '»', ']']); - rest.starts_with(char::is_whitespace) && rest.trim_start().chars().all(|c| matches!(c, '"' | '“' | '«' | '(' | '[' | '\'' | '—')) -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() - && gap.chars().all(char::is_whitespace) - && w0.chars().all(char::is_alphabetic) - && w0.to_lowercase() == w1.to_lowercase() - && !ALLOWED_REPEATS.contains(&w0.to_lowercase().as_str()) - { - v.push(issue(*a0, *b1, format!("Parola ripetuta: \"{w1}\""), vec![w0.to_string()])); - } - } -} - -fn spaces(text: &str, v: &mut Vec) { - for (i, c) in text.char_indices() { - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - let flag = match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '”' | '’' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Spazio prima della punteggiatura".into(), vec![p.to_string()])); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Spazio doppio".into(), vec![" ".into()])); - } -} - -fn sentence_capitals(text: &str, ws: &[(usize, usize)], capital_first: bool, v: &mut Vec) { - let mut after_end = capital_first; - for (a, b) in ws { - let Some(w) = text.get(*a..*b) else { continue }; - if after_end && w.chars().next().is_some_and(char::is_lowercase) && w.chars().all(char::is_alphabetic) { - let fixed: String = w.chars().next().map(|f| f.to_uppercase().chain(w.chars().skip(1)).collect()).unwrap_or_default(); - v.push(issue(*a, *b, "Maiuscola a inizio frase".into(), vec![fixed])); - } - after_end = ends_sentence(text.get(*b..).unwrap_or(""), w); - } -} - -fn doubled_punctuation(text: &str, v: &mut Vec) { - for pat in [",,", "..", ";;", "::", "??", "!!"] { - for (i, _) in text.match_indices(pat) { - if pat == ".." && text.get(i + 2..).is_some_and(|r| r.starts_with('.')) { - continue; // ellipsis - } - v.push(issue(i, i + pat.len(), "Punteggiatura doppia".into(), vec![pat[..1].into()])); - } - } -} - -/// Is `c` an apostrophe (straight or typographic)? -fn is_apostrophe(c: char) -> bool { - matches!(c, '\'' | '’') -} - -/// `pò` (never accented) and `po` without its apostrophe → `po'`. `Po` (the river) is left alone. -fn po(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for (a, b) in ws { - let Some(w) = text.get(*a..*b) else { continue }; - let apostrophe = text.get(*b..).and_then(|r| r.chars().next()).is_some_and(is_apostrophe); - let fix = match w { - "pò" => "po'", - "Pò" => "Po'", - "po" if !apostrophe => "po'", - _ => continue, - }; - v.push(issue(*a, *b, "Si scrive «po'», con l'apostrofo e senza accento".into(), vec![fix.into()])); - } -} - -/// `qual'è` → `qual è` (any case): the fix when `word` is `qual` + apostrophe + `è`/`era`. -pub(crate) fn qual_elided(word: &str) -> Option { - let head_end = word.char_indices().take(4).last().map(|(i, c)| i + c.len_utf8())?; - let (head, rest) = (word.get(..head_end)?, word.get(head_end..)?); - let tail = rest.strip_prefix(is_apostrophe)?; - let tail_ok = matches!(tail.to_lowercase().as_str(), "è" | "era"); - (head.eq_ignore_ascii_case("qual") && tail_ok).then(|| format!("{head} {tail}")) -} - -fn qual_e(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for (a, b) in ws { - if let Some(fix) = text.get(*a..*b).and_then(qual_elided) { - v.push(issue(*a, *b, "«Qual è» si scrive senza apostrofo".into(), vec![fix])); - } - } -} - -/// `E'` for the verb → `È`. A quoted letter (`la lettera 'E'`) is left alone. -fn e_apostrophe(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for (a, b) in ws { - if text.get(*a..*b) != Some("E") { - continue; - } - let mut after = text.get(*b..).unwrap_or("").chars(); - let Some(ap) = after.next().filter(|&c| is_apostrophe(c)) else { continue }; - let ends = after.next().is_none_or(char::is_whitespace); - let quoted = text.get(..*a).and_then(|t| t.chars().next_back()).is_some_and(|c| is_apostrophe(c) || c == '‘'); - if ends && !quoted { - v.push(issue(*a, *b + ap.len_utf8(), "«È» si scrive con l'accento, non con l'apostrofo".into(), vec!["È".into()])); - } - } -} - -/// Does `w` (lower case) start with a vowel or `h`, as the words `un'` elides before? -fn starts_with_vowel(w: &str) -> bool { - w.chars().next().is_some_and(|c| "aeiouhàèéìòóù".contains(c)) -} - -/// Is `w` (lower case) a feminine word in `-a` (`altra`, `amica`, `ora`), as far as the ending -/// tells? -fn feminine_in_a(w: &str) -> bool { - w.chars().count() >= 2 && w.ends_with('a') && !MASCULINE_A_ENDINGS.iter().any(|e| w.ends_with(e)) && !NOT_FEMININE_IN_A.contains(&w) -} - -/// `un'` before a masculine word (`un'altro` → `un altro`), and `un` without the apostrophe -/// before a feminine one (`un altra` → `un'altra`). Only lower-case words: a name's gender isn't -/// in its ending. -fn un_apostrophe(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for (a, b) in ws { - let Some(w) = text.get(*a..*b) else { continue }; - let Some((head, tail)) = w.split_once(is_apostrophe) else { continue }; - if UN_FORMS.contains(&head.to_lowercase().as_str()) - && tail.chars().all(|c| c.is_alphabetic() && c.is_lowercase()) - && starts_with_vowel(tail) - && tail.ends_with('o') - && !FEMININE_IN_O.contains(&tail) - { - v.push(issue(*a, *b, format!("Davanti a un nome maschile «{head}» non vuole l'apostrofo"), vec![format!("{head} {tail}")])); - } - } - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if gap == " " - && UN_FORMS.contains(&w0.to_lowercase().as_str()) - && w1.chars().all(|c| c.is_alphabetic() && c.is_lowercase()) - && starts_with_vowel(w1) - && feminine_in_a(w1) - { - v.push(issue(*a0, *b1, format!("Davanti a un nome femminile si scrive «{w0}'», con l'apostrofo"), vec![format!("{w0}'{w1}")])); - } - } -} - -/// Grammar issues in a paragraph of Italian text, in order. -pub fn check(text: &str) -> Vec { - check_with(text, true) -} - -/// [`check`] where `capital_first` says whether the text's first word must start a sentence with a capital -/// (false for a list item, which may be a fragment). -pub fn check_with(text: &str, capital_first: bool) -> Vec { - let ws: Vec<(usize, usize)> = words(text); - let mut v = Vec::new(); - repeated_words(text, &ws, &mut v); - spaces(text, &mut v); - sentence_capitals(text, &ws, capital_first, &mut v); - doubled_punctuation(text, &mut v); - po(text, &ws, &mut v); - qual_e(text, &ws, &mut v); - e_apostrophe(text, &ws, &mut v); - un_apostrophe(text, &ws, &mut v); - v.sort_by_key(|i| (i.start, i.end)); - v -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn italian_grammar_flags() { - test_dict_install(); - let t = "questo questo è un test , fatto male.. Vedi dott. rossi"; - let v = check(t); - let msgs: Vec<&str> = v.iter().map(|i| i.message.as_str()).collect(); - assert!(msgs.iter().any(|m| m.contains("ripetuta")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("prima della punteggiatura")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("doppio")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("doppia")), "{msgs:?}"); - assert!(v.iter().all(|i| i.lang == ProofLang::It), "{v:?}"); - } - - #[test] - fn italian_grammar_stays_quiet() { - test_dict_install(); - assert!( - check("Vado a Roma. Il dott. Rossi è già lì... piano piano!").is_empty(), - "{:?}", - check("Vado a Roma. Il dott. Rossi è già lì... piano piano!") - ); - assert!(check("Sì sì, bene bene :)").is_empty()); - } - - /// The fixes the rules offer for `t`, in order. - fn fixes(t: &str) -> Vec<(String, String)> { - check(t).into_iter().map(|i| (t.get(i.start..i.end).unwrap_or("").to_string(), i.suggestions.join("|"))).collect() - } - - fn pair(a: &str, b: &str) -> (String, String) { - (a.to_string(), b.to_string()) - } - - #[test] - fn po_takes_an_apostrophe() { - assert_eq!(fixes("Dammi un pò di pane"), [pair("pò", "po'")]); - assert_eq!(fixes("Dammi un po di pane"), [pair("po", "po'")]); - assert_eq!(fixes("Pò di più"), [pair("Pò", "Po'")]); - assert!(fixes("Dammi un po' di pane, un po’ d'acqua").is_empty()); - assert!(fixes("Il Po scorre a Torino").is_empty(), "the river"); - } - - #[test] - fn qual_e_has_no_apostrophe() { - assert_eq!(fixes("Dimmi qual'è il problema"), [pair("qual'è", "qual è")]); - assert_eq!(fixes("Qual’era la strada?"), [pair("Qual’era", "Qual era")]); - assert!(fixes("Qual è il problema? Quale era?").is_empty()); - assert!(fixes("Quale'è").is_empty(), "not the truncation"); - assert_eq!(qual_elided("qual'è").as_deref(), Some("qual è")); - assert_eq!(qual_elided("qua"), None); - assert_eq!(qual_elided(""), None); - } - - #[test] - fn e_apostrophe_becomes_e_grave() { - assert_eq!(fixes("E' vero"), [pair("E'", "È")]); - assert_eq!(fixes("Lo so. E’ tardi"), [pair("E’", "È")]); - assert!(fixes("È vero. E poi?").is_empty()); - assert!(fixes("La lettera 'E' è una vocale").is_empty(), "a quoted letter"); - } - - #[test] - fn un_apostrophe_follows_the_gender() { - assert_eq!(fixes("Ho visto un'altro amico"), [pair("un'altro", "un altro")]); - assert_eq!(fixes("È passato un’anno"), [pair("un’anno", "un anno")]); - assert_eq!(fixes("Prendo un altra strada"), [pair("un altra", "un'altra")]); - assert_eq!(fixes("Ho un amica a Roma"), [pair("un amica", "un'amica")]); - assert_eq!(fixes("Non c'è nessun altra via"), [pair("nessun altra", "nessun'altra")]); - for ok in [ - "Un altro amico", - "Un'altra amica", - "Un'auto nuova", - "Un'eco lontana", - "Un artista", - "Un atleta", - "Un idiota", - "Un ipocrita", - "Un enigma", - "Un omicida", - "Un astronauta", - "Un acrobata", - "Un'ora", - "Un'idea", - "Un uomo", - "Un'Alfa", - "Un Andrea", - "Un ha", - ] { - assert!(fixes(ok).is_empty(), "{ok}: {:?}", fixes(ok)); - } - } - - #[test] - fn more_emphatic_doubles_are_allowed() { - for ok in ["Pian piano", "Poco poco", "Zitto zitto", "Via via", "Così così", "Dai dai"] { - assert!(fixes(ok).is_empty(), "{ok}: {:?}", fixes(ok)); - } - } - - proptest::proptest! { - #[test] - fn never_panics(s in "\\PC{0,80}") { - for i in check(&s) { - proptest::prop_assert!(i.start <= i.end && i.end <= s.len()); - } - } - - #[test] - fn never_panics_on_italian_like_text(s in "[a-zàèéìòóùA-ZÈ ,.;:!?'’‘\"«»…\\-]{0,60}") { - for i in check(&s) { - proptest::prop_assert!(i.start <= i.end && i.end <= s.len() && s.is_char_boundary(i.start) && s.is_char_boundary(i.end)); - } - } - } - - fn test_dict_install() { - crate::it::test_dict::install(); - } -} diff --git a/crates/proof/src/it/mod.rs b/crates/proof/src/it/mod.rs deleted file mode 100644 index ee5aaaee3..000000000 --- a/crates/proof/src/it/mod.rs +++ /dev/null @@ -1,213 +0,0 @@ -//! Italian proofing. -//! -//! - **Spelling** against the Hunspell `it_IT` dictionary installed on the system (found by -//! [`crate::hunspell`]: `it_IT.aff`/`it_IT.dic` from the OS, a package manager or -//! LibreOffice; it isn't bundled — the upstream file is GPL-licensed). Without it Italian -//! text gets no spelling marks. Elided forms check the word after the apostrophe -//! (`l'amico`, `un'idea`, `c'è`, `dov'è`, `senz'altro`); truncations keep their apostrophe -//! (`po'`, `mo'`); hyphenated compounds are checked part by part; the user dictionary applies -//! as in English. Forms a grammar rule explains better (`pò`, `qual'è`) are left to it. -//! - **Suggestions** tuned for Italian ([`suggest`]): missing accents first (`caffe` → `caffè`, -//! `perche` → `perché`), then the dictionary's `REP` confusions, a missing space, one-letter -//! typos, and a dictionary scan for anything further off. -//! - **Grammar** ([`grammar`]): cheap, reliable rules with Italian messages, which work with -//! no dictionary installed. - -pub mod grammar; -mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// File names of an Italian Hunspell dictionary (Italy first, then Switzerland). -const NAMES: &[&str] = &["it_IT", "it_CH", "it-IT", "it"]; - -/// The Italian dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Italian"); - -/// The Italian dictionary: the installed Hunspell `it_IT` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available (Italian spelling is off -/// then; see [`has_dictionary`]). -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Use this Italian dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to -/// UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is an Italian dictionary available (spelling is checked)? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Proclitics (and verb clippings) that elide before a vowel or `h`: the word after the -/// apostrophe is what gets checked (`l'amico`, `un'idea`, `c'è`, `m'hai`, `dov'è`, `anch'io`). -const PROCLITICS: &[&str] = &[ - "l", "un", "una", "uno", "all", "dall", "dell", "nell", "sull", "coll", "pell", "c", "m", "t", "s", "v", "d", "j", "n", "ch", "gl", "quest", - "quell", "bell", "buon", "sant", "grand", "poc", "nessun", "ciascun", "qualcun", "alcun", "dov", "cos", "com", "quand", "anch", "senz", "tutt", - "nient", "mezz", "sott", "tant", "quant", "dev", -]; - -/// Truncated words written with an apostrophe (`un po'`, `a mo' di`, `be'`): [`crate::words`] -/// trims the apostrophe, so spelling sees the bare word; [`grammar`] checks the apostrophe. -const TRUNCATIONS: &[&str] = &["po", "mo", "be"]; - -/// Misspellings a grammar rule reports with a clearer message and fix (`pò` → `po'`, `qual'è` -/// → `qual è`), so spelling leaves them alone rather than marking the word twice. -fn left_to_grammar(lower: &str) -> bool { - lower == "pò" || grammar::qual_elided(lower).is_some() -} - -/// Is `word` spelled correctly in Italian? Numbers, single letters, short words in capitals -/// (acronyms: `RAI`, `TG`), words with digits and words in other scripts are accepted, and so -/// is every word when no Italian dictionary is installed. -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool { - let Some(w) = crate::spelling::word_to_check(d, word, opts) else { - return true; - }; - let lower = w.to_lowercase(); - if TRUNCATIONS.contains(&lower.as_str()) || left_to_grammar(&lower) { - return true; - } - if w.contains('-') { - return w.split('-').all(|p| p.chars().count() <= 1 || known(d, p)); - } - if let Some((head, tail)) = w.split_once(['\'', '’']) - && PROCLITICS.contains(&head.to_lowercase().as_str()) - && !tail.is_empty() - { - return known(d, tail); - } - known(d, w) -} - -/// Is one word (no hyphens, no apostrophe) in the dictionary or the user dictionary? -fn known(d: &AffixDict, w: &str) -> bool { - d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_dictionary_loads() { - test_dict::install(); - let d = dictionary(); - assert!(has_dictionary()); - assert!(d.len() > 50, "{}", d.len()); - assert!(d.rule_count() >= 4, "the test dictionary's affix rules"); - assert!(!d.rep().is_empty() && !d.map().is_empty() && !d.try_chars().is_empty()); - } - - #[test] - fn swiss_dictionary_files_are_found() { - // it_CH is searched too, so Swiss users with only it_CH installed get Italian spelling. - let dir = std::env::temp_dir().join(format!("wc-it-ch-{}", std::process::id())); - let _ = std::fs::create_dir_all(&dir); - std::fs::write(dir.join("it_CH.aff"), test_dict::AFF).expect("aff"); - std::fs::write(dir.join("it_CH.dic"), test_dict::DIC).expect("dic"); - let found = crate::hunspell::find_in(&dir, NAMES); - let ok = found.as_ref().is_some_and(|(a, d)| a.ends_with("it_CH.aff") && d.ends_with("it_CH.dic")); - assert!(ok, "{found:?}"); - let _ = std::fs::remove_dir_all(&dir); - } - - #[test] - fn without_a_dictionary_nothing_is_flagged() { - let none = AffixDict::default(); - for w in ["caffè", "perché", "xqzv", ""] { - assert!(is_correct_with(&none, w), "{w}"); - } - assert!(install_dictionary("FLAG long\n", "1\nkot\n").is_err(), "unsupported files are refused"); - } - - #[test] - fn italian_words_are_correct() { - test_dict::install(); - for w in [ - "casa", "case", "libro", "libri", "città", "caffè", "perché", "più", "può", "parlare", "parla", "parlano", "rifare", "bello", "belli", - "nuovo", "bianco", "Roma", "Milano", "l'amico", "un'idea", "L'amico", "RAI", "2026", "e", "non", - ] { - assert!(is_correct(w), "{w}"); - } - } - - #[test] - fn elisions_and_truncations_are_correct() { - test_dict::install(); - for w in ["dov'è", "Com'è", "cos'è", "anch'io", "senz'acqua", "tutt'uno", "quand'è", "nessun'amica", "n'ha", "po'", "po", "mo'"] { - assert!(is_correct(w), "{w}"); - } - } - - #[test] - fn misspellings_are_flagged() { - test_dict::install(); - for w in ["caffé", "perche", "lbiro", "parlre", "amicaa", "citta", "quelo", "dov'lbiro", "xyz'casa"] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn grammar_owned_forms_are_not_marked_twice() { - test_dict::install(); - // `pò` and `qual'è` are reported by the grammar rules, with the fix; spelling stays quiet. - for w in ["pò", "Pò", "qual'è", "Qual’era"] { - assert!(is_correct(w), "{w}"); - assert!(!grammar::check(w).is_empty(), "{w}: grammar reports it"); - } - } - - #[test] - fn user_dictionary_applies() { - test_dict::install(); - assert!(!is_correct("zxqitalianaparola")); - crate::add_word("Zxqitalianaparola"); - assert!(is_correct("zxqitalianaparola")); - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Italian checks - /// those words against the dictionary instead of accepting them. - #[test] - fn proofing_options_reach_the_italian_check() { - test_dict::install(); - let lang = crate::ProofLang::It; - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("LIBRRO", lang, &defaults), "short capitals are acronyms by default"); - assert!(!crate::is_correct_in_with("LIBRRO", lang, &check_caps)); - assert!(crate::is_correct_in_with("LIBRO", lang, &check_caps)); - assert!(crate::is_correct_in_with("librro2", lang, &defaults)); - assert!(!crate::is_correct_in_with("librro2", lang, &check_digits)); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("LIBRRO"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/it/suggest.rs b/crates/proof/src/it/suggest.rs deleted file mode 100644 index 55f869091..000000000 --- a/crates/proof/src/it/suggest.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! Italian spelling suggestions, best first: the shared ranking ([`crate::affix_suggest`]) with the -//! words Italian runs into their neighbours (`perme` → `per me`, `amilano` → `a milano`) and -//! letters to try for a dictionary without a TRY line. - -use crate::affix::AffixDict; -use crate::affix_suggest::{Split, Tuning}; - -/// Short words often run into the next one (`per me`, `a Milano`, `di Roma`). -const LEADING_WORDS: &[&str] = &[ - "a", "e", "è", "di", "da", "in", "con", "su", "per", "tra", "fra", "che", "chi", "cui", "non", "si", "ci", "vi", "ne", "più", "meno", "molto", - "tanto", "come", "dove", "quando", "mai", "qui", "qua", "lì", "là", "ogni", "altro", "tutto", "questo", "quello", "bene", "male", "anche", - "ancora", "sempre", "già", "poi", "ed", "od", "ad", "un", "uno", "una", "il", "lo", "la", "i", "gli", "le", "del", "al", "dal", "nel", "sul", - "col", "pel", "allo", "dello", "nello", "sullo", "della", "alla", "dalla", "nella", "sulla", "degli", "agli", "dagli", "negli", "sugli", "delle", - "alle", "dalle", "nelle", "sulle", "dei", "ai", "dai", "nei", "sui", "coi", "dei", -]; -/// Short words often run into the one before (enclitic pronouns: `dimmelo` in writing without -/// the doubling, or a dropped space: `perme` → `per me`). -const TRAILING_WORDS: &[&str] = &[ - "mi", "ti", "si", "ci", "vi", "ne", "lo", "la", "li", "le", "gli", "me", "te", "lui", "lei", "noi", "voi", "loro", "che", "cosa", "come", "dove", - "è", -]; - -/// What Italian adds to its dictionary's tables. -const TUNING: Tuning = - Tuning { - split: Split::Near { leading: LEADING_WORDS, trailing: TRAILING_WORDS }, alphabet: "aeiortnslcdupmghfbqvàèéìòóùz", ..Tuning::DEFAULT - }; - -/// Spelling suggestions for an Italian word, best first, at most `max`, in the word's letter case. -pub fn suggest(word: &str, max: usize) -> Vec { - suggest_in(&super::dictionary(), word, max) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize) -> Vec { - crate::affix_suggest::suggest_in(d, word, max, &TUNING) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::affix_suggest::{fold, weighted_distance}; - - fn has(word: &str, want: &str) { - let s = suggest(word, 8); - assert!(s.iter().any(|x| x == want), "{word} → {s:?}, want {want}"); - } - - #[test] - fn nosuggest_stems_and_forms_are_never_offered() { - crate::affix::tests::assert_nosuggest_is_honoured(suggest_in); - } - - #[test] - fn italian_misspellings_get_the_right_suggestion() { - crate::it::test_dict::install(); - has("caffe", "caffè"); - has("perche", "perché"); - has("cioe", "cioè"); - has("citta", "città"); - has("lbiro", "libro"); - has("parlre", "parlare"); - has("quelo", "quello"); - has("perme", "per me"); - has("amilano", "a Milano"); - has("roma", "Roma"); - has("Caffe", "Caffè"); - } - - #[test] - fn the_likeliest_fix_comes_first() { - crate::it::test_dict::install(); - assert_eq!(suggest("caffe", 3).first().map(String::as_str), Some("caffè")); - assert_eq!(suggest("perche", 3).first().map(String::as_str), Some("perché")); - } - - #[test] - fn edge_cases_stay_empty_or_bounded() { - crate::it::test_dict::install(); - assert!(suggest("", 5).is_empty()); - assert!(suggest("bene", 0).is_empty()); - assert!(suggest(&"a".repeat(41), 5).is_empty()); - assert!(suggest("due parole", 5).is_empty()); - assert!(suggest_in(&crate::affix::AffixDict::default(), "caffe", 5).is_empty()); - assert!(suggest("caffè", 5).len() <= 5); - assert!(!suggest("caffè", 5).contains(&"caffè".to_string())); - } - - #[test] - fn distance_weighs_accents_half() { - let map = vec![vec!['a', 'à'], vec!['e', 'é', 'è']]; - let related = |a: char, b: char| fold(a, &map) == fold(b, &map); - let c = |s: &str| s.chars().collect::>(); - assert_eq!(weighted_distance(&c("caffe"), &c("caffè"), &related, 9), 1); - assert_eq!(weighted_distance(&c("citta"), &c("città"), &related, 9), 1); - assert_eq!(weighted_distance(&c("bene"), &c("bene"), &related, 9), 0); - assert_eq!(weighted_distance(&c("bene"), &c("male"), &related, 2), 3); - assert_eq!(weighted_distance(&c("bene"), &c("benissimo"), &related, 2), 3); - assert_eq!(weighted_distance(&c(""), &c("ab"), &related, 9), 4); - } - - #[test] - fn suggestions_are_fast_enough() { - crate::it::test_dict::install(); - // Warm the dictionary, then time a worst case (a long word nothing near): the scan round. - let _ = suggest("bene", 1); - let t = std::time::Instant::now(); - for w in ["xqzvweitalianaparola", "prsidenti", "parallellepipedo"] { - let _ = suggest(w, 6); - } - let ms = t.elapsed().as_secs_f64() * 1000.0; - eprintln!("3 suggestion calls: {ms:.0} ms"); - assert!(ms < 20_000.0, "{ms} ms"); - } -} diff --git a/crates/proof/src/it/test_dict.rs b/crates/proof/src/it/test_dict.rs deleted file mode 100644 index 5d594d2fd..000000000 --- a/crates/proof/src/it/test_dict.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! A tiny synthetic Italian Hunspell dictionary for tests (written for these tests; not taken -//! from any real dictionary). Real dictionaries are installed on the system, never bundled. -//! -//! It exercises what the Italian dictionaries use: the default single-character `FLAG` type, -//! suffixes (verb endings `-are`, noun plurals `-o` → `-i`), a prefix (`ri-`), the suggestion -//! tables (`TRY`, `MAP` for accents, `REP` for common confusions) and elided forms with an -//! apostrophe (`l'amico`). - -pub const AFF: &str = "SET UTF-8 -TRY aeiortnslcdupmghfbqvàèéìòóùz -MAP 5 -MAP aà -MAP eéè -MAP iì -MAP oòó -MAP uù -REP 3 -REP caffé caffè -REP perche perché -REP cioe cioè -SFX A Y 3 -SFX A 0 are . -SFX A 0 a . -SFX A 0 ano . -SFX N Y 2 -SFX N o i o -SFX N a e a -PFX R Y 1 -PFX R 0 ri . -"; - -/// Stems (the count line first, as in Hunspell files). -pub const DIC: &str = "97 -città -caffè -perché -più -può -è -già -così -virtù -tribù -menù -tè -Mosca -Roma -Milano -Torino -casa/N -libro/N -cane -gatto -sole -luna -mare -amico -amica -idea -parl/A -mangiare -dormire -fare/R -scuola -bambino/N -albero -fiore -pane -acqua -terra -cielo -notte -giorno -vecchia -vado -con -ha -ho -bel -cioè -la -un -in -piazza -lavoro -tempo -uomo -donna -bello/N -buono -grande -piccolo -nuovo/N -vecchio -bianco -nero -rosso -verde -giallo -uno -due -tre -tutto -come -quando -dove -senza -sopra -sotto -tra -molto -sempre -mai -qui -lì -sì -no -non -che -chi -cui -mio -tuo -suo -nostro -a -per -me -quello -io -"; - -/// Make [`AFF`]/[`DIC`] the Italian dictionary (every test that needs one calls this; they all -/// install the same one, so tests running in parallel agree). -pub fn install() { - if let Err(e) = super::install_dictionary(AFF, DIC) { - panic!("test dictionary: {e}"); - } -} diff --git a/crates/proof/src/lang.rs b/crates/proof/src/lang.rs deleted file mode 100644 index 8f681408c..000000000 --- a/crates/proof/src/lang.rs +++ /dev/null @@ -1,388 +0,0 @@ -//! Proofing languages: which dictionary, grammar rules and hyphenation patterns apply to a run of -//! text. A run's language is its BCP 47 tag (`w:lang` in .docx, `CharProps::lang` in the model), -//! resolved with [`ProofLang::from_tag`]. - -/// A language WordCraft can proof, or [`ProofLang::Other`] for one it can't. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum ProofLang { - /// American English (`en`, `en-US`, and every region not listed for [`ProofLang::EnGb`] or - /// [`ProofLang::EnCa`]): the public-domain Moby word list, the English grammar rules and the - /// English hyphenation dictionary and patterns. British spellings (`colour`, `centre`, - /// `organise`) are flagged, with the American form suggested ([`crate::en_variants`]). Also - /// text with no language at all (WordCraft's default editing language is US English, like - /// Word's). - #[default] - En, - /// English with British spelling (`en-GB`, `en-AU`, `en-NZ`, `en-IE`, `en-ZA`, `en-IN`…): - /// [`ProofLang::En`]'s tools, but American spellings (`color`, `center`, `analyze`) are - /// flagged and `-ize` and `-ise` are both right, as in Oxford spelling. - EnGb, - /// Canadian English (`en-CA`): British `-our`, `-re` and `-ence` (`colour`, `centre`, - /// `defence`) with American `-ize` (`organize`), `tire` and `program`. - EnCa, - /// Polish (`pl`, `pl-PL`): the installed Hunspell dictionary (when there is one), the Polish grammar rules and the Polish - /// hyphenation patterns. - Pl, - /// Bulgarian (`bg`, `bg-BG`): hyphenation by the Bulgarian syllable rules ([`crate::bg`]). - /// There is no Bulgarian dictionary or grammar yet, so, as for [`ProofLang::Other`], no - /// spelling or grammar marks. - Bg, - /// Portuguese (`pt`, `pt-BR`, `pt-PT`): the installed Hunspell dictionary (VERO and - /// friends, when there is one), the Portuguese grammar rules and the Portuguese hyphenation - /// patterns. - Pt, - /// Italian (`it`, `it-IT`, `it-CH`): the installed Hunspell dictionary (`it_IT`, when there - /// is one), the Italian grammar rules and the Italian hyphenation patterns. - It, - /// Croatian (`hr`, `hr-HR`) - Hr, - /// Turkish (`tr`, `tr-TR`, `tr-CY`): the installed Hunspell dictionary (when there is one), - /// Turkish grammar rules, spelling suggestions and Turkish syllable hyphenation rules. - Tr, - /// Russian (`ru`, `ru-RU`): the installed Hunspell dictionary (when there is one), the Russian - /// grammar rules and the Russian word-division rules. - Ru, - /// Czech (`cs`, `cs-CZ`): the installed Hunspell dictionary, dictionary-driven suggestions - /// and generic punctuation checks. Hyphenation keeps the fallback English patterns. - Cs, - /// Indonesian (`id`, `id-ID`; also the old `in` and ISO 639-2 `ind`): the installed Hunspell - /// `id_ID` dictionary, dictionary-driven suggestions and generic checks with Indonesian - /// messages. Hyphenation keeps the fallback English patterns. - Id, - /// Spanish of Spain (`es`, `es-ES`, and every region not listed for [`ProofLang::EsMx`], - /// [`ProofLang::Es419`] or [`ProofLang::EsUy`]; also ISO 639-2 `spa`): the installed - /// Hunspell `es_ES` dictionary (or a generic Spanish one), dictionary-driven suggestions and - /// generic checks with Spanish messages ([`crate::es`]). Hyphenation keeps the fallback - /// English patterns. - Es, - /// Mexican Spanish (`es-MX`): [`ProofLang::Es`]'s tools with the installed `es_MX` - /// dictionary, else a Latin American or generic Spanish one. - EsMx, - /// Latin American Spanish (`es-419`, and the Spanish of the Americas other than Mexico and - /// Uruguay: `es-AR`, `es-CO`, `es-US`…): [`ProofLang::Es`]'s tools with the installed `es_419` - /// dictionary, else a generic or another Latin American Spanish one. - Es419, - /// Uruguayan Spanish (`es-UY`): [`ProofLang::Es`]'s tools with the installed `es_UY` - /// dictionary (else the neighbouring Rioplatense `es_AR`, a Latin American or a generic one), - /// and the `vos` forms of verbs (`tenés`, `vení`, `decime`) accepted ([`crate::es::voseo`]). - EsUy, - /// German (`de`, `de-DE`, `de-AT`, `de-CH`…): hyphenation by the German patterns (reformed - /// spelling). There is no German dictionary or grammar yet, so, as for [`ProofLang::Other`], - /// no spelling or grammar marks. - De, - /// A language without proofing tools here (`fr-FR`, `ja-JP`, `x-none`…). Like Word without - /// the language's proofing tools: no spelling or grammar marks, so its words are never - /// flagged as English misspellings. Hyphenation keeps using the English patterns, as before - /// languages were told apart. - Other, -} - -impl ProofLang { - /// Every language with a spelling dictionary, in display order. - pub const PROOFED: [ProofLang; 15] = [ - ProofLang::En, - ProofLang::EnGb, - ProofLang::EnCa, - ProofLang::Pl, - ProofLang::Pt, - ProofLang::It, - ProofLang::Hr, - ProofLang::Tr, - ProofLang::Ru, - ProofLang::Cs, - ProofLang::Id, - ProofLang::Es, - ProofLang::EsMx, - ProofLang::Es419, - ProofLang::EsUy, - ]; - - /// The proofing language of a BCP 47 tag (`pl-PL`, `en_GB`, `PL`…). The primary subtag - /// counts, and for English and Spanish the region too ([`ProofLang::english`], - /// [`ProofLang::spanish`]). No tag (or an empty one) is English, WordCraft's default editing - /// language. - pub fn from_tag(tag: Option<&str>) -> ProofLang { - let Some(tag) = tag.map(str::trim).filter(|t| !t.is_empty()) else { return ProofLang::En }; - let primary = tag.split(['-', '_']).next().unwrap_or(""); - if primary.eq_ignore_ascii_case("en") || primary.eq_ignore_ascii_case("eng") { - ProofLang::english(tag) - } else if primary.eq_ignore_ascii_case("pl") || primary.eq_ignore_ascii_case("pol") { - ProofLang::Pl - } else if primary.eq_ignore_ascii_case("bg") || primary.eq_ignore_ascii_case("bul") { - ProofLang::Bg - } else if primary.eq_ignore_ascii_case("pt") || primary.eq_ignore_ascii_case("por") { - ProofLang::Pt - } else if primary.eq_ignore_ascii_case("it") || primary.eq_ignore_ascii_case("ita") { - ProofLang::It - } else if primary.eq_ignore_ascii_case("hr") || primary.eq_ignore_ascii_case("hrv") { - ProofLang::Hr - } else if primary.eq_ignore_ascii_case("tr") || primary.eq_ignore_ascii_case("tur") { - ProofLang::Tr - } else if primary.eq_ignore_ascii_case("ru") || primary.eq_ignore_ascii_case("rus") { - ProofLang::Ru - } else if primary.eq_ignore_ascii_case("cs") || primary.eq_ignore_ascii_case("ces") || primary.eq_ignore_ascii_case("cze") { - ProofLang::Cs - } else if primary.eq_ignore_ascii_case("id") || primary.eq_ignore_ascii_case("ind") || primary.eq_ignore_ascii_case("in") { - ProofLang::Id - } else if primary.eq_ignore_ascii_case("es") || primary.eq_ignore_ascii_case("spa") { - ProofLang::spanish(tag) - } else if ["de", "deu", "ger"].iter().any(|c| primary.eq_ignore_ascii_case(c)) { - ProofLang::De - } else { - ProofLang::Other - } - } - - /// The English of an English tag, by its [`region`]: Canada is [`ProofLang::EnCa`], regions - /// that spell the British way are [`ProofLang::EnGb`], and the United States, any other - /// region and no region are [`ProofLang::En`]. - fn english(tag: &str) -> ProofLang { - match region(tag) { - Some(r) if r.eq_ignore_ascii_case("CA") => ProofLang::EnCa, - Some(r) if BRITISH_SPELLING.iter().any(|b| b.eq_ignore_ascii_case(r)) => ProofLang::EnGb, - _ => ProofLang::En, - } - } - - /// The Spanish of a Spanish tag, by its [`region`]: Mexico is [`ProofLang::EsMx`], Uruguay - /// [`ProofLang::EsUy`], Latin America (`419`) and the other countries of the Americas are - /// [`ProofLang::Es419`], and Spain, any other region and no region are [`ProofLang::Es`]. - fn spanish(tag: &str) -> ProofLang { - match region(tag) { - Some(r) if r.eq_ignore_ascii_case("MX") => ProofLang::EsMx, - Some(r) if r.eq_ignore_ascii_case("UY") => ProofLang::EsUy, - Some(r) if LATIN_AMERICA.iter().any(|l| l.eq_ignore_ascii_case(r)) => ProofLang::Es419, - _ => ProofLang::Es, - } - } - - /// Is this one of the Englishes ([`ProofLang::En`], [`ProofLang::EnGb`], [`ProofLang::EnCa`])? - pub fn is_english(self) -> bool { - matches!(self, ProofLang::En | ProofLang::EnGb | ProofLang::EnCa) - } - - /// Is this one of the Spanishes ([`ProofLang::Es`], [`ProofLang::EsMx`], [`ProofLang::Es419`], - /// [`ProofLang::EsUy`])? - pub fn is_spanish(self) -> bool { - matches!(self, ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy) - } - - /// The primary language subtag (`en`, `pl`, `bg`, `tr`, `ru`, `cs`, `id`, `de`, `es`); empty for [`ProofLang::Other`]. - pub fn code(self) -> &'static str { - match self { - ProofLang::En | ProofLang::EnGb | ProofLang::EnCa => "en", - ProofLang::Pl => "pl", - ProofLang::Bg => "bg", - ProofLang::Pt => "pt", - ProofLang::It => "it", - ProofLang::Hr => "hr", - ProofLang::Tr => "tr", - ProofLang::Ru => "ru", - ProofLang::Cs => "cs", - ProofLang::Id => "id", - ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => "es", - ProofLang::De => "de", - ProofLang::Other => "", - } - } - - /// Does WordCraft check spelling and grammar in this language? - pub fn is_proofed(self) -> bool { - Self::PROOFED.contains(&self) - } -} - -/// The region of a tag: the first two-letter or three-digit subtag after the language and -/// script, before any singleton; a POSIX `.UTF-8` or `@…` tail is ignored. -fn region(tag: &str) -> Option<&str> { - let tag = tag.split(['.', '@']).next().unwrap_or(""); - // Subtags after a singleton (`x-…` private use, `u-…` extensions) aren't regions. - tag.split(['-', '_']) - .skip(1) - .take_while(|p| p.len() != 1) - .find(|p| (p.len() == 2 && p.chars().all(|c| c.is_ascii_alphabetic())) || (p.len() == 3 && p.chars().all(|c| c.is_ascii_digit()))) -} - -/// Regions whose Spanish is Latin American ([`ProofLang::Es419`]): Latin America and the -/// Caribbean (`419`) and its parts (South America `005`, Central America `013`, the Caribbean -/// `029`), the Spanish-speaking countries of the Americas other than Mexico and Uruguay, and the -/// United States. -const LATIN_AMERICA: [&str; 22] = - ["419", "005", "013", "029", "AR", "BO", "CL", "CO", "CR", "CU", "DO", "EC", "GT", "HN", "NI", "PA", "PE", "PR", "PY", "SV", "US", "VE"]; - -/// Regions whose English uses British spelling ([`ProofLang::EnGb`]): the United Kingdom (and -/// the non-standard `UK`), Ireland, Australia, New Zealand, South Africa, India and other -/// Commonwealth countries, the Crown dependencies and Europe (`150`). -const BRITISH_SPELLING: [&str; 26] = [ - "GB", "UK", "IE", "AU", "NZ", "ZA", "IN", "SG", "HK", "MY", "MT", "CY", "GI", "IM", "JE", "GG", "JM", "TT", "BS", "BB", "BZ", "KE", "NG", "GH", - "PK", "150", -]; - -/// A BCP 47 tag in canonical case (`pl-PL`, `en-GB`, `zh-Hant-TW`, `x-none`), or `None` when -/// `tag` isn't shaped like one: 1–35 ASCII letters, digits and `-`/`_` separators, starting with -/// a 2–8 letter language (or `x`/`i` for private and grandfathered tags). `_` becomes `-`. -pub fn normalize_tag(tag: &str) -> Option { - let tag = tag.trim(); - if tag.is_empty() || tag.len() > 35 || !tag.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') { - return None; - } - let parts: Vec<&str> = tag.split(['-', '_']).collect(); - if parts.iter().any(|p| p.is_empty() || p.len() > 8) { - return None; - } - let first = parts.first()?; - let language = (2..=8).contains(&first.len()) && first.chars().all(|c| c.is_ascii_alphabetic()); - if !language && !first.eq_ignore_ascii_case("x") && !first.eq_ignore_ascii_case("i") { - return None; - } - let letters = |p: &str| p.chars().all(|c| c.is_ascii_alphabetic()); - // After a singleton (`x-…` private use, `u-…` extensions) subtags are lower case. - let singleton = parts.iter().position(|p| p.len() == 1).unwrap_or(usize::MAX); - let out: Vec = parts - .iter() - .enumerate() - .map(|(i, p)| { - if i >= singleton { - p.to_ascii_lowercase() - } else if i > 0 && p.len() == 2 && letters(p) { - p.to_ascii_uppercase() // region - } else if i > 0 && p.len() == 4 && letters(p) { - let lower = p.to_ascii_lowercase(); - let mut c = lower.chars(); - c.next().map(|f| f.to_ascii_uppercase().to_string() + c.as_str()).unwrap_or_default() // script - } else { - p.to_ascii_lowercase() - } - }) - .collect(); - Some(out.join("-")) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tags_normalize() { - assert_eq!(normalize_tag("PL-pl").as_deref(), Some("pl-PL")); - assert_eq!(normalize_tag(" pl_pl ").as_deref(), Some("pl-PL")); - assert_eq!(normalize_tag("pl").as_deref(), Some("pl")); - assert_eq!(normalize_tag("zh-hant-tw").as_deref(), Some("zh-Hant-TW")); - assert_eq!(normalize_tag("es-419").as_deref(), Some("es-419")); - assert_eq!(normalize_tag("x-none").as_deref(), Some("x-none")); - assert_eq!(normalize_tag("de-DE-u-co-PHONEBK").as_deref(), Some("de-DE-u-co-phonebk")); - for bad in ["", " ", "p", "1pl", "pl--PL", "pl PL", "pl-PL;", "polski język", "-pl", "pl-", "abcdefghi", &"a".repeat(40)] { - assert_eq!(normalize_tag(bad), None, "{bad:?}"); - } - } - - fn assert_tags(tags: &[&str], expected: ProofLang) { - for tag in tags { - assert_eq!(ProofLang::from_tag(Some(tag)), expected, "{tag}"); - } - } - - #[test] - fn tags_resolve_primary_languages() { - assert_tags(&["pl", "pl-PL", "PL-pl", "pl_PL", "pl_PL.UTF-8", " pl-PL ", "pol"], ProofLang::Pl); - assert_tags(&["pt", "pt-BR", "PT_br", "pt-BR.UTF-8", " pt-PT ", "por"], ProofLang::Pt); - assert_tags(&["it", "it-IT", "IT_it", "it-CH", "it-CH.UTF-8", " it-IT ", "ita"], ProofLang::It); - assert_tags(&["cs", "cs-CZ", "CS-cz", "cs_CZ", " ces ", "CES", "cze", "CZE"], ProofLang::Cs); - assert_tags(&["id", "id-ID", "ID-id", "id_ID", "id_ID.UTF-8", " id-ID ", "ind", "in", "in_ID"], ProofLang::Id); - assert_eq!(ProofLang::Cs.code(), "cs"); - assert!(ProofLang::Cs.is_proofed() && ProofLang::PROOFED.contains(&ProofLang::Cs)); - assert_eq!(ProofLang::Id.code(), "id"); - assert!(ProofLang::Id.is_proofed() && ProofLang::PROOFED.contains(&ProofLang::Id)); - } - - #[test] - fn tags_resolve_english_regions() { - assert_tags(&["en", "en-US", "EN_us", "en-Latn-US", "en-PH", "en-001", "eng", "en_US.UTF-8", "en-x-gb"], ProofLang::En); - assert_tags( - &[ - "en-GB", - "EN_gb", - "en_GB.UTF-8", - "en-AU", - "en_AU", - "en-NZ", - "en-IE", - "en-ZA", - "en-IN", - "en-Latn-GB", - "en-UK", - "en-150", - "eng-GB", - "en_GB@euro", - ], - ProofLang::EnGb, - ); - assert_tags(&["en-CA", "en_CA", "EN-ca", "en_CA.UTF-8", "en-Latn-CA"], ProofLang::EnCa); - for language in [ProofLang::En, ProofLang::EnGb, ProofLang::EnCa] { - assert!(language.is_english() && language.is_proofed() && ProofLang::PROOFED.contains(&language), "{language:?}"); - assert_eq!(language.code(), "en"); - } - assert!(!ProofLang::Pl.is_english() && !ProofLang::Other.is_english()); - // The region is the first two-letter or three-digit subtag: a script or variant isn't one. - assert_eq!(ProofLang::from_tag(Some("en-Brai")), ProofLang::En); - assert_eq!(ProofLang::from_tag(Some("en-GB-oxendict")), ProofLang::EnGb); - } - - #[test] - fn tags_resolve_additional_locales_and_defaults() { - assert_tags(&["bg", "bg-BG", "BG_bg", "bg_BG.UTF-8", "bul"], ProofLang::Bg); - assert_tags(&["tr", "tr-TR", "tr-CY", "TR_tr", "tur"], ProofLang::Tr); - assert_tags(&["de", "de-DE", "de-CH", "DE_at", "deu", "ger"], ProofLang::De); - assert_tags(&["ru", "ru-RU", "RU-ru", "ru_RU", "ru_RU.UTF-8", " ru-RU ", "rus"], ProofLang::Ru); - assert!(!ProofLang::De.is_proofed()); - assert!(!ProofLang::Bg.is_proofed()); - assert_eq!(ProofLang::Bg.code(), "bg"); - assert!(ProofLang::Pt.is_proofed() && !ProofLang::Other.is_proofed()); - assert!(ProofLang::Tr.is_proofed()); - assert!(ProofLang::Ru.is_proofed()); - assert_eq!(ProofLang::Ru.code(), "ru"); - assert!(ProofLang::PROOFED.contains(&ProofLang::Ru)); - assert_eq!(ProofLang::Pl.code(), "pl"); - assert_eq!(ProofLang::Pt.code(), "pt"); - assert!(ProofLang::It.is_proofed()); - assert_eq!(ProofLang::It.code(), "it"); - assert!(ProofLang::Hr.is_proofed()); - assert_eq!(ProofLang::Hr.code(), "hr"); - assert_eq!(ProofLang::Tr.code(), "tr"); - assert!(ProofLang::PROOFED.contains(&ProofLang::Pt), "every proofed language is listed"); - assert_eq!(ProofLang::from_tag(None), ProofLang::En); - assert_eq!(ProofLang::from_tag(Some("")), ProofLang::En); - assert_eq!(ProofLang::from_tag(Some(" ")), ProofLang::En); - assert!(ProofLang::Pl.is_proofed() && !ProofLang::Other.is_proofed()); - } - - #[test] - fn tags_reject_unknown_or_partial_language_codes() { - assert_tags(&["fr-FR", "dex", "x-none", "ja-JP", "plx", "bgx", "trx", "csx", "idx", "inx", "esx", "spax", "e", "-"], ProofLang::Other); - } - - /// #3360, #3553: Spanish is proofed, by region: Spain (and no region), Mexico, Uruguay, and - /// Latin America with the other countries of the Americas. - #[test] - fn spanish_tags_resolve_by_region() { - for t in ["es", "es-ES", "ES_es", "es_ES.UTF-8", " es-ES ", "spa", "es-Latn-ES", "es-GQ", "es-PH", "es-x-mx", "es_ES@euro"] { - assert_eq!(ProofLang::from_tag(Some(t)), ProofLang::Es, "{t}"); - } - for t in ["es-MX", "es_MX", "ES-mx", "es_MX.UTF-8", "spa-MX", "es-Latn-MX"] { - assert_eq!(ProofLang::from_tag(Some(t)), ProofLang::EsMx, "{t}"); - } - for t in ["es-419", "es_419", "es-AR", "es-CO", "es_CL.UTF-8", "es-US", "ES-ve", "es-PR", "es-Latn-419", "es-005", "spa-PE"] { - assert_eq!(ProofLang::from_tag(Some(t)), ProofLang::Es419, "{t}"); - } - for t in ["es-UY", "es_UY", "ES-uy", "es_UY.UTF-8", "spa-UY", "es-Latn-UY"] { - assert_eq!(ProofLang::from_tag(Some(t)), ProofLang::EsUy, "{t}"); - } - for l in [ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy] { - assert!(l.is_spanish() && l.is_proofed() && ProofLang::PROOFED.contains(&l), "{l:?}"); - assert!(!l.is_english()); - assert_eq!(l.code(), "es"); - } - assert!(!ProofLang::En.is_spanish() && !ProofLang::Pt.is_spanish() && !ProofLang::Other.is_spanish()); - assert_eq!(normalize_tag("ES_mx").as_deref(), Some("es-MX")); - } -} diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs deleted file mode 100644 index f92ad19a3..000000000 --- a/crates/proof/src/lib.rs +++ /dev/null @@ -1,2000 +0,0 @@ -//! WordCraft proofing, per language. A run's language (its BCP 47 tag) picks the tools through -//! [`ProofLang`]; the `*_in` functions take it, the older English-only ones remain. -//! -//! - **English** ([`ProofLang::En`]; also text with no language): spelling against the -//! public-domain Moby word list (~160k words, the same list that drives hyphenation) plus our -//! supplement of modern words it lacks (`assets/spelling/en-extra.txt`: `desktop`, `email`, -//! `download`…), with inflections (`-s`, `-es`, `-ed`, `-ing`, `-ly`, `'s`…), a user dictionary and ignore lists; -//! suggestions by edit distance. Grammar checks that are cheap and reliable: repeated words, -//! `a`/`an`, capital letter after a sentence end, spaces before punctuation, doubled spaces. -//! - **Polish** ([`ProofLang::Pl`], module [`pl`]): spelling against the Hunspell `pl_PL` -//! dictionary installed on the system ([`hunspell`]; not bundled, so without it Polish has no -//! spelling marks), read by the affix engine ([`affix`]), suggestions tuned for Polish -//! (diacritics, `ż`/`rz`, `u`/`ó`, missing spaces), and Polish grammar rules with Polish -//! messages. -//! - **Bulgarian** ([`ProofLang::Bg`], module [`bg`]): hyphenation by the Bulgarian syllable -//! rules; no spelling or grammar marks yet. -//! - **Portuguese** ([`ProofLang::Pt`], module [`pt`]): spelling against the Hunspell `pt_BR` -//! dictionary installed on the system (VERO and friends; not bundled), suggestions tuned for -//! Portuguese (missing accents, `seje`/`seja` confusions, missing spaces), conservative -//! grammar rules with Portuguese messages, and `hyph-utf8`'s Portuguese patterns. -//! - **Italian** ([`ProofLang::It`], module [`it`]): spelling against the Hunspell `it_IT` -//! dictionary installed on the system (not bundled — the upstream file is GPL-licensed), -//! suggestions tuned for Italian (missing accents, `REP` confusions, missing spaces), -//! conservative grammar rules with Italian messages, and `hyph-utf8`'s Italian patterns -//! (Beccari, MIT option). -//! - **Russian** ([`ProofLang::Ru`], module [`ru`]): spelling against the Hunspell `ru_RU` -//! dictionary installed on the system (not bundled either), with `ё` optional and hyphenated -//! words (`кто-то`, `по-русски`, `северо-западный`) understood, suggestions tuned for Russian -//! (`е`/`ё`, `жи`/`ши`, `тся`/`ться`, voiced and voiceless pairs, missing spaces), Russian -//! grammar rules with Russian messages, and Russian word division. -//! - **Czech** ([`ProofLang::Cs`], module [`cs`]): the installed Hunspell `cs_CZ` dictionary -//! (not bundled), dictionary-driven suggestions and generic checks with Czech messages. -//! Hyphenation uses the existing English fallback; no Czech patterns are bundled. -//! - **Indonesian** ([`ProofLang::Id`], module [`id`]): the installed Hunspell `id_ID` -//! dictionary (not bundled), dictionary-driven suggestions and generic checks with Indonesian -//! messages. Hyphenation uses the existing English fallback. -//! - **Spanish** ([`ProofLang::Es`], [`ProofLang::EsMx`], [`ProofLang::Es419`], [`ProofLang::EsUy`], -//! module [`es`]): each variety's installed Hunspell dictionary (`es_ES`, `es_MX`, `es_419`, -//! `es_UY`, with fallbacks; not bundled), dictionary-driven suggestions, Uruguayan `vos` forms, -//! and generic checks plus the opening `¿`/`¡` with Spanish messages. Hyphenation uses the -//! existing English fallback. -//! - **German** ([`ProofLang::De`]): hyphenation by `hyph-utf8`'s German patterns (reformed -//! spelling, MIT); no spelling or grammar marks yet. -//! - **Other languages** ([`ProofLang::Other`]): no spelling or grammar marks, like Word without -//! the language's proofing tools. -//! - **Hyphenation** ([`hyphen`]): dictionary, Liang patterns (English, Polish, Portuguese, -//! Italian, German), Bulgarian syllable rules, Russian word-division rules, heuristic. -//! -//! [`check_text`] checks a paragraph whose runs are in different languages. -#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] - -pub mod affix; -mod affix_suggest; -pub mod bg; -pub mod cs; -pub mod dict; -pub mod en_variants; -pub mod es; -pub mod hr; -pub mod hunspell; -pub mod hyphen; -pub mod id; -pub mod it; -pub mod lang; -pub mod patterns; -pub mod pl; -pub mod pt; -pub mod ru; -mod slot; -mod spelling; -pub mod tr; -pub mod vi; - -use std::collections::HashSet; -use std::ops::Range; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{OnceLock, RwLock}; - -/// What the spelling check skips (File › Options › Proofing). Word's defaults: all on. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Options { - /// Words in capitals (`NASA`, `WORDCRAFT`) are taken as correct. - pub ignore_uppercase: bool, - /// Words containing digits (`B2B`, `mp3`) are taken as correct. - pub ignore_numbers: bool, - /// Web and email addresses and file paths (`https://…`, `a@b.org`, `www.…`) are skipped. - pub ignore_internet: bool, -} - -impl Default for Options { - fn default() -> Self { - Options { ignore_uppercase: true, ignore_numbers: true, ignore_internet: true } - } -} - -pub use lang::ProofLang; - -/// A problem found in text: byte range, kind, message and suggestions. -#[derive(Clone, Debug, PartialEq)] -pub struct Issue { - pub start: usize, - pub end: usize, - pub kind: IssueKind, - /// In the language of the text it was found in (Polish rules explain in Polish). - pub message: String, - pub suggestions: Vec, - /// The language the text was checked in: spelling suggestions come from its dictionary - /// ([`suggest_in`]). - pub lang: ProofLang, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum IssueKind { - Spelling, - Grammar, -} - -fn user_words() -> &'static RwLock> { - static U: OnceLock>> = OnceLock::new(); - U.get_or_init(|| RwLock::new(HashSet::new())) -} - -/// Bumped whenever the user dictionary changes, so cached proofing results can be dropped. -static DICTIONARY_GENERATION: AtomicU64 = AtomicU64::new(0); - -/// The key a word has in the user dictionary and the Ignore All list: lower case and composed -/// (NFC), as spelling looks words up ([`composed`]). A word added, ignored, removed or loaded -/// decomposed (`zqprue\u{301}ba`) is then the same entry as its composed form (`zqpruéba`), and -/// the other way round (#3562 review). -fn dictionary_key(w: &str) -> String { - composed(&w.to_lowercase()).into_owned() -} - -/// Add a word to the user dictionary ("Add to Dictionary"). It applies to every language. -pub fn add_word(w: &str) { - if user_words().write().unwrap_or_else(std::sync::PoisonError::into_inner).insert(dictionary_key(w)) { - DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); - } -} - -/// Take a word out of the user dictionary (File › Options › Proofing). Returns whether it was there. -pub fn remove_word(w: &str) -> bool { - let removed = user_words().write().unwrap_or_else(std::sync::PoisonError::into_inner).remove(&dictionary_key(w)); - if removed { - DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); - } - removed -} - -fn ignored_words() -> &'static RwLock> { - static I: OnceLock>> = OnceLock::new(); - I.get_or_init(|| RwLock::new(HashSet::new())) -} - -/// Ignore a word everywhere until the app closes ("Ignore All"). Unlike [`add_word`] it isn't -/// part of the user dictionary: it is never listed, saved or loaded, as in Word. -pub fn ignore_word(w: &str) { - if ignored_words().write().unwrap_or_else(std::sync::PoisonError::into_inner).insert(dictionary_key(w)) { - DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); - } -} - -/// A language proofed against an installed Hunspell dictionary: all the functions below need to -/// know about it, so a language joins with one row in [`LANGUAGES`]. -struct Language { - lang: ProofLang, - slot: &'static dyn slot::Slot, - /// The spelling mark's message, in the language. - message: &'static str, - is_correct: fn(&str, ProofLang, &Options) -> bool, - suggest: fn(&str, usize, ProofLang) -> Vec, - /// Grammar issues; the flag says whether the first word must have a capital (not in a list - /// item). - grammar: fn(&str, ProofLang, bool) -> Vec, -} - -/// Every language proofed against a Hunspell dictionary, in the order File › Options › Proofing -/// lists them. English has a built-in word list and is handled apart. -static LANGUAGES: [Language; 12] = [ - Language { - lang: ProofLang::Pl, - slot: &pl::DICT, - message: "Możliwy błąd pisowni", - is_correct: |w, _, o| pl::is_correct_with_options(w, o), - suggest: |w, n, _| pl::suggest(w, n), - grammar: |t, _, c| pl::grammar::check_with(t, c), - }, - Language { - lang: ProofLang::Pt, - slot: &pt::DICT, - message: "Possível erro de ortografia", - is_correct: |w, _, o| pt::is_correct_with_options(w, o), - suggest: |w, n, _| pt::suggest(w, n), - grammar: |t, _, c| pt::grammar::check_with(t, c), - }, - Language { - lang: ProofLang::It, - slot: &it::DICT, - message: "Possibile errore di ortografia", - is_correct: |w, _, o| it::is_correct_with_options(w, o), - suggest: |w, n, _| it::suggest(w, n), - grammar: |t, _, c| it::grammar::check_with(t, c), - }, - Language { - lang: ProofLang::Hr, - slot: &hr::DICT, - message: "Moguća pravopisna pogreška", - is_correct: |w, _, o| hr::is_correct_with_options(w, o), - suggest: |w, n, _| hr::suggest(w, n), - grammar: |t, _, c| hr::grammar::check_with(t, c), - }, - Language { - lang: ProofLang::Tr, - slot: &tr::DICT, - message: "Olası yazım hatası", - is_correct: |w, _, o| tr::is_correct_with_options(w, o), - suggest: |w, n, _| tr::suggest(w, n), - grammar: |t, _, c| tr::grammar::check_with(t, c), - }, - Language { - lang: ProofLang::Ru, - slot: &ru::DICT, - message: "Возможна орфографическая ошибка", - is_correct: |w, _, o| ru::is_correct_with_options(w, o), - suggest: |w, n, _| ru::suggest(w, n), - grammar: |t, _, c| ru::grammar::check_with(t, c), - }, - // Czech, Indonesian and Spanish have no sentence-capital rule, so a list item needs no - // exception. - Language { - lang: ProofLang::Cs, - slot: &cs::DICT, - message: "Možná pravopisná chyba", - is_correct: |w, _, o| cs::is_correct_with_options(w, o), - suggest: |w, n, _| cs::suggest(w, n), - grammar: |t, _, _| cs::grammar::check(t), - }, - Language { - lang: ProofLang::Id, - slot: &id::DICT, - message: "Kemungkinan salah eja", - is_correct: |w, _, o| id::is_correct_with_options(w, o), - suggest: |w, n, _| id::suggest(w, n), - grammar: |t, _, _| id::grammar::check(t), - }, - Language { - lang: ProofLang::Es, - slot: &es::SPAIN_DICT, - message: "Posible error ortográfico", - is_correct: es::is_correct_with_options, - suggest: es::suggest, - grammar: |t, l, _| es::grammar::check(t, l), - }, - Language { - lang: ProofLang::EsMx, - slot: &es::MEXICO_DICT, - message: "Posible error ortográfico", - is_correct: es::is_correct_with_options, - suggest: es::suggest, - grammar: |t, l, _| es::grammar::check(t, l), - }, - Language { - lang: ProofLang::Es419, - slot: &es::LATIN_AMERICA_DICT, - message: "Posible error ortográfico", - is_correct: es::is_correct_with_options, - suggest: es::suggest, - grammar: |t, l, _| es::grammar::check(t, l), - }, - Language { - lang: ProofLang::EsUy, - slot: &es::URUGUAY_DICT, - message: "Posible error ortográfico", - is_correct: es::is_correct_with_options, - suggest: es::suggest, - grammar: |t, l, _| es::grammar::check(t, l), - }, -]; - -/// `lang`'s row in [`LANGUAGES`]; `None` for English and the languages WordCraft doesn't proof. -fn language(lang: ProofLang) -> Option<&'static Language> { - LANGUAGES.iter().find(|l| l.lang == lang) -} - -/// Search `dir` for Hunspell dictionaries before the system locations (File › Options › -/// Proofing › Dictionaries folder, #1063); `None` clears it. The dictionaries load again from -/// the new search path, and text is checked again. -pub fn set_dictionaries_folder(dir: Option) { - hunspell::set_dictionary_dir(dir); - for l in &LANGUAGES { - l.slot.reload(); - } - DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); -} - -/// Each language that reads an installed Hunspell dictionary, with the `.dic` file found for it -/// now (`None`: not installed, so the language has no spelling marks). -pub fn installed_dictionaries() -> Vec<(ProofLang, Option)> { - LANGUAGES.iter().map(|l| (l.lang, l.slot.installed_path())).collect() -} - -/// Why the dictionary found for `lang` failed to load (unsupported encoding, too large, a parse -/// error), if it was read and failed. Doesn't read anything itself: a dictionary nobody has -/// checked yet reports `None` (#2830). -pub fn dictionary_load_error(lang: ProofLang) -> Option { - hunspell::load_error(language(lang)?.slot.name()).map(|(_, reason)| reason) -} - -/// A number that changes whenever a word is added to or removed from the user dictionary, or the -/// dictionaries folder changes. -pub fn dictionary_generation() -> u64 { - DICTIONARY_GENERATION.load(Ordering::Relaxed) -} - -/// Is `lower` (a lower-case word) in the user dictionary or ignored? It is looked up composed, as -/// the entries are kept ([`dictionary_key`]). -pub(crate) fn in_user_dictionary(lower: &str) -> bool { - let key = composed(lower); - user_words().read().unwrap_or_else(std::sync::PoisonError::into_inner).contains(&*key) - || ignored_words().read().unwrap_or_else(std::sync::PoisonError::into_inner).contains(&*key) -} - -/// Words in the user dictionary. -pub fn user_dictionary() -> Vec { - let mut v: Vec = user_words().read().unwrap_or_else(std::sync::PoisonError::into_inner).iter().cloned().collect(); - v.sort(); - v -} - -/// Longest custom-dictionary word kept, in characters (mirrors `file.options` validation). -pub const MAX_CUSTOM_WORD_CHARS: usize = 64; -/// Most custom-dictionary words read from one file (caps a hostile file). -const MAX_CUSTOM_WORDS: usize = 100_000; -/// Largest custom-dictionary file read, in bytes (caps a hostile file). -const MAX_CUSTOM_FILE_BYTES: u64 = 1_000_000; - -/// `w` as a custom-dictionary word: trimmed and lower-cased, or `None` when it isn't a single -/// word of at most [`MAX_CUSTOM_WORD_CHARS`] characters (empty, spaces, control characters, or -/// bytes that weren't UTF-8). -/// Adding and loading both use it, so a word that can be added is never dropped on the next load. -pub fn custom_word(w: &str) -> Option { - let w = w.trim(); - if w.is_empty() - || w.chars().count() > MAX_CUSTOM_WORD_CHARS - || w.chars().any(|c| c.is_whitespace() || c.is_control() || c == char::REPLACEMENT_CHARACTER) - { - return None; - } - Some(dictionary_key(w)) -} - -/// The valid words of the custom-dictionary file at `path`, reading at most -/// [`MAX_CUSTOM_FILE_BYTES`] of it and keeping at most [`MAX_CUSTOM_WORDS`]. A missing or -/// unreadable file reads as no words. -fn read_dictionary_file(path: &std::path::Path) -> Vec { - use std::io::Read; - let mut bytes = Vec::new(); - let Ok(file) = std::fs::File::open(path) else { return Vec::new() }; - // `take` bounds the read itself, so a huge file is never read whole. - if file.take(MAX_CUSTOM_FILE_BYTES).read_to_end(&mut bytes).is_err() { - return Vec::new(); - } - // Lossy: a cut multi-byte tail becomes U+FFFD and is skipped by validation below, - // instead of discarding the whole file. - String::from_utf8_lossy(&bytes).lines().filter_map(custom_word).take(MAX_CUSTOM_WORDS).collect() -} - -/// Read custom-dictionary words from a file into the user dictionary (one word per line; -/// blank lines are skipped). A missing file reads as empty; a hostile file is capped, -/// never a panic and never an error that stops startup. Returns the words added. -pub fn load_user_dictionary(path: &std::path::Path) -> Vec { - let words = read_dictionary_file(path); - let mut set = user_words().write().unwrap_or_else(std::sync::PoisonError::into_inner); - let mut added = Vec::new(); - for w in words { - if set.len() >= MAX_CUSTOM_WORDS { - break; - } - if set.insert(w.clone()) { - added.push(w); - } - } - if !added.is_empty() { - DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); - } - added -} - -/// Write the user dictionary to a file (one word per line, sorted), creating its folder. -/// -/// Words another process (a second window, the MCP bridge) saved to the file since it was loaded -/// are merged in first, so neither loses the other's words; `removed` (words just taken out) stay -/// out. The file is replaced atomically: written beside it, then renamed over it. Errors (a -/// read-only folder, a full disk) go back to the caller; the in-memory words are kept. -pub fn save_user_dictionary(path: &std::path::Path, removed: &[String]) -> std::io::Result<()> { - let removed: HashSet = removed.iter().map(|w| dictionary_key(w)).collect(); - let on_disk = read_dictionary_file(path); - { - let mut set = user_words().write().unwrap_or_else(std::sync::PoisonError::into_inner); - let mut changed = false; - for w in on_disk { - if set.len() >= MAX_CUSTOM_WORDS { - break; - } - if !removed.contains(&w) && set.insert(w) { - changed = true; - } - } - for w in &removed { - changed |= set.remove(w); - } - if changed { - DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); - } - } - let mut out = String::new(); - for w in user_dictionary() { - out.push_str(&w); - out.push('\n'); - } - if let Some(dir) = path.parent().filter(|d| !d.as_os_str().is_empty()) { - std::fs::create_dir_all(dir)?; - } - let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_else(|| "custom-dictionary.txt".into()); - let tmp = path.with_file_name(format!(".{name}.{}.tmp", std::process::id())); - let written = std::fs::write(&tmp, out).and_then(|()| std::fs::rename(&tmp, path)); - if written.is_err() { - let _ = std::fs::remove_file(&tmp); - } - written -} - -/// Common words the source list lacks (modern vocabulary, contractions). -const COMMON: &str = "a an the and or but if of to in on at by for with from as is am are was were be been being it its we us our you your he him his she her they them their i me my mine this that these those there here what which who whom whose when where why how all any both each few more most other some such no nor not only own same so than too very can will just should now do does did done has have had having may might must shall would could up down out over under again further then once off into onto upon about above below between through during before after until while because per via vs etc ok yes yeah oh ah hi hey bye one two three four five six seven eight nine ten first last next new old get got go goes went gone see saw seen say said make made take took come came give gave know knew think thought look want need use used find found tell told ask asked feel felt try left keep let put mean set run show turn move live play pay hear hold bring begin seem help talk start might"; - -/// Our supplement of common words the Moby list lacks (`desktop`, `email`, `download`…): one -/// lowercase base form per line, `#` comments. Inflections come from [`known_core`]. -const EXTRA_EN: &str = include_str!("../../../assets/spelling/en-extra.txt"); - -fn extra_words() -> &'static HashSet<&'static str> { - static E: OnceLock> = OnceLock::new(); - E.get_or_init(|| EXTRA_EN.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')).collect()) -} - -/// A base form in the dictionary or the supplement. -fn base_known(d: &dict::Dictionary, w: &str) -> bool { - d.get(w).is_some() || extra_words().contains(w) -} - -fn known_core(w: &str) -> bool { - let d = dict::Dictionary::en_us(); - if base_known(d, w) || COMMON.split(' ').any(|c| c == w) { - return true; - } - if in_user_dictionary(w) { - return true; - } - // Inflections. - let stems: [(&str, &[&str]); 13] = [ - ("'s", &[""]), - ("s", &[""]), - ("es", &["", "e"]), - ("ies", &["y"]), - ("ed", &["", "e"]), - ("ied", &["y"]), - ("ing", &["", "e"]), - ("ly", &["", "le"]), - ("ily", &["y"]), - // specifically → specific (#1694): only -ic adjectives, so "desktopally" stays wrong. - ("ically", &["ic"]), - ("er", &["", "e"]), - ("est", &["", "e"]), - ("ness", &[""]), - ]; - for (suf, adds) in stems { - if let Some(stem) = w.strip_suffix(suf) - && stem.len() >= 2 - { - for a in adds { - let s = format!("{stem}{a}"); - if base_known(d, &s) { - return true; - } - } - // Doubled consonant: running → run, stopped → stop. - let b = stem.as_bytes(); - if (suf == "ing" || suf == "ed" || suf == "er" || suf == "est") - && b.len() >= 3 - && b.last() == b.get(b.len() - 2) - && let Some(s) = stem.get(..stem.len() - 1) - && base_known(d, s) - { - return true; - } - } - } - // Prefixes. - for pre in ["un", "re", "non", "pre", "over", "under", "co", "mis"] { - if let Some(rest) = w.strip_prefix(pre) - && rest.len() >= 3 - && base_known(d, rest) - { - return true; - } - } - false -} - -/// Is there a proofing dictionary for this BCP 47 language tag? `None` (and empty tags) mean the -/// document default, which is en-US. Runs in a language without one are skipped (#100) instead -/// of flagging every word against another language's dictionary; see [`ProofLang::from_tag`]. -pub fn has_dictionary(lang: Option<&str>) -> bool { - ProofLang::from_tag(lang).is_proofed() -} - -/// The Hunspell dictionary name (`pl_PL`, `it_IT`) a proofing language needs but doesn't have -/// installed, for a status-bar hint (#1167). `None` when the language's dictionary is available, -/// or when it needs none (English has a built-in word list; unproofed languages aren't offered -/// one). Reads the dictionary once (it is cached afterwards). -pub fn missing_dictionary(lang: Option<&str>) -> Option<&'static str> { - let lang = ProofLang::from_tag(lang); - dictionary_needed(lang, dictionary_loaded(lang)?) -} - -/// The Hunspell name, `.dic` file and reason of a dictionary that is installed for this language -/// but couldn't be read (unsupported `SET` encoding, too large, a parse error). `None` when it -/// loaded, or when no file exists (then [`missing_dictionary`] names it). Reads the dictionary -/// once (it is cached afterwards). -pub fn unreadable_dictionary(lang: Option<&str>) -> Option<(&'static str, std::path::PathBuf, String)> { - let lang = ProofLang::from_tag(lang); - let name = dictionary_needed(lang, dictionary_loaded(lang)?)?; - hunspell::load_error(name).map(|(file, reason)| (name, file, reason)) -} - -/// Did the Hunspell dictionary of `lang` load? `None` for languages that use none (English). -fn dictionary_loaded(lang: ProofLang) -> Option { - language(lang).map(|l| l.slot.has_dictionary()) -} - -/// The Hunspell name `lang` needs when `installed` is false (the first name its module looks for; -/// a Spanish variety's is its own: `es_ES`, `es_MX`, `es_419`, `es_UY`). -fn dictionary_needed(lang: ProofLang, installed: bool) -> Option<&'static str> { - language(lang).filter(|_| !installed).map(|l| l.slot.name()) -} - -/// Is `word` spelled correctly in the run's proofing language? A language without a dictionary -/// can't be checked, so every word counts as correct (no red underlines for it). -pub fn is_correct_for(word: &str, lang: Option<&str>) -> bool { - is_correct_in(word, ProofLang::from_tag(lang)) -} - -/// Is `word` spelled correctly? Numbers, single letters, ALL-CAPS acronyms, URLs and words with -/// digits are accepted. -pub fn is_correct(word: &str) -> bool { - is_correct_with(word, &Options::default()) -} - -/// The words every language's check accepts under `opts`: single letters, words without -/// letters, words with digits (Ignore words that contain numbers), words in capitals -/// (Ignore words in UPPERCASE) and words in the user dictionary. -fn accepted_by_options(word: &str, opts: &Options) -> bool { - let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '\'').trim_matches('\''); - w.chars().count() <= 1 - || !w.chars().any(char::is_alphabetic) - || (opts.ignore_numbers && w.chars().any(|c| c.is_ascii_digit())) - || (opts.ignore_uppercase && w.chars().all(|c| !c.is_lowercase())) - // The whole word: the languages split a hyphenated word into its parts, and an added - // `Unigranrio-RJ` is none of them (#2082). - || in_user_dictionary(&w.to_lowercase()) -} - -/// [`is_correct`] with the File › Options › Proofing choices. -pub fn is_correct_with(word: &str, opts: &Options) -> bool { - let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '\''); - let w = w.trim_matches('\''); - if accepted_by_options(w, opts) { - return true; - } - if !w.is_ascii() && !w.chars().any(|c| c.is_ascii_alphabetic()) { - return true; // other scripts: no dictionary - } - let lower = w.to_lowercase().replace('’', "'"); - if known_core(&lower) { - return true; - } - // Hyphenated compounds: each part. - if lower.contains('-') { - return lower.split('-').all(|p| p.is_empty() || known_core(p)); - } - false -} - -/// Spelling suggestions for a misspelled word (best first, at most `max`). -pub fn suggest(word: &str, max: usize) -> Vec { - let lower = word.to_lowercase(); - let n = lower.chars().count(); - if n == 0 || n > 40 { - return Vec::new(); - } - let d = dict::Dictionary::en_us(); - let mut scored: Vec<(usize, i64, String)> = Vec::new(); - let target: Vec = lower.chars().collect(); - for w in d.iter().map(|(w, _)| w).chain(extra_words().iter().copied()) { - let wn = w.chars().count(); - if wn + 2 < n || wn > n + 2 { - continue; - } - let dist = damerau(&target, w); - if dist <= 2 { - let same_first = w.chars().next() == target.first().copied(); - scored.push((dist, if same_first { 0 } else { 1 }, w.to_string())); - } - } - scored.sort_by(|a, b| (a.0, a.1, a.2.len().abs_diff(n)).cmp(&(b.0, b.1, b.2.len().abs_diff(n))).then(a.2.cmp(&b.2))); - scored.into_iter().take(max).map(|(_, _, w)| match_case(word, w)).collect() -} - -/// `w` (lower case) in the case of `word`: all capitals, an initial capital, or as it is. -fn match_case(word: &str, w: String) -> String { - let cap = word.chars().next().is_some_and(char::is_uppercase); - let upper = word.chars().all(|c| !c.is_lowercase()); - if upper && word.chars().count() > 1 { - w.to_uppercase() - } else if cap { - let mut c = w.chars(); - c.next().map(|f| f.to_uppercase().chain(c).collect()).unwrap_or(w) - } else { - w - } -} - -/// `lower` in the spelling `lang` (an English) wants, when it or a part of a hyphenated compound -/// is the other dialect's spelling (`colour-blind` → `color-blind` in American English). -fn english_preferred(lower: &str, lang: ProofLang) -> Option { - let mut changed = false; - let parts: Vec = lower - .split('-') - .map(|p| match en_variants::preferred(p, lang) { - Some(n) => { - changed = true; - n - } - None => p.to_string(), - }) - .collect(); - changed.then(|| parts.join("-")) -} - -/// Is `word` spelled right in the English `lang` ([`ProofLang::En`], [`ProofLang::EnGb`], -/// [`ProofLang::EnCa`])? The shared word list's words, less the other dialect's spellings -/// (`colour` in American English, `color` in British), plus this dialect's spellings of the -/// list's words ([`en_variants`]). The user dictionary overrules both. -fn is_correct_english(word: &str, lang: ProofLang, opts: &Options) -> bool { - let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '\'').trim_matches('\''); - if accepted_by_options(w, opts) { - return true; - } - let lower = w.to_lowercase().replace('’', "'"); - if in_user_dictionary(&lower) { - return true; - } - let parts: Vec<&str> = lower.split('-').filter(|p| !p.is_empty()).collect(); - if parts.iter().any(|p| en_variants::is_foreign(p, lang)) { - return false; - } - is_correct_with(word, opts) || (!parts.is_empty() && parts.iter().all(|p| known_core(p) || en_variants::known_variant(p, lang, known_core))) -} - -/// Spelling suggestions in the English `lang`: this dialect's spelling first when `word` is the -/// other dialect's, then the shared suggestions in this dialect's spelling. -fn suggest_english(word: &str, max: usize, lang: ProofLang) -> Vec { - let lower = word.to_lowercase().replace('’', "'"); - let mut out: Vec = Vec::new(); - if let Some(p) = english_preferred(&lower, lang) { - out.push(match_case(word, p)); - } - for s in suggest(word, max.saturating_add(2)) { - let s = english_preferred(&s.to_lowercase(), lang).map_or(s, |p| match_case(word, p)); - if !out.contains(&s) { - out.push(s); - } - } - out.truncate(max); - out -} - -/// Optimal string alignment distance, early-exit at 3. -fn damerau(a: &[char], b: &str) -> usize { - let b: Vec = b.chars().collect(); - let (n, m) = (a.len(), b.len()); - let mut prev2 = vec![0usize; m + 1]; - let mut prev: Vec = (0..=m).collect(); - let mut cur = vec![0usize; m + 1]; - for i in 1..=n { - if let Some(c) = cur.get_mut(0) { - *c = i; - } - let mut row_min = i; - for j in 1..=m { - let (ai, bj) = (a.get(i - 1), b.get(j - 1)); - let cost = usize::from(ai != bj); - let mut v = (prev.get(j).copied().unwrap_or(9) + 1) - .min(cur.get(j - 1).copied().unwrap_or(9) + 1) - .min(prev.get(j - 1).copied().unwrap_or(9) + cost); - if i > 1 && j > 1 && a.get(i - 1) == b.get(j - 2) && a.get(i - 2) == b.get(j - 1) { - v = v.min(prev2.get(j - 2).copied().unwrap_or(9) + 1); - } - if let Some(c) = cur.get_mut(j) { - *c = v; - } - row_min = row_min.min(v); - } - if row_min > 2 { - return 3; - } - std::mem::swap(&mut prev2, &mut prev); - std::mem::swap(&mut prev, &mut cur); - } - prev.get(m).copied().unwrap_or(9) -} - -/// Words in `text`: (byte start, byte end). Combining marks belong to the word they follow, so a -/// word typed decomposed (`tambie\u{301}n`, NFD) is one word, as its precomposed form is. -pub fn words(text: &str) -> Vec<(usize, usize)> { - let mut out = Vec::new(); - let mut start: Option = None; - let is_w = |c: char| c.is_alphanumeric() || c == '\'' || c == '’' || c == '-' || unicode_normalization::char::is_combining_mark(c); - for (i, c) in text.char_indices() { - match (start, is_w(c)) { - (None, true) => start = Some(i), - (Some(s), false) => { - out.push((s, i)); - start = None; - } - _ => {} - } - } - if let Some(s) = start { - out.push((s, text.len())); - } - // Trim surrounding apostrophes/hyphens. - out.into_iter() - .filter_map(|(a, b)| { - let w = text.get(a..b)?; - let lead = w.len() - w.trim_start_matches(['\'', '’', '-']).len(); - let trail = w.len() - w.trim_end_matches(['\'', '’', '-']).len(); - (a + lead < b - trail).then_some((a + lead, b - trail)) - }) - .collect() -} - -/// Spelling issues in a paragraph of English text (no suggestions; ask [`suggest`] on demand). -pub fn check_spelling(text: &str) -> Vec { - check_spelling_with(text, &Options::default()) -} - -/// [`check_spelling`] with the File › Options › Proofing choices. -pub fn check_spelling_with(text: &str, opts: &Options) -> Vec { - spelling_issues(text, |_| ProofLang::En, opts) -} - -/// Spelling issues in a paragraph of text in `lang` (none for a language without a dictionary; -/// no suggestions: ask [`suggest_in`] on demand). -pub fn check_spelling_in(text: &str, lang: ProofLang) -> Vec { - if !lang.is_proofed() { - return Vec::new(); - } - spelling_issues(text, |_| lang, &Options::default()) -} - -/// Spelling issues of every word of `text`, each word spelled in `lang(word_start)`. -/// -/// The web/e-mail classification of the whitespace-delimited token around a word is computed -/// once per token and shared by every word punctuation splits out of it. Classifying per word -/// rescanned the token each time, so a long whitespace-free run of punctuation-separated words -/// cost quadratic scanning (#1335). -fn spelling_issues(text: &str, lang: impl Fn(usize) -> ProofLang, opts: &Options) -> Vec { - let mut token = (0, 0, false); - words(text) - .into_iter() - .filter_map(|(a, b)| { - if a < token.0 || a >= token.1 { - token = enclosing_token(text, a, b); - } - spelling_issue(text, a, b, lang(a), opts, token.2) - }) - .collect() -} - -/// The whitespace-delimited token enclosing the word at `a..b` of `text`: its byte range, and -/// whether it looks like a web or e-mail address. -fn enclosing_token(text: &str, a: usize, b: usize) -> (usize, usize, bool) { - // The token starts after the last white-space character, whatever its width (a no-break - // space is two bytes and an ideographic space is three). - let ts = text.get(..a).and_then(|t| t.char_indices().rev().find(|(_, c)| c.is_whitespace())).map(|(i, c)| i + c.len_utf8()).unwrap_or(0); - let te = text.get(b..).and_then(|t| t.find(char::is_whitespace)).map(|i| b + i).unwrap_or(text.len()); - (ts, te, text.get(ts..te).is_some_and(is_address)) -} - -/// Does a whitespace-delimited token look like a web or e-mail address (`https://…`, `www.…`, -/// `a@b.org`)? Spelling skips its words, and Spanish grammar its punctuation. -pub(crate) fn is_address(token: &str) -> bool { - token.contains("://") || token.contains('@') || token.starts_with("www.") -} - -/// The spelling issue of the word at `a..b` of `text`, if it is misspelled in `lang`. Words in -/// web and e-mail addresses are skipped (unless `opts` says otherwise); `internet` is the -/// classification of the word's enclosing token, from [`enclosing_token`]. -fn spelling_issue(text: &str, a: usize, b: usize, lang: ProofLang, opts: &Options, internet: bool) -> Option { - let w = text.get(a..b)?; - // A word with a Vietnamese-only letter (a name like Nguyễn in English text) isn't English. - if (opts.ignore_internet && internet) || (lang.is_english() && vi::is_vietnamese_word(w)) || is_correct_in_with(w, lang, opts) { - return None; - } - let message = language(lang).map_or("Possible spelling mistake", |l| l.message); - Some(Issue { start: a, end: b, kind: IssueKind::Spelling, message: message.into(), suggestions: Vec::new(), lang }) -} - -/// Is `word` spelled correctly in `lang`? Always true in a language without a dictionary. -pub fn is_correct_in(word: &str, lang: ProofLang) -> bool { - is_correct_in_with(word, lang, &Options::default()) -} - -/// `word` composed (NFC), as the dictionaries spell their words: `tambie\u{301}n` is looked up as -/// `también`. Words already composed (all ASCII ones among them) are borrowed as they are. -fn composed(word: &str) -> std::borrow::Cow<'_, str> { - use unicode_normalization::{IsNormalized, UnicodeNormalization, is_nfc_quick}; - if word.is_ascii() || is_nfc_quick(word.chars()) == IsNormalized::Yes { - std::borrow::Cow::Borrowed(word) - } else { - std::borrow::Cow::Owned(word.nfc().collect()) - } -} - -/// [`is_correct_in`] with the File › Options › Proofing choices. A word with combining marks is -/// checked as its composed form ([`composed`]). -pub fn is_correct_in_with(word: &str, lang: ProofLang, opts: &Options) -> bool { - let word = &*composed(word); - if lang.is_english() { - return is_correct_english(word, lang, opts); - } - // A language WordCraft doesn't proof (Bulgarian, German, any other) has no spelling marks. - language(lang).is_none_or(|l| accepted_by_options(word, opts) || (l.is_correct)(word, lang, opts)) -} - -/// Spelling suggestions for a word misspelled in `lang` (best first, at most `max`), composed -/// ([`composed`]) like the dictionaries' words. -pub fn suggest_in(word: &str, max: usize, lang: ProofLang) -> Vec { - let word = &*composed(word); - if lang.is_english() { - return suggest_english(word, max, lang); - } - language(lang).map_or_else(Vec::new, |l| (l.suggest)(word, max, lang)) -} - -/// Grammar issues in a paragraph of text in `lang`: the English rules, the Polish ones -/// ([`pl::grammar`]), the Portuguese ones ([`pt::grammar`]), the Italian ones ([`it::grammar`]), -/// the Turkish ones ([`tr::grammar`]), Czech and Indonesian generic checks ([`cs::grammar`], [`id::grammar`]), the Spanish ones -/// ([`es::grammar`]), or none for a language WordCraft doesn't proof. -pub fn check_grammar_in(text: &str, lang: ProofLang) -> Vec { - grammar_in(text, lang, true) -} - -/// [`check_grammar_in`] where `capital_first` says whether the first word must be capitalized -/// (false for a list item). -fn grammar_in(text: &str, lang: ProofLang, capital_first: bool) -> Vec { - match lang { - ProofLang::En => grammar_en(text, capital_first), - ProofLang::EnGb | ProofLang::EnCa => grammar_en(text, capital_first).into_iter().map(|i| Issue { lang, ..i }).collect(), - _ => language(lang).map_or_else(Vec::new, |l| (l.grammar)(text, lang, capital_first)), - } -} - -/// Spelling and grammar issues in a paragraph whose text is in several languages, in order. -/// `spans` gives the language of byte ranges of `text` (in order, not overlapping); text outside -/// them is English. Each word is spelled in the language of its first letter. Each language's -/// grammar rules read the whole paragraph (so sentence boundaries are seen) but report only -/// inside that language's spans: an English quotation in a Polish paragraph gets English -/// spelling and is never told a Polish rule. -pub fn check_text(text: &str, spans: &[(Range, ProofLang)]) -> Vec { - check_text_with(text, spans, &Options::default(), true, false) -} - -/// [`check_text`] with the File › Options › Proofing choices: what spelling skips, and whether -/// grammar is checked at all. -/// -/// `list_item` marks a list paragraph: items are often fragments, so the English rule asking for -/// a capital on the paragraph's first word is off for it (later sentences are still checked). -pub fn check_text_with(text: &str, spans: &[(Range, ProofLang)], opts: &Options, grammar: bool, list_item: bool) -> Vec { - // Sorted, empty ones dropped, neighbours of one language merged. - let mut sorted: Vec<&(Range, ProofLang)> = spans.iter().filter(|(r, _)| r.start < r.end).collect(); - sorted.sort_by_key(|s| s.0.start); - let mut merged: Vec<(Range, ProofLang)> = Vec::new(); - for (r, l) in sorted { - match merged.last_mut() { - Some((m, ml)) if *ml == *l && m.end == r.start => m.end = r.end, - _ => merged.push((r.clone(), *l)), - } - } - // A paragraph typed in Vietnamese under the default English language is Vietnamese, which - // has no proofing tools: no marks (Word detects the language the same way). - let default = if vi::looks_vietnamese(text) { - for (_, l) in merged.iter_mut().filter(|(_, l)| l.is_english()) { - *l = ProofLang::Other; - } - ProofLang::Other - } else { - ProofLang::En - }; - let lang_at = |i: usize| -> ProofLang { - let k = merged.partition_point(|(r, _)| r.end <= i); - merged.get(k).filter(|(r, _)| r.start <= i).map_or(default, |s| s.1) - }; - let mut v: Vec = spelling_issues(text, lang_at, opts); - if !grammar { - v.sort_by_key(|i| (i.start, i.end)); - return v; - } - let mut langs: Vec = merged.iter().map(|s| s.1).collect(); - let mut covered = 0; - for (r, _) in &merged { - if r.start > covered { - break; - } - covered = covered.max(r.end); - } - if covered < text.len() || merged.is_empty() { - langs.push(default); - } - langs.sort(); - langs.dedup(); - for lang in langs { - let found = grammar_in(text, lang, !list_item); - v.extend(found.into_iter().filter(|i| lang_at(i.start) == lang)); - } - v.sort_by_key(|i| (i.start, i.end)); - v -} - -/// Grammar issues in a paragraph of English text. -pub fn check_grammar(text: &str) -> Vec { - grammar_en(text, true) -} - -/// The English rules; `capital_first` is whether the text's first word must start a sentence -/// with a capital (false for a list item, which may be a fragment). -fn grammar_en(text: &str, capital_first: bool) -> Vec { - let mut v = Vec::new(); - let ws = words(text); - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if gap.trim().is_empty() - && w0.eq_ignore_ascii_case(w1) - && w0.chars().all(char::is_alphabetic) - && !matches!(w0.to_lowercase().as_str(), "had" | "that" | "bye" | "so") - { - v.push(Issue { - start: *a0, - end: *b1, - kind: IssueKind::Grammar, - message: format!("Repeated word: \"{w1}\""), - suggestions: vec![w0.to_string()], - lang: ProofLang::En, - }); - } - if gap == " " && (w0 == "a" || w0 == "A") && w1.chars().next().is_some_and(|c| "aeiouAEIOU".contains(c)) && !starts_consonant_sound(w1) { - v.push(Issue { - start: *a0, - end: *b0, - kind: IssueKind::Grammar, - message: "Use \"an\" before a vowel sound".into(), - suggestions: vec![if w0 == "A" { "An" } else { "an" }.into()], - lang: ProofLang::En, - }); - } - if gap == " " - && (w0 == "an" || w0 == "An") - && w1.chars().next().is_some_and(|c| c.is_alphabetic() && !"aeiouAEIOUhH".contains(c)) - && !w1.chars().all(char::is_uppercase) - { - v.push(Issue { - start: *a0, - end: *b0, - kind: IssueKind::Grammar, - message: "Use \"a\" before a consonant sound".into(), - suggestions: vec![if w0 == "An" { "A" } else { "a" }.into()], - lang: ProofLang::En, - }); - } - } - // Space before punctuation; doubled spaces. - for (i, _) in text.match_indices(" ,").chain(text.match_indices(" .")).chain(text.match_indices(" ;")) { - if text.get(i + 2..).is_some_and(|r| r.starts_with('.') || r.starts_with(',')) { - continue; // "..." or numbers like " .5" - } - v.push(Issue { - start: i, - end: i + 2, - kind: IssueKind::Grammar, - message: "Remove the space before the punctuation".into(), - suggestions: vec![text.get(i + 1..i + 2).unwrap_or("").to_string()], - lang: ProofLang::En, - }); - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(Issue { - start: i, - end: i + 2, - kind: IssueKind::Grammar, - message: "Extra space".into(), - suggestions: vec![" ".into()], - lang: ProofLang::En, - }); - } - // Sentence start capital. - let mut after_end = capital_first; - for (a, b) in &ws { - let Some(w) = text.get(*a..*b) else { continue }; - if after_end && w.chars().next().is_some_and(char::is_lowercase) && w.chars().all(char::is_alphabetic) { - let fixed: String = w.chars().next().map(|f| f.to_uppercase().chain(w.chars().skip(1)).collect()).unwrap_or_default(); - v.push(Issue { - start: *a, - end: *b, - kind: IssueKind::Grammar, - message: "Capitalize the first word of a sentence".into(), - suggestions: vec![fixed], - lang: ProofLang::En, - }); - } - let tail = text.get(*b..).unwrap_or(""); - let punctuation = tail.trim_start_matches(['"', '”', ')', '\'']); - // Two or more dots are an ellipsis, not a full stop ending a sentence. - // A Unicode ellipsis (U+2026) does not match sentence-ending punctuation either. - after_end = matches!(punctuation.chars().next(), Some('.' | '!' | '?')) - && !punctuation.starts_with("..") - && punctuation.trim_start_matches(['.', '!', '?', '"', '”', ')']).starts_with(' '); - } - v.sort_by_key(|i| i.start); - v -} - -fn starts_consonant_sound(w: &str) -> bool { - let l = w.to_lowercase(); - ["uni", "use", "usu", "eu", "one", "once", "ubiq", "uti", "ure", "uro"].iter().any(|p| l.starts_with(p)) -} - -#[cfg(test)] -mod tests { - #[test] - fn missing_dictionary_is_named_only_for_hunspell_languages_without_one() { - assert_eq!(dictionary_needed(ProofLang::Pl, false), Some("pl_PL")); - assert_eq!(dictionary_needed(ProofLang::Pt, false), Some("pt_BR")); - assert_eq!(dictionary_needed(ProofLang::Pl, true), None); - assert_eq!(dictionary_needed(ProofLang::Pt, true), None); - // Every Hunspell language is named by the first name its module looks for (#3150). - for (l, name) in [ - (ProofLang::It, "it_IT"), - (ProofLang::Hr, "hr_HR"), - (ProofLang::Tr, "tr_TR"), - (ProofLang::Ru, "ru_RU"), - (ProofLang::Cs, "cs_CZ"), - (ProofLang::Id, "id_ID"), - // #3360: each Spanish variety names its own. - (ProofLang::Es, "es_ES"), - (ProofLang::EsMx, "es_MX"), - (ProofLang::Es419, "es_419"), - (ProofLang::EsUy, "es_UY"), - ] { - assert_eq!(dictionary_needed(l, false), Some(name), "{l:?}"); - assert_eq!(dictionary_needed(l, true), None, "{l:?}"); - } - // Every proofed language other than English has a Hunspell dictionary to report. - for l in ProofLang::PROOFED { - let english = matches!(l, ProofLang::En | ProofLang::EnGb | ProofLang::EnCa); - assert_eq!(dictionary_needed(l, false).is_some(), !english, "{l:?}"); - } - for l in [ProofLang::En, ProofLang::Bg, ProofLang::De, ProofLang::Other] { - assert_eq!(dictionary_needed(l, false), None); - } - // Installed dictionaries and the built-in English list: no hint. - pl::test_dict::install(); - pt::test_dict::install(); - es::test_dict::install(); - for tag in [Some("pl-PL"), Some("pt-BR"), Some("es-ES"), Some("es-MX"), Some("es-419"), Some("es-UY"), Some("en-US"), Some("fr-FR"), None] { - assert_eq!(missing_dictionary(tag), None, "{tag:?}"); - } - } - - use super::*; - - /// Held by tests that swap or rely on the shared Spanish dictionary, so a folder reset can't land - /// mid-test, and by those that save the whole user dictionary and load it back, which would - /// bring back a word another test just removed. - static DICTIONARY_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(()); - - /// #1063: the dictionaries folder is searched before the system locations; the dictionaries - /// in it are reported and used, and cached checks are dropped. The folder holds the same test - /// dictionaries the other tests install, so tests running meanwhile see the same words; it - /// stays set for the rest of the run for that reason. - #[test] - fn dictionaries_folder_is_searched_first() { - let _guard = DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner); - let dir = std::env::temp_dir().join(format!("wc-proof-dictionaries-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - for (name, aff, dic) in [ - ("pl_PL", pl::test_dict::AFF, pl::test_dict::DIC), - ("pt_BR", pt::test_dict::AFF, pt::test_dict::DIC), - ("it_IT", it::test_dict::AFF, it::test_dict::DIC), - ("hr_HR", hr::test_dict::AFF, hr::test_dict::DIC), - ("tr_TR", tr::test_dict::AFF, tr::test_dict::DIC), - ("ru_RU", ru::test_dict::AFF, ru::test_dict::DIC), - ("cs_CZ", cs::test_dict::AFF, cs::test_dict::DIC), - ("id_ID", id::test_dict::AFF, id::test_dict::DIC), - ("es_ES", es::test_dict::AFF, es::test_dict::DIC), - ("es_MX", es::test_dict::AFF, es::test_dict::DIC_AMERICAS), - ("es_419", es::test_dict::AFF, es::test_dict::DIC_AMERICAS), - ("es_UY", es::test_dict::AFF, es::test_dict::DIC_AMERICAS), - ] { - std::fs::write(dir.join(format!("{name}.aff")), aff).unwrap(); - std::fs::write(dir.join(format!("{name}.dic")), dic).unwrap(); - } - // #3252 review: force Russian and Czech to cache whatever they find before the folder is - // set (empty, with no folder and nothing installed on CI), so the folder change below has - // a stale cache to actually invalidate, not just an untouched `None`. - ru::dictionary(); - cs::dictionary(); - let before = dictionary_generation(); - set_dictionaries_folder(Some(dir.clone())); - assert!(dictionary_generation() > before, "cached checks are dropped"); - // Every language is listed, Russian and Czech too (#3226: they were once missing, so - // neither showed up in File › Options › Proofing). A language reports the folder's file, - // or none while a dictionary given with install_dictionary() is in use (#3252 review); - // the languages' own tests install theirs concurrently, so an exact path would race them. - // The folder is searched first whatever is in use. - let found = installed_dictionaries(); - let names = ["pl_PL", "pt_BR", "it_IT", "hr_HR", "tr_TR", "ru_RU", "cs_CZ", "id_ID", "es_ES", "es_MX", "es_419", "es_UY"]; - assert_eq!( - found.iter().map(|(lang, _)| *lang).collect::>(), - [ - ProofLang::Pl, - ProofLang::Pt, - ProofLang::It, - ProofLang::Hr, - ProofLang::Tr, - ProofLang::Ru, - ProofLang::Cs, - ProofLang::Id, - ProofLang::Es, - ProofLang::EsMx, - ProofLang::Es419, - ProofLang::EsUy - ] - ); - for ((lang, path), name) in found.iter().zip(names) { - let file = dir.join(format!("{name}.dic")); - assert!(path.is_none() || path.as_ref() == Some(&file), "{lang:?}: {path:?}"); - assert_eq!(hunspell::find(&[name]).map(|(_, dic)| dic), Some(file), "{lang:?}"); - } - assert!(is_correct_in("chrząszcz", ProofLang::Pl) && !is_correct_in("chrzonszcz", ProofLang::Pl)); - assert!(is_correct_in("tenés", ProofLang::EsUy) && !is_correct_in("prueva", ProofLang::EsUy)); - assert!(is_correct_in("дом", ProofLang::Ru) && !is_correct_in("дум", ProofLang::Ru)); - } - - /// A dictionary that fails to load says why in File › Options › Proofing in every language - /// that reads one; Russian and Czech were missing from the list that named their dictionaries - /// there. - #[test] - fn every_dictionary_language_reports_its_load_error() { - let _guard = DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner); - for (lang, _) in installed_dictionaries() { - let name = dictionary_needed(lang, false).unwrap(); - let reason = format!("zq unreadable {name}"); - let _ = hunspell::note_load::<()>(name, std::path::Path::new("zq.dic"), Err(reason.clone())); - assert_eq!(dictionary_load_error(lang), Some(reason), "{lang:?}"); - hunspell::clear_load_error(name); - } - } - - /// Every proofed language other than English has a row in the language table, and only those. - #[test] - fn the_language_table_covers_the_proofed_languages() { - for l in ProofLang::PROOFED { - assert_eq!(language(l).is_some(), !l.is_english(), "{l:?}"); - } - for (i, row) in LANGUAGES.iter().enumerate() { - assert!(row.lang.is_proofed(), "{:?}", row.lang); - assert!(LANGUAGES.iter().skip(i + 1).all(|other| other.lang != row.lang), "{:?} twice", row.lang); - } - } - - /// #1741: a list item may be a fragment, so its first word needs no capital in any language - /// with the rule; a plain paragraph's does, and later sentences in the item are still checked. - #[test] - fn list_item_first_word_needs_no_capital() { - let grammar = |t: &str, lang: ProofLang, list: bool| -> Vec { - check_text_with(t, &[(0..t.len(), lang)], &Options::default(), true, list) - .into_iter() - .filter(|i| i.kind == IssueKind::Grammar) - .filter_map(|i| t.get(i.start..i.end).map(str::to_string)) - .collect() - }; - for (lang, text) in [ - (ProofLang::En, "item one"), - (ProofLang::Pl, "jabłka i gruszki"), - (ProofLang::Hr, "jabuke i kruške"), - (ProofLang::It, "mele e pere"), - (ProofLang::Tr, "elma ve armut"), - (ProofLang::Pt, "maçãs e peras"), - (ProofLang::Ru, "яблоки и груши"), - ] { - let first = text.split(' ').next().unwrap(); - assert!(grammar(text, lang, true).is_empty(), "{lang:?} list item: {:?}", grammar(text, lang, true)); - assert_eq!(grammar(text, lang, false), [first], "{lang:?} plain paragraph"); - } - assert_eq!(grammar("hello world.", ProofLang::En, false), ["hello"]); - // Later sentences in a list item are still checked. - assert_eq!(grammar("item one. then two", ProofLang::En, true), ["then"]); - assert_eq!(grammar("elma ve armut. sonra kiraz", ProofLang::Tr, true), ["sonra"]); - } - - #[test] - fn dictionary_loads() { - assert!(dict::Dictionary::en_us().len() > 100_000); - } - - #[test] - fn spelling() { - for w in [ - "if", - "we", - "hello", - "world", - "running", - "studios", - "don't", - "Thursday", - "NASA", - "2026", - "co-operate", - "unhappy", - "colour", - "it's", - "stopped", - ] { - assert!(is_correct(w), "{w}"); - } - for w in ["helo", "wrold", "sentense", "recieve"] { - assert!(!is_correct(w), "{w}"); - } - let s = suggest("recieve", 5); - assert!(s.contains(&"receive".to_string()), "{s:?}"); - assert!(suggest("Wrold", 3).contains(&"World".to_string())); - } - - /// #1694: `-ically` adverbs come from their `-ic` adjective (specific → specifically). - #[test] - fn ically_adverbs_are_known() { - for w in ["specifically", "scientifically", "automatically", "dramatically", "Specifically"] { - assert!(is_correct(w), "{w}"); - } - for w in ["specificaly", "specifcally", "zzzically"] { - assert!(!is_correct(w), "{w}"); - } - } - - /// #1694 review: only `-ic` adjectives take `-ally`; other known words (dictionary or - /// supplement) don't. - #[test] - fn non_ic_words_do_not_take_ally() { - for (base, wrong) in [("desktop", "desktopally"), ("house", "houseally"), ("final", "finalally"), ("green", "greenally")] { - assert!(is_correct(base), "{base}"); - assert!(!is_correct(wrong), "{wrong}"); - } - } - - /// #520: common modern words (and their inflections) are known; misspellings still flagged. - #[test] - fn common_modern_words() { - for w in [ - "desktop", - "Desktops", - "laptop", - "download", - "downloaded", - "uploading", - "email", - "emailing", - "website", - "online", - "offline", - "smartphones", - "login", - "username", - "spreadsheet", - "database", - "screenshots", - "blogging", - "inbox", - "workplace", - "uploader", - ] { - assert!(is_correct(w), "{w}"); - } - for w in ["dekstop", "laptp", "dowload", "spreadshet"] { - assert!(!is_correct(w), "{w}"); - } - assert!(suggest("dekstop", 5).contains(&"desktop".to_string()), "{:?}", suggest("dekstop", 5)); - assert!(check_spelling("My desktop and laptop download email online.").is_empty()); - } - - /// The supplement stays a sorted list of lowercase base forms. - /// #40: British spellings and their inflections are not flagged. - #[test] - fn british_spellings_are_known() { - for w in [ - "judgement", - "defence", - "licence", - "programme", - "catalogue", - "behaviour", - "colour", - "colourful", - "colourless", - "honour", - "honoured", - "honouring", - "honours", - "honourable", - "rigour", - "glamour", - "clamour", - "endeavour", - "paediatric", - "prioritise", - "prioritised", - "prioritising", - "privatise", - "privatised", - "synthesise", - "travelogue", - "manoeuvrable", - ] { - assert!(is_correct(w), "{w}"); - } - let text = "The programme honoured her rigour and prioritised colourful, paediatric care."; - for lang in [ProofLang::EnGb, ProofLang::EnCa] { - let flagged: Vec<&str> = check_spelling_in(text, lang).iter().map(|i| &text[i.start..i.end]).collect(); - let expect: &[&str] = if lang == ProofLang::EnCa { &["prioritised"] } else { &[] }; - assert_eq!(flagged, expect, "{lang:?}"); - } - // American English flags the British spellings (#1281). - let flagged: Vec<&str> = check_spelling(text).iter().map(|i| &text[i.start..i.end]).collect(); - assert_eq!(flagged, ["programme", "honoured", "rigour", "prioritised", "colourful", "paediatric"]); - } - - /// Each English accepts its own spellings and flags the other dialect's, suggesting its own - /// first, as Word does (#1281, #1045). - #[test] - fn english_dialects_flag_each_others_spellings() { - let (us, gb, ca) = (ProofLang::En, ProofLang::EnGb, ProofLang::EnCa); - // (word, right in en-US, right in en-CA, right in en-GB) - let cases = [ - ("color", true, false, false), - ("colour", false, true, true), - ("colours", false, true, true), - ("coloured", false, true, true), - ("Colourful", false, true, true), - ("colour's", false, true, true), - ("honorable", true, false, false), - ("neighbourhood", false, true, true), - ("center", true, false, false), - ("centre", false, true, true), - ("centred", false, true, true), - ("centring", false, true, true), - ("centering", true, false, false), - ("theater", true, false, false), - ("theatre", true, true, true), - ("organize", true, true, true), - ("organise", false, false, true), - ("organisation", false, false, true), - ("organisations", false, false, true), - ("realised", false, false, true), - ("recognising", false, false, true), - ("analyze", true, true, false), - ("analyse", false, true, true), - ("paralysed", false, true, true), - ("defense", true, false, false), - ("defence", false, true, true), - ("licence", false, true, true), - ("license", true, true, true), - ("practise", false, true, true), - ("traveled", true, false, false), - ("travelled", true, true, true), - ("catalog", true, false, false), - ("catalogue", true, true, true), - ("anaemia", false, true, true), - ("anemia", true, true, false), - ("tire", true, true, true), - ("tyre", false, false, true), - ("programme", false, true, true), - ("program", true, true, true), - ("meter", true, true, true), - ("metre", false, true, true), - ("plough", false, true, true), - ("colour-blind", false, true, true), - // Words spelled alike everywhere, and real misspellings, are unaffected. - ("humorous", true, true, true), - ("honorary", true, true, true), - ("tour", true, true, true), - ("four", true, true, true), - ("programmed", true, true, true), - ("colourr", false, false, false), - ("colorr", false, false, false), - ("organisationz", false, false, false), - ]; - for (w, in_us, in_ca, in_gb) in cases { - assert_eq!(is_correct_in(w, us), in_us, "en-US: {w}"); - assert_eq!(is_correct_in(w, ca), in_ca, "en-CA: {w}"); - assert_eq!(is_correct_in(w, gb), in_gb, "en-GB: {w}"); - assert_eq!(is_correct_for(w, Some("en-AU")), in_gb, "en-AU follows British spelling: {w}"); - } - // The dialect's own spelling comes first, in the word's case. - assert_eq!(suggest_in("colour", 5, us).first().map(String::as_str), Some("color")); - assert_eq!(suggest_in("Colours", 5, us).first().map(String::as_str), Some("Colors")); - assert_eq!(suggest_in("color", 5, gb).first().map(String::as_str), Some("colour")); - assert_eq!(suggest_in("organisation", 5, us).first().map(String::as_str), Some("organization")); - assert_eq!(suggest_in("organisation", 5, ca).first().map(String::as_str), Some("organization")); - assert_eq!(suggest_in("analyze", 5, gb).first().map(String::as_str), Some("analyse")); - assert_eq!(suggest_in("CENTER", 5, gb).first().map(String::as_str), Some("CENTRE")); - assert_eq!(suggest_in("colour's", 5, us).first().map(String::as_str), Some("color's")); - assert_eq!(suggest_in("colour-blind", 5, us).first().map(String::as_str), Some("color-blind")); - // Shared suggestions come in the dialect's spelling, never the other one's. - let gb_sugg = suggest_in("colouur", 8, gb); - assert!(gb_sugg.contains(&"colour".to_string()) && !gb_sugg.contains(&"color".to_string()), "{gb_sugg:?}"); - let us_sugg = suggest_in("colouur", 8, us); - assert!(us_sugg.contains(&"color".to_string()) && !us_sugg.contains(&"colour".to_string()), "{us_sugg:?}"); - // A word in the user dictionary is right in every English. - add_word("zzcolourzz"); - assert!(is_correct_in("zzcolourzz", us)); - // Issues carry the dialect, so suggestions on demand come from the same one. - let issues = check_text("The color is nice.", &[(0..18, gb)]); - assert_eq!(issues.len(), 1, "{issues:?}"); - assert_eq!((issues[0].lang, &"The color is nice."[issues[0].start..issues[0].end]), (gb, "color")); - let grammar = check_text("the colour is nice.", &[(0..19, gb)]); - assert!(grammar.iter().any(|i| i.kind == IssueKind::Grammar && i.lang == gb), "{grammar:?}"); - } - - /// The words #40 (folded into #1281) reported as flagged in British English, with their - /// inflections: right in en-GB, and in en-US flagged or not as `en-variants.txt` decides. - #[test] - fn british_words_reported_in_issue_40_are_right_in_en_gb() { - let (us, gb, ca) = (ProofLang::En, ProofLang::EnGb, ProofLang::EnCa); - // (word, right in en-US, right in en-CA, right in en-GB) - let cases = [ - // Both spellings are right everywhere: `judgment` is also British legal usage, and - // American dictionaries list `judgement` as a variant. - ("judgement", true, true, true), - ("judgements", true, true, true), - ("judgment", true, true, true), - ("judgments", true, true, true), - ("defence", false, true, true), - ("defences", false, true, true), - ("defense", true, false, false), - // `license` is the British verb, so only American English flags `licence`. - ("licence", false, true, true), - ("licences", false, true, true), - ("license", true, true, true), - ("licensed", true, true, true), - ("licenses", true, true, true), - // `program` is British for computing. - ("programme", false, true, true), - ("programmes", false, true, true), - ("program", true, true, true), - ("programs", true, true, true), - // American English accepts both. - ("catalogue", true, true, true), - ("catalogues", true, true, true), - ("catalogued", true, true, true), - ("cataloguing", true, true, true), - ("catalog", true, false, false), - ("catalogs", true, false, false), - ]; - for (w, in_us, in_ca, in_gb) in cases { - assert_eq!(is_correct_in(w, us), in_us, "en-US: {w}"); - assert_eq!(is_correct_in(w, ca), in_ca, "en-CA: {w}"); - assert_eq!(is_correct_in(w, gb), in_gb, "en-GB: {w}"); - } - let text = "The judgement on the defence licence put the programme in the catalogue."; - assert!(check_spelling_in(text, gb).is_empty(), "{:?}", check_spelling_in(text, gb)); - assert_eq!(suggest_in("licences", 5, us).first().map(String::as_str), Some("licenses")); - assert_eq!(suggest_in("programmes", 5, us).first().map(String::as_str), Some("programs")); - } - - /// More British spellings that must stay right in en-GB and en-AU (#1281): -re, single l, ae, - /// with their inflections. - #[test] - fn more_british_spellings_are_right_in_en_gb() { - let gb = ProofLang::EnGb; - assert_eq!(ProofLang::from_tag(Some("en-AU")), gb); - for w in [ - "goitre", - "goitres", - "reconnoitre", - "sceptre", - "sceptres", - "enrol", - "enrols", - "enrolled", - "enrolment", - "fulfil", - "fulfils", - "fulfilled", - "fulfilment", - "instalment", - "instalments", - "skilful", - "skilfully", - "wilful", - "wilfully", - "encyclopaedia", - "encyclopaedias", - ] { - assert!(is_correct_in(w, gb), "en-GB: {w}"); - } - let text = "The wilful knight took the sceptre to fulfil his enrolment, paid the instalment and read the encyclopaedia."; - assert!(check_spelling_in(text, gb).is_empty(), "{:?}", check_spelling_in(text, gb)); - } - - #[test] - fn user_dictionary_accepts_the_other_dialects_spelling() { - add_word("savour"); - assert!(is_correct_in("savour", ProofLang::En), "a word the user added is never flagged"); - assert!(!is_correct_in("savor", ProofLang::EnGb)); - remove_word("savour"); - assert!(!is_correct_in("savour", ProofLang::En)); - } - - #[test] - fn supplement_is_sorted_lowercase() { - let words: Vec<&str> = EXTRA_EN.lines().filter(|l| !l.is_empty() && !l.starts_with('#')).collect(); - assert!(words.len() > 100); - assert!(words.windows(2).all(|p| p[0] < p[1]), "not sorted / duplicate"); - assert!(words.iter().all(|w| w.chars().all(|c| c.is_lowercase() || c == '\'' || c == '-')), "not lowercase"); - } - - #[test] - fn spelling_issues_in_text() { - let t = "This sentense has a mispelled word, see https://example.com/xyzq."; - let v = check_spelling(t); - let bad: Vec<&str> = v.iter().map(|i| &t[i.start..i.end]).collect(); - assert_eq!(bad, vec!["sentense", "mispelled"]); - } - - #[test] - fn address_token_covers_every_word_split_out_of_it() { - // #1335: one whitespace-delimited token, however punctuation splits it, has one - // web/e-mail classification, shared by all its words. - let t = "zxq@mail.example.com,blorp;qwirpy wugwug"; - let bad: Vec<&str> = check_spelling(t).iter().map(|i| &t[i.start..i.end]).collect(); - assert_eq!(bad, vec!["wugwug"]); - let opts = Options { ignore_internet: false, ..Options::default() }; - let bad: Vec<&str> = check_spelling_with(t, &opts).iter().map(|i| &t[i.start..i.end]).collect(); - assert_eq!(bad.first(), Some(&"zxq"), "{bad:?}"); - assert!(bad.contains(&"qwirpy"), "{bad:?}"); - assert_eq!(bad.last(), Some(&"wugwug"), "{bad:?}"); - } - - #[test] - fn address_after_a_wide_space_is_an_internet_token() { - // A web or e-mail address after a no-break space, a narrow no-break space, a figure space - // or an ideographic space is skipped, as it is after a plain space. - let t = "see\u{a0}https://xyzq.example/blorp and\u{3000}www.qwirpy.example or\u{a0}zxq@mail.example \ - at\u{202f}https://frobq.example/zibb by\u{2007}snorf@mail.example wugwug"; - let bad: Vec<&str> = check_spelling(t).iter().map(|i| &t[i.start..i.end]).collect(); - assert_eq!(bad, vec!["wugwug"]); - let opts = Options { ignore_internet: false, ..Options::default() }; - let bad: Vec<&str> = check_spelling_with(t, &opts).iter().map(|i| &t[i.start..i.end]).collect(); - assert!(["xyzq", "blorp", "qwirpy", "zxq", "frobq", "zibb", "snorf", "wugwug"].iter().all(|w| bad.contains(w)), "{bad:?}"); - } - - #[test] - fn spelling_scales_over_one_long_token() { - // #1335: thousands of punctuation-separated words inside a single - // whitespace-free token: one classification for the token, one issue per - // misspelled word, through every public spelling entry point. - let word = |i: usize| { - let mut w = String::from("zxq"); - let mut n = i; - loop { - w.push((b'a' + (n % 26) as u8) as char); - n /= 26; - if n == 0 { - break; - } - } - w - }; - let t = (0..2000).map(word).collect::>().join(","); - assert_eq!(check_spelling(&t).len(), 2000); - assert_eq!(check_spelling_in(&t, ProofLang::En).len(), 2000); - let opts = Options { ignore_internet: false, ..Options::default() }; - assert_eq!(check_spelling_with(&t, &opts).len(), 2000); - assert_eq!(check_text(&t, &[]).into_iter().filter(|i| i.kind == IssueKind::Spelling).count(), 2000); - } - - #[test] - fn grammar_issues() { - let t = "this is is a apple , and an car. Done"; - let v = check_grammar(t); - let msgs: Vec<&str> = v.iter().map(|i| i.message.as_str()).collect(); - assert!(msgs.iter().any(|m| m.contains("Repeated")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("\"an\"")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("\"a\" before")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("space before")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("Capitalize")), "{msgs:?}"); - assert!(msgs.iter().any(|m| m.contains("Extra space")), "{msgs:?}"); - assert!(check_grammar("A university is an hour away.").is_empty(), "{:?}", check_grammar("A university is an hour away.")); - } - - #[test] - fn ellipses_do_not_start_sentences() { - for text in [ - "I saw... a figure right outside.", - "I saw… a figure right outside.", - "I saw.... a figure right outside.", - "I saw... \"a figure\" right outside.", - "I saw… “a figure” right outside.", - ] { - let capitals: Vec<_> = - check_grammar(text).into_iter().filter(|issue| issue.message == "Capitalize the first word of a sentence").collect(); - assert!(capitals.is_empty(), "{text}: {capitals:?}"); - } - - for text in ["I saw. a figure.", "I saw! a figure.", "I saw? a figure.", "I saw.\" a figure."] { - let capitals: Vec<_> = - check_grammar(text).into_iter().filter(|issue| issue.message == "Capitalize the first word of a sentence").collect(); - assert_eq!(capitals.len(), 1, "{text}: {capitals:?}"); - assert_eq!(capitals[0].suggestions, ["A"], "{text}"); - } - } - - #[test] - fn user_dictionary_works() { - assert!(!is_correct("zxqwordcrafty")); - add_word("zxqwordcrafty"); - assert!(is_correct("zxqwordcrafty")); - } - - /// #40: the user dictionary survives a save and a fresh load. - #[test] - fn user_dictionary_persists_through_a_file() { - let _guard = DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner); - let dir = std::env::temp_dir().join(format!("wc-dict-{}-{}", std::process::id(), "persist")); - let _ = std::fs::remove_dir_all(&dir); - // The folder is created on the first save. - let path = dir.join("sub/custom-dictionary.txt"); - add_word("zxqpersistone"); - add_word("zxqpersisttwo"); - save_user_dictionary(&path, &[]).expect("save works"); - let saved = std::fs::read_to_string(&path).unwrap(); - assert!(saved.lines().any(|w| w == "zxqpersistone") && saved.lines().any(|w| w == "zxqpersisttwo"), "{saved}"); - // Written atomically: no temporary file is left beside it. - let names: Vec<_> = std::fs::read_dir(dir.join("sub")).unwrap().map(|e| e.unwrap().file_name()).collect(); - assert_eq!(names, ["custom-dictionary.txt"]); - remove_word("zxqpersistone"); - assert!(!is_correct("zxqpersistone")); - load_user_dictionary(&path); - assert!(is_correct("zxqpersistone"), "loading brings the word back"); - let _ = std::fs::remove_dir_all(&dir); - } - - /// #40: a hostile file is capped: invalid lines are skipped, and only the first megabyte is read. - #[test] - fn hostile_dictionary_files_are_capped() { - let dir = std::env::temp_dir().join(format!("wc-dict-{}-{}", std::process::id(), "hostile")); - let _ = std::fs::create_dir_all(&dir); - let path = dir.join("custom-dictionary.txt"); - let mut hostile = vec![0xff, 0xfe, b'\n']; - hostile.extend(b"zxqhostileok\n zxqhostiletrim \nzxq hostile space\nzxq\x07bell\n"); - hostile.extend("zxq".repeat(30).bytes()); // 90 characters: too long - hostile.push(b'\n'); - // Pad past the 1 MB cap with blank lines; the word after it is never read. - hostile.resize(1_000_100, b'\n'); - hostile.extend(b"zxqhostilepastcap\n"); - std::fs::write(&path, &hostile).unwrap(); - let added = load_user_dictionary(&path); - assert!(added.contains(&"zxqhostileok".to_string()) && added.contains(&"zxqhostiletrim".to_string()), "{added:?}"); - assert!(!added.iter().any(|w| w.contains("space") || w.contains("bell") || w.chars().count() > MAX_CUSTOM_WORD_CHARS), "{added:?}"); - assert!(!is_correct("zxqhostilepastcap"), "the file is read only up to the cap"); - assert_eq!(read_dictionary_file(&path).len(), 2); - let _ = std::fs::remove_dir_all(&dir); - } - - /// #40: saving merges in words another process saved, and leaves out the ones just removed. - #[test] - fn saving_merges_with_the_file() { - let dir = std::env::temp_dir().join(format!("wc-dict-{}-{}", std::process::id(), "merge")); - let _ = std::fs::create_dir_all(&dir); - let path = dir.join("custom-dictionary.txt"); - std::fs::write(&path, "zxqmergeother\nzxqmergegone\n").unwrap(); - add_word("zxqmergemine"); - save_user_dictionary(&path, &["zxqmergegone".to_string()]).unwrap(); - let saved = std::fs::read_to_string(&path).unwrap(); - assert!(saved.lines().any(|w| w == "zxqmergeother"), "the other process's word is kept: {saved}"); - assert!(saved.lines().any(|w| w == "zxqmergemine"), "{saved}"); - assert!(!saved.lines().any(|w| w == "zxqmergegone"), "a removed word stays out: {saved}"); - assert!(is_correct("zxqmergeother") && !is_correct("zxqmergegone")); - let _ = std::fs::remove_dir_all(&dir); - } - - /// Ignore All lasts for the session: the word is accepted but isn't in the dictionary. - #[test] - fn ignored_words_are_not_dictionary_words() { - assert!(!is_correct("zxqignoredword")); - ignore_word("ZxqIgnoredWord"); - assert!(is_correct("zxqignoredword")); - assert!(!user_dictionary().contains(&"zxqignoredword".to_string())); - } - - #[test] - fn custom_words_are_single_short_words() { - assert_eq!(custom_word(" Hello "), Some("hello".into())); - for bad in ["", " ", "two words", "tab\tbed", "bell\u{7}", "bad\u{FFFD}", &"x".repeat(MAX_CUSTOM_WORD_CHARS + 1)] { - assert_eq!(custom_word(bad), None, "{bad:?}"); - } - assert!(custom_word(&"é".repeat(MAX_CUSTOM_WORD_CHARS)).is_some(), "the cap counts characters, not bytes"); - } - - /// #40: a missing file loads as empty, never an error. - #[test] - fn missing_dictionary_file_loads_empty() { - let path = std::env::temp_dir().join(format!("wc-dict-{}-missing.txt", std::process::id())); - let _ = std::fs::remove_file(&path); - assert!(load_user_dictionary(&path).is_empty()); - } - - /// #100: runs are proofed when their language has a dictionary (the default language too) - /// and skipped otherwise. - #[test] - fn dictionary_lang_tags() { - for l in [None, Some(""), Some(" "), Some("en"), Some("en-US"), Some("en-GB"), Some("EN-us"), Some("en_US")] { - assert!(has_dictionary(l), "{l:?}"); - } - for l in [Some("lt-LT"), Some("fr-FR"), Some("de"), Some("uk"), Some("-US"), Some("e")] { - assert!(!has_dictionary(l), "{l:?}"); - } - // The gate follows the dictionaries, not English: every proofed language is proofed. - for d in ProofLang::PROOFED.map(ProofLang::code) { - assert!(has_dictionary(Some(d)), "{d}"); - assert!(has_dictionary(Some(&format!("{}-XX", d.to_ascii_uppercase()))), "{d}"); - } - } - - /// Combining marks stay in their word (#3562 review): a decomposed word is one word, with the - /// byte range of what was typed, and it is checked as its composed form in every language. - #[test] - fn decomposed_words_stay_whole() { - let text = "tambie\u{301}n, cafe\u{301} \u{301}x"; - let ws: Vec<&str> = words(text).into_iter().map(|(a, b)| &text[a..b]).collect(); - assert_eq!(ws, ["tambie\u{301}n", "cafe\u{301}", "\u{301}x"]); - assert_eq!(words("tambie\u{301}n"), [(0, 9)]); - assert_eq!(&*composed("tambie\u{301}n"), "también"); - assert!(matches!(composed("también"), std::borrow::Cow::Borrowed(_)) && matches!(composed("plain"), std::borrow::Cow::Borrowed(_))); - pt::test_dict::install(); - assert!(is_correct_in("corac\u{327}a\u{303}o", ProofLang::Pt), "Portuguese gets the same composition"); - } - - /// #3562 review: Add to Dictionary, Ignore All, removal and the dictionary file use the same - /// composed key as spelling, so a word added or ignored decomposed is accepted in both forms, - /// an NFD entry in a saved file loads (and saves) composed, and either form removes it. - #[test] - fn user_dictionary_and_ignore_all_match_both_normalization_forms() { - let _guard = DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner); - es::test_dict::install(); - let es = ProofLang::Es; - let (nfd, nfc) = ("zqprue\u{301}ba", "zqpruéba"); - let text = format!("esto {nfd}"); - let issues = check_text(&text, &[(0..text.len(), es)]); - assert_eq!(issues.iter().map(|i| (i.start, i.end)).collect::>(), [(5, text.len())], "flagged at the typed bytes"); - assert!(!is_correct_in(nfc, es)); - add_word(nfd); - assert!(is_correct_in(nfd, es) && is_correct_in(nfc, es)); - assert!(check_text(&text, &[(0..text.len(), es)]).is_empty()); - assert!(user_dictionary().contains(&nfc.to_string()) && !user_dictionary().contains(&nfd.to_string())); - assert!(remove_word(nfc), "the composed form removes the entry added decomposed"); - assert!(!is_correct_in(nfd, es) && !is_correct_in(nfc, es)); - // Ignore All, either way round. - ignore_word("zqigno\u{301}ro"); - assert!(is_correct_in("zqignóro", es) && is_correct_in("zqigno\u{301}ro", es)); - ignore_word("zqcallé"); - assert!(is_correct_in("zqcalle\u{301}", es)); - // A dictionary file with a decomposed entry loads composed and is saved composed. - let path = std::env::temp_dir().join(format!("wc-nfd-dictionary-{}.txt", std::process::id())); - std::fs::write(&path, "zqguarda\u{301}do\nzqotra\n").unwrap(); - let added = load_user_dictionary(&path); - assert!(added.contains(&"zqguardádo".to_string()) && !added.iter().any(|w| w.contains('\u{301}')), "{added:?}"); - assert!(is_correct_in("zqguardádo", es) && is_correct_in("zqguarda\u{301}do", es)); - save_user_dictionary(&path, &["zqotra".to_string()]).unwrap(); - let saved = std::fs::read_to_string(&path).unwrap(); - assert!(saved.lines().any(|l| l == "zqguardádo") && !saved.contains('\u{301}') && !saved.lines().any(|l| l == "zqotra"), "{saved}"); - // Removing the decomposed form takes out the composed entry, and the file follows. - assert!(remove_word("zqguarda\u{301}do")); - save_user_dictionary(&path, &["zqguarda\u{301}do".to_string()]).unwrap(); - assert!(!std::fs::read_to_string(&path).unwrap().lines().any(|l| l == "zqguardádo")); - assert!(!is_correct_in("zqguardádo", es)); - let _ = std::fs::remove_file(&path); - } - - /// #100: runs in a language without a dictionary are never flagged; English still is. - #[test] - fn non_english_words_are_not_flagged() { - for w in ["ąžuolas", "élève", "über", "labas", "bonjour"] { - assert!(is_correct_for(w, Some("lt-LT")), "{w}"); - assert!(is_correct_for(w, Some("fr-FR")), "{w}"); - } - assert!(!is_correct_for("helo", Some("en-US")), "English typos still flagged"); - assert!(!is_correct_for("helo", None), "default language still proofed"); - assert!(is_correct_for("hello", Some("en-US"))); - } - - /// File › Options › Proofing (#485): each "Ignore" choice turned off makes those words count. - #[test] - fn ignore_options_decide_what_is_checked() { - let t = "SENTENSE helo2 www.exampel.org"; - let bad = |o: Options| check_spelling_with(t, &o).iter().map(|i| t[i.start..i.end].to_string()).collect::>(); - assert!(bad(Options::default()).is_empty(), "Word's defaults skip all three"); - let all = Options { ignore_uppercase: false, ignore_numbers: false, ignore_internet: false }; - assert_eq!(bad(all), ["SENTENSE", "helo2", "www", "exampel"]); - assert_eq!(bad(Options { ignore_uppercase: false, ..Options::default() }), ["SENTENSE"]); - assert!(is_correct_with("NASA", &all), "capitals that are a word stay correct"); - } - - #[test] - fn hyphenation() { - let l = hyphen::Limits::default(); - assert!(hyphen::hyphenate_word("hyphenation", &l).matches('-').count() >= 2); - } - - fn flagged(t: &str, v: &[Issue]) -> Vec { - v.iter().filter(|i| i.kind == IssueKind::Spelling).map(|i| t[i.start..i.end].to_string()).collect() - } - - #[test] - fn each_language_uses_its_own_tools() { - pl::test_dict::install(); - let pl = "Zażółć gęślą jaźń, chrząszcz brzmi w trzcinie."; - assert!(check_spelling_in(pl, ProofLang::Pl).is_empty(), "{:?}", check_spelling_in(pl, ProofLang::Pl)); - assert!(check_spelling_in(pl, ProofLang::En).len() >= 3, "Polish words are English misspellings"); - assert!(check_spelling_in("Nous écrivons en français.", ProofLang::Other).is_empty()); - assert!(check_grammar_in("this is is wrong", ProofLang::Other).is_empty()); - assert!(is_correct_in("anything", ProofLang::Other) && suggest_in("xyzzy", 5, ProofLang::Other).is_empty()); - let v = check_spelling_in("Płynie żeka.", ProofLang::Pl); - assert_eq!(flagged("Płynie żeka.", &v), ["żeka"]); - assert_eq!((v[0].lang, v[0].message.as_str()), (ProofLang::Pl, "Możliwy błąd pisowni")); - assert!(suggest_in("żeka", 5, ProofLang::Pl).contains(&"rzeka".to_string())); - assert!(suggest_in("recieve", 5, ProofLang::En).contains(&"receive".to_string())); - // Polish grammar speaks Polish and has no English a/an rule. - let g = check_grammar_in("Wiem że to jest a apple.", ProofLang::Pl); - assert!(g.iter().any(|i| i.message == "Brak przecinka przed „że”"), "{g:?}"); - assert!(!g.iter().any(|i| i.message.contains("\"an\"")), "{g:?}"); - } - - #[test] - fn italian_uses_its_own_tools() { - it::test_dict::install(); - let it = "La città vecchia ha un bel caffè in piazza."; - assert!(check_spelling_in(it, ProofLang::It).is_empty(), "{:?}", check_spelling_in(it, ProofLang::It)); - assert!(check_spelling_in(it, ProofLang::En).len() >= 3, "Italian words are English misspellings"); - let v = check_spelling_in("Vado a Roma con lbiro.", ProofLang::It); - assert_eq!(flagged("Vado a Roma con lbiro.", &v), ["lbiro"]); - assert_eq!((v[0].lang, v[0].message.as_str()), (ProofLang::It, "Possibile errore di ortografia")); - assert!(suggest_in("caffe", 5, ProofLang::It).contains(&"caffè".to_string())); - // Italian grammar speaks Italian and has no English a/an rule. - let g = check_grammar_in("questo questo è un test", ProofLang::It); - assert!(g.iter().any(|i| i.message.contains("ripetuta")), "{g:?}"); - } - - #[test] - fn mixed_language_paragraphs_check_each_run_in_its_language() { - pl::test_dict::install(); - // A Polish paragraph quoting English: English spelling and rules inside the quote only. - let t = "Napisał „the quick brown fox is is here” i poszedł spać, bo żeka wylała."; - let (a, b) = (t.find("the").unwrap(), t.find('”').unwrap()); - let spans = [(0..a, ProofLang::Pl), (a..b, ProofLang::En), (b..t.len(), ProofLang::Pl)]; - let v = check_text(t, &spans); - assert_eq!(flagged(t, &v), ["żeka"], "{v:?}"); - let rep = v.iter().find(|i| i.kind == IssueKind::Grammar).unwrap(); - assert_eq!((rep.lang, rep.message.as_str()), (ProofLang::En, "Repeated word: \"is\""), "{v:?}"); - // The English rules don't run over the Polish text (no "Capitalize" for the lower-case - // Polish words), and the Polish rules don't run over the English quote. - assert!(v.iter().all(|i| i.lang == ProofLang::Pl || (a..b).contains(&i.start)), "{v:?}"); - // Text outside every span is English; no spans at all is English. - assert_eq!(flagged("helo świat", &check_text("helo świat", &[])), ["helo", "świat"]); - assert_eq!(flagged("helo świat", &check_text("helo świat", &[(5..12, ProofLang::Pl)])), ["helo"]); - // A language without tools: nothing flagged there. - assert!(check_text("Bonjour mes amis", &[(0..16, ProofLang::Other)]).is_empty()); - // Portuguese checks in their span only, with the Portuguese message. - pt::test_dict::install(); - let t = "Ele disse que coraçao e frequencia existem."; - let v = check_text(t, &[(0..t.len(), ProofLang::Pt)]); - assert_eq!(flagged(t, &v), ["coraçao", "frequencia"], "{v:?}"); - assert!(v.iter().all(|i| i.lang == ProofLang::Pt && i.message == "Possível erro de ortografia"), "{v:?}"); - // Italian checks in its span only, with the Italian message. - it::test_dict::install(); - let t = "Vado a Roma con lbiro e caffe."; - let v = check_text(t, &[(0..t.len(), ProofLang::It)]); - assert_eq!(flagged(t, &v), ["lbiro", "caffe"], "{v:?}"); - assert!(v.iter().all(|i| i.lang == ProofLang::It && i.message == "Possibile errore di ortografia"), "{v:?}"); - } - - proptest::proptest! { - #[test] - fn never_panics(s in "\\PC{0,80}") { - let _ = check_spelling(&s); - let _ = check_grammar(&s); - let _ = suggest(&s, 3); - let _ = has_dictionary(Some(&s)); - let _ = is_correct_for(&s, Some(&s)); - let _ = hyphen::hyphen_points(&s, &hyphen::Limits::default()); - for lang in [ProofLang::En, ProofLang::EnGb, ProofLang::EnCa, ProofLang::Pl, ProofLang::Pt, ProofLang::It, ProofLang::Hr, ProofLang::Tr, ProofLang::Ru, ProofLang::Cs, ProofLang::Id, ProofLang::De, ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy, ProofLang::Other] { - let _ = check_spelling_in(&s, lang); - let _ = check_grammar_in(&s, lang); - let _ = is_correct_in(&s, lang); - let _ = hyphen::hyphen_points_in(&s, &hyphen::Limits::default(), lang); - } - let _ = lang::normalize_tag(&s); - let _ = ProofLang::from_tag(Some(&s)); - } - - #[test] - fn polish_suggestions_never_panic(s in "[a-ząćęłńóśźżA-ZĄĆĘŁŃÓŚŹŻ'’ -]{0,14}") { - let _ = suggest_in(&s, 4, ProofLang::Pl); - } - - #[test] - fn portuguese_suggestions_never_panic(s in "[a-záàâãéêíóôõúüçA-ZÁÀÂÃÉÊÍÓÔÕÚÜÇ'’ -]{0,14}") { - let _ = suggest_in(&s, 4, ProofLang::Pt); - } - - #[test] - fn italian_suggestions_never_panic(s in "[a-zàèéìòóùA-ZÀÈÉÌÒÓÙ'’ -]{0,14}") { - let _ = suggest_in(&s, 4, ProofLang::It); - } - - #[test] - fn turkish_suggestions_never_panic(s in "[a-zçğıöşüA-ZÇĞİÖŞÜ'’ -]{0,14}") { - let _ = suggest_in(&s, 4, ProofLang::Tr); - } - - #[test] - fn czech_suggestions_never_panic(s in "[a-zěščřžýáíéůúĚŠČŘŽÝÁÍÉŮÚ'’ -]{0,14}") { - cs::test_dict::install(); - let _ = suggest_in(&s, 4, ProofLang::Cs); - } - - #[test] - fn indonesian_suggestions_never_panic(s in "[a-zA-Zé'’ -]{0,14}") { - id::test_dict::install(); - let _ = suggest_in(&s, 4, ProofLang::Id); - let _ = is_correct_in(&s, ProofLang::Id); - } - - #[test] - fn spanish_suggestions_never_panic(s in "[a-zA-ZáéíóúüñÁÉÍÓÚÜÑ¿¡'’ -]{0,14}") { - es::test_dict::install(); - for lang in [ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy] { - let _ = suggest_in(&s, 4, lang); - let _ = is_correct_in(&s, lang); - } - } - - #[test] - fn mixed_spans_never_panic(s in "\\PC{0,60}", cuts in proptest::collection::vec((0usize..80, 0usize..80, 0u8..15), 0..6)) { - let langs = [ProofLang::En, ProofLang::EnGb, ProofLang::EnCa, ProofLang::Pl, ProofLang::Pt, ProofLang::It, ProofLang::Tr, ProofLang::Ru, ProofLang::Cs, ProofLang::Id, ProofLang::Es, ProofLang::EsMx, ProofLang::Es419, ProofLang::EsUy, ProofLang::Other]; - let spans: Vec<(Range, ProofLang)> = cuts.iter().map(|&(a, b, l)| (a.min(b)..a.max(b), langs[l as usize % langs.len()])).collect(); - for i in check_text(&s, &spans) { - proptest::prop_assert!(i.start <= i.end && i.end <= s.len()); - } - } - } - - proptest::proptest! { - // Suggestions scan the whole word list: fewer cases keep the test quick. - #![proptest_config(proptest::prelude::ProptestConfig::with_cases(24))] - #[test] - fn english_dialect_suggestions_never_panic(s in "[a-zA-Zéß'’ -]{0,16}|(colou?r|cent(er|re)|organi[sz]ation)['’s-]{0,4}") { - for lang in [ProofLang::En, ProofLang::EnGb, ProofLang::EnCa] { - let _ = suggest_in(&s, 4, lang); - let _ = is_correct_in(&s, lang); - let _ = en_variants::preferred(&s, lang); - let _ = en_variants::known_variant(&s, lang, |_| true); - } - } - } -} diff --git a/crates/proof/src/patterns.rs b/crates/proof/src/patterns.rs deleted file mode 100644 index cb5855d49..000000000 --- a/crates/proof/src/patterns.rs +++ /dev/null @@ -1,356 +0,0 @@ -//! Liang hyphenation patterns (TeX notation, e.g. `.ab1c`, `4t1ion`): parsing and application. -//! -//! The bundled English set is *ours*: trained from the public-domain Moby Hyphenator word list by -//! DesignCraft's patgen (see `examples/hyphgen.rs`). The Polish set is the TeX one by Hanna -//! Kołodziejska, Bogusław Jackowski and Marek Ryćko (hyph-utf8 `hyph-pl.tex`, used under its MIT -//! licence option), bundled unmodified: [`Patterns::parse`] reads TeX's `\patterns{…}` and -//! `\hyphenation{…}` blocks directly. Patterns are stored in a hash map keyed by the packed letter -//! string, so applying them to a word costs `O(len × max_pattern_len)` lookups. - -use std::collections::HashMap; -use std::hash::{BuildHasherDefault, Hasher}; - -/// Word-boundary marker in patterns. -pub const DOT: char = '.'; -/// Longest pattern the packed key supports (6 bits a letter in a `u128`; the Polish set's longest -/// is 14 including the dots). -pub const MAX_PATTERN_LEN: usize = 20; - -/// Small, fast multiplicative hasher for packed integer keys and short strings (FxHash-style). -#[derive(Default, Clone, Copy)] -pub struct FxHasher(u64); - -impl Hasher for FxHasher { - fn finish(&self) -> u64 { - self.0 - } - fn write(&mut self, bytes: &[u8]) { - let (chunks, rest) = bytes.as_chunks::<8>(); - for c in chunks { - self.write_u64(u64::from_le_bytes(*c)); - } - for &b in rest { - self.write_u64(b as u64); - } - } - fn write_u8(&mut self, i: u8) { - self.write_u64(i as u64); - } - fn write_usize(&mut self, i: usize) { - self.write_u64(i as u64); - } - fn write_u64(&mut self, i: u64) { - self.0 = (self.0.rotate_left(5) ^ i).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95); - } - fn write_u128(&mut self, i: u128) { - self.write_u64(i as u64); - self.write_u64((i >> 64) as u64); - } -} - -pub type FxMap = HashMap>; - -/// Maps characters to 6-bit codes (1 = word boundary, 2.. = letters); 0 = not in the alphabet. -#[derive(Clone, Debug)] -pub struct Alphabet { - ascii: [u8; 128], - other: Vec<(char, u8)>, - next: u8, -} - -impl Default for Alphabet { - fn default() -> Self { - let mut a = Alphabet { ascii: [0; 128], other: Vec::new(), next: 2 }; - a.ascii[DOT as usize] = 1; - a - } -} - -impl Alphabet { - pub fn code(&self, c: char) -> u8 { - if (c as u32) < 128 { self.ascii[c as usize] } else { self.other.iter().find(|x| x.0 == c).map_or(0, |x| x.1) } - } - /// Code for `c`, adding it if new (None when the 6-bit alphabet is full). - pub fn intern(&mut self, c: char) -> Option { - let k = self.code(c); - if k != 0 { - return Some(k); - } - if self.next >= 63 { - return None; - } - let k = self.next; - self.next += 1; - if (c as u32) < 128 { - self.ascii[c as usize] = k; - } else { - self.other.push((c, k)); - } - Some(k) - } -} - -/// Packed key of a code string (≤ [`MAX_PATTERN_LEN`] codes): 6 bits a code, the length on top. -#[inline] -pub fn pack(codes: &[u8]) -> u128 { - let mut k = 0u128; - for &c in codes { - k = (k << 6) | c as u128; - } - k | ((codes.len() as u128) << 120) -} - -/// A compiled pattern set, with its exception words (TeX's `\hyphenation{…}`). -#[derive(Clone, Debug, Default)] -pub struct Patterns { - alpha: Alphabet, - /// Packed letters → inter-letter values (`len + 1` entries). - map: FxMap>, - max_len: usize, - /// Lowercase word → its break points (char indices), overriding the patterns. - exceptions: HashMap>, -} - -/// Which TeX block [`Patterns::parse`] is reading. -#[derive(Clone, Copy, PartialEq, Eq)] -enum Block { - Patterns, - Exceptions, - /// Another command's argument (`\message{…}`), ignored. - Skip, -} - -impl Patterns { - /// Parse patterns: whitespace-separated, `%` comments to the end of the line (`#` too at the - /// start of a line). A whole TeX pattern file works as is: `\patterns{…}` holds patterns and - /// `\hyphenation{…}` exception words written with their hyphens (`ni-gdy`), and other - /// commands' arguments (`\message{…}`) are skipped; outside any block, a token with a hyphen - /// is an exception and anything else a pattern. - pub fn parse(text: &str) -> Patterns { - let mut p = Patterns::default(); - let mut block = Block::Patterns; - for line in text.lines() { - let line = line.trim(); - if line.starts_with('#') { - continue; - } - let line = line.split('%').next().unwrap_or(""); - for tok in line.split_whitespace() { - let mut tok = tok; - if let Some(cmd) = tok.strip_prefix('\\') { - let (name, rest) = cmd.split_once('{').unwrap_or((cmd, "")); - block = match name { - "hyphenation" => Block::Exceptions, - "patterns" => Block::Patterns, - // shortcut: the argument ends at the first `}`, so nested braces would leak; no bundled file has them. - _ if cmd.contains('{') => Block::Skip, - _ => Block::Patterns, - }; - tok = rest; - } - let closes = tok.ends_with('}'); - let tok = tok.trim_end_matches('}'); - if !tok.is_empty() && block != Block::Skip { - if block == Block::Exceptions || tok.contains('-') { - p.insert_exception(tok); - } else { - p.insert(tok); - } - } - if closes { - block = Block::Patterns; - } - } - } - p - } - - /// Add one pattern like `a1b`; returns false if it can't be represented. - pub fn insert(&mut self, pat: &str) -> bool { - let mut codes = Vec::with_capacity(pat.len()); - let mut vals = vec![0u8]; - for c in pat.chars() { - if let Some(d) = c.to_digit(10) { - *vals.last_mut().unwrap_or(&mut 0) = d as u8; - } else { - let Some(k) = self.alpha.intern(c) else { return false }; - codes.push(k); - vals.push(0); - } - } - if codes.is_empty() || codes.len() > MAX_PATTERN_LEN { - return false; - } - self.max_len = self.max_len.max(codes.len()); - self.map.insert(pack(&codes), vals.into_boxed_slice()); - true - } - - /// Add an exception word written with its hyphens (`ni-gdy`; no hyphen: never broken). - pub fn insert_exception(&mut self, word: &str) { - let mut plain = String::with_capacity(word.len()); - let mut points = Vec::new(); - let mut n = 0usize; - for c in word.chars() { - if c == '-' { - if n > 0 && points.last() != Some(&n) { - points.push(n); - } - } else { - plain.extend(c.to_lowercase()); - n += 1; - } - } - points.retain(|&i| i < n); - if n > 0 { - self.exceptions.insert(plain, points.into_boxed_slice()); - } - } - - /// Break points of an exception word (lowercase), if it is one. - pub fn exception(&self, word: &str) -> Option<&[usize]> { - self.exceptions.get(word).map(|b| &b[..]) - } - - /// Number of exception words. - pub fn exception_count(&self) -> usize { - self.exceptions.len() - } - - /// Make `letters` part of the alphabet even if no pattern mentions them. - pub fn add_letters(&mut self, letters: impl IntoIterator) { - for c in letters { - let _ = self.alpha.intern(c); - } - } - - pub fn len(&self) -> usize { - self.map.len() - } - - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - /// Inter-letter values for a lowercase word: `out[i]` is the value *before* char `i` - /// (`out.len() == word.len() + 1`). None if a char is outside the pattern alphabet. - pub fn values(&self, word: &[char]) -> Option> { - let mut codes = Vec::with_capacity(word.len() + 2); - codes.push(1); - for &c in word { - let k = self.alpha.code(c); - if k == 0 { - return None; - } - codes.push(k); - } - codes.push(1); - let mut vals = vec![0u8; codes.len() + 1]; - apply(&self.map, self.max_len, &codes, &mut vals); - // Padded position p sits before padded char p; word char i is padded char i + 1. - vals.get(1..codes.len()).map(<[u8]>::to_vec) - } - - /// Break points (char indices, a hyphen goes before that char) of a lowercase word. - pub fn points(&self, word: &[char]) -> Option> { - let v = self.values(word)?; - Some((1..word.len()).filter(|&i| v.get(i).is_some_and(|x| x % 2 == 1)).collect()) - } - - /// The patterns in TeX notation (sorted by letters), e.g. for writing a pattern file. - pub fn to_tex(&self) -> Vec { - let mut rev: Vec<(u8, char)> = - (0u8..128).filter(|&c| self.alpha.ascii[c as usize] != 0).map(|c| (self.alpha.ascii[c as usize], c as char)).collect(); - rev.extend(self.alpha.other.iter().map(|&(c, k)| (k, c))); - let ch = |k: u8| rev.iter().find(|x| x.0 == k).map_or('?', |x| x.1); - let mut out: Vec<(String, String)> = self - .map - .iter() - .map(|(&key, vals)| { - let n = ((key >> 120) as usize).min(MAX_PATTERN_LEN); - let letters: Vec = (0..n).map(|i| ch(((key >> (6 * (n - 1 - i))) & 63) as u8)).collect(); - let mut s = String::new(); - for (i, l) in letters.iter().enumerate() { - if let Some(&v) = vals.get(i).filter(|v| **v > 0) { - s.push(char::from(b'0' + v.min(9))); - } - s.push(*l); - } - if let Some(&v) = vals.get(n).filter(|v| **v > 0) { - s.push(char::from(b'0' + v.min(9))); - } - (letters.into_iter().collect(), s) - }) - .collect(); - out.sort(); - out.into_iter().map(|x| x.1).collect() - } -} - -/// Apply `map` to padded `codes`, raising `vals` (len = codes.len() + 1; shorter is tolerated). -#[inline] -pub fn apply(map: &FxMap>, max_len: usize, codes: &[u8], vals: &mut [u8]) { - let n = codes.len(); - for s in 0..n { - let mut k = 0u128; - for (len, &c) in codes.iter().skip(s).take(max_len.min(MAX_PATTERN_LEN)).enumerate() { - k = (k << 6) | c as u128; - if let Some(v) = map.get(&(k | (((len + 1) as u128) << 120))) { - for (j, &x) in v.iter().enumerate() { - if let Some(slot) = vals.get_mut(s + j) - && x > *slot - { - *slot = x; - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn liang_example() { - // The classic example from Liang's thesis: "hy-phen-ation". - let p = Patterns::parse("hy3ph he2n hena4 hen5at 1na n2at 1tio 2io o2n"); - let w: Vec = "hyphenation".chars().collect(); - assert_eq!(p.points(&w).unwrap(), vec![2, 6]); - let mut tex = p.to_tex(); - tex.sort(); - assert!(tex.contains(&"hen5at".to_string())); - assert!(p.values(&['h', 'é']).is_none()); - } - - #[test] - fn tex_files_parse_with_blocks_comments_and_exceptions() { - let p = Patterns::parse( - "% a comment\n\\patterns{\n1na n2at % trailing comment\n.ab4cdefghijklm5n\n}\n\\hyphenation{\nni-gdy\nna-dal\nzawsze\n}\nhy-phen-ation\n", - ); - assert_eq!(p.len(), 3); - assert_eq!(p.exception("nigdy"), Some(&[2][..])); - assert_eq!(p.exception("nadal"), Some(&[2][..])); - assert_eq!(p.exception("zawsze"), Some(&[][..])); - assert_eq!(p.exception("hyphenation"), Some(&[2, 6][..])); - assert_eq!(p.exception_count(), 4); - // A pattern longer than the old 9-letter limit applies. - let w: Vec = "abcdefghijklmn".chars().collect(); - assert_eq!(p.values(&w).unwrap()[13], 5); - assert!(p.to_tex().contains(&".ab4cdefghijklm5n".to_string())); - } - - #[test] - fn other_commands_arguments_are_skipped() { - let p = Patterns::parse("\\message{Some Patterns `x-y' 2024-02-28}\n\\patterns{%\na1b\n}\n\\endinput\n"); - assert_eq!((p.len(), p.exception_count()), (1, 0)); - } - - #[test] - fn apply_tolerates_short_value_buffers() { - let p = Patterns::parse("a1b"); - let mut vals = [0u8; 1]; - apply(&p.map, p.max_len, &[1, 2, 3, 1], &mut vals); - } -} diff --git a/crates/proof/src/pl/grammar.rs b/crates/proof/src/pl/grammar.rs deleted file mode 100644 index 4289f0c7b..000000000 --- a/crates/proof/src/pl/grammar.rs +++ /dev/null @@ -1,548 +0,0 @@ -//! Polish grammar and punctuation checks: cheap, rule-based and tuned to stay quiet when unsure -//! (a missed comma costs less than a wrong mark on correct text). Messages are in Polish. -//! -//! - **Repeated word** (`się się`), except interjections and words Polish doubles on purpose -//! (`to to`, `tak tak`, `ha ha`). -//! - **Space before punctuation** (`tak ,`) and **double spaces**; emoticons (` :)`) and -//! ellipses are left alone. -//! - **Capital letter at the start of a sentence**, knowing Polish abbreviations (`np.`, `itd.`, -//! `r.`, `m.in.`), initials, ordinal numbers (`5. miejsce`), ellipses and dialogue dashes -//! (`– Tak? – zapytał`), after which lower case is correct. -//! - **Missing comma** before subordinating conjunctions and relative pronouns that always open a -//! clause: `że`, `ponieważ`, `gdyż`, `aby`, `żeby`, `jeśli`, `jeżeli`, `gdyby`, `lecz` and the -//! forms of `który` — unless a conjunction or particle comes first (`mimo że`, `chyba że`, -//! `tak aby`, `i który`) or, for `który`, a preposition (`w którym`: the comma goes before it). -//! - **`we`/`ze`** where the next word's consonant cluster requires them (`we wtorek`, -//! `ze szkoły`, `ze mną`). -//! - **Doubled punctuation** (`,,` — often a typed opening quote `„` — `..`, `;;`). -//! -//! No English rule (such as `a`/`an`) applies to Polish text. - -use crate::{Issue, IssueKind, ProofLang, words}; - -/// Words Polish repeats on purpose: interjections, onomatopoeia and emphatic doubling. -const ALLOWED_REPEATS: &[&str] = &[ - "to", "tak", "nie", "no", "ha", "he", "hi", "hej", "oj", "aj", "ej", "ach", "och", "ech", "puk", "hop", "bum", "cyk", "tik", "pa", "fiu", "la", - "bardzo", "dawno", "daleko", "długo", -]; - -/// Abbreviations written with a final period after which a sentence goes on in lower case -/// (lower case, without the period). -const ABBREVIATIONS: &[&str] = &[ - "np", "itd", "itp", "tzn", "tj", "tzw", "ok", "ul", "al", "pl", "os", "in", "wg", "zob", "por", "str", "ss", "nr", "pkt", "ust", "art", "par", - "rozdz", "tab", "rys", "il", "przyp", "red", "wyd", "oprac", "tłum", "cyt", "jw", "ds", "im", "ks", "św", "bł", "prof", "dr", "doc", "hab", - "inż", "mgr", "lek", "płk", "gen", "mjr", "kpt", "ppłk", "sierż", "szer", "kmdr", "adm", "abp", "bp", "br", "ub", "bm", "pn", "płd", "płn", - "wsch", "zach", "gł", "godz", "min", "sek", "tys", "mln", "mld", "zł", "gr", "kg", "dag", "mies", "tyg", "pon", "wt", "śr", "czw", "pt", "sob", - "niedz", "sty", "lut", "mar", "kwi", "cze", "lip", "sie", "wrz", "paź", "lis", "gru", "ang", "niem", "franc", "łac", "ros", "wł", "hiszp", "pol", - "lit", "dosł", "przen", "pot", "zdrob", "fot", "ryc", "tel", "kom", "ew", "ob", "obyw", "pp", "zm", "ur", "ca", "vs", "etc", "cdn", "dot", "wz", - "zw", "zał", "cz", "rr", "ww", "jęz", "jun", "sen", "mec", "red", "dyr", "kier", "proc", "poj", "lp", "lm", "zaw", "woj", "pow", "gm", "wsp", - "nast", "poprz", "ang", "fr", -]; - -/// Subordinating conjunctions and relative pronouns that open a clause: a comma comes before -/// them. -const CLAUSE_OPENERS: &[&str] = &[ - "że", - "ponieważ", - "gdyż", - "aby", - "żeby", - "jeśli", - "jeżeli", - "gdyby", - "lecz", - "który", - "która", - "które", - "którego", - "której", - "któremu", - "którą", - "którym", - "których", - "którymi", - "którzy", -]; - -/// Words after which a clause opener takes no comma of its own: conjunctions (`i że`), particles -/// (`tylko jeśli`, `zwłaszcza że`) and the first halves of compound conjunctions (`mimo że`, -/// `chyba że`, `tak aby`, `jak gdyby`, `na wypadek gdyby`, `z tym że`). -const NO_COMMA_AFTER: &[&str] = &[ - "i", - "a", - "oraz", - "lub", - "albo", - "ani", - "czy", - "bądź", - "ale", - "lecz", - "bo", - "że", - "aby", - "żeby", - "by", - "niż", - "jak", - "jako", - "niby", - "tylko", - "nawet", - "właśnie", - "zwłaszcza", - "szczególnie", - "głównie", - "częściowo", - "jedynie", - "wyłącznie", - "chyba", - "mianowicie", - "przynajmniej", - "również", - "także", - "też", - "dopiero", - "jeszcze", - "już", - "zaś", - "więc", - "zatem", - "toteż", - "przecież", - "chociaż", - "choć", - "choćby", - "gdy", - "kiedy", - "jeśli", - "jeżeli", - "gdyby", - "skoro", - "zanim", - "dopóki", - "póki", - "aż", - "co", - "tak", - "mimo", - "pomimo", - "wtedy", - "teraz", - "dziś", - "dzisiaj", - "podczas", - "tym", - "bardziej", - "warunkiem", - "wypadek", - "zamiast", - "byle", - "lada", - "wiadomo", - "obojętnie", - "jedno", - "wszystkim", - "może", - "pewnie", - "prawdopodobnie", - "zapewne", - "dlatego", - "tyle", - "dość", - "ponieważ", - "gdyż", - "lecz", - "albowiem", - "oto", - "no", -]; - -/// Prepositions: a relative pronoun after one takes the comma before the preposition instead -/// (`dom, w którym`). -const PREPOSITIONS: &[&str] = &[ - "w", - "we", - "z", - "ze", - "na", - "do", - "od", - "ode", - "o", - "u", - "po", - "za", - "przed", - "przede", - "przez", - "przeze", - "przy", - "nad", - "nade", - "pod", - "pode", - "dla", - "bez", - "beze", - "ku", - "między", - "pomiędzy", - "wśród", - "spośród", - "spod", - "spoza", - "sprzed", - "znad", - "zza", - "poza", - "ponad", - "poprzez", - "dzięki", - "według", - "wobec", - "oprócz", - "prócz", - "obok", - "koło", - "około", - "wokół", - "dookoła", - "naprzeciw", - "naprzeciwko", - "wzdłuż", - "wewnątrz", - "zewnątrz", - "względem", - "przeciw", - "przeciwko", - "wbrew", - "wskutek", - "śród", - "wedle", - "pośród", - "ponad", - "niedaleko", - "blisko", -]; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang: ProofLang::Pl } -} - -fn capitalize(w: &str) -> String { - crate::affix::capitalize(w) -} - -/// Polish vowels (and the accented vowels of loanwords). -fn is_vowel(c: char) -> bool { - matches!(c.to_lowercase().next().unwrap_or(c), 'a' | 'ą' | 'e' | 'ę' | 'i' | 'o' | 'ó' | 'u' | 'y' | 'é' | 'á' | 'ö' | 'ü' | 'í' | 'ú') -} - -fn is_consonant(c: char) -> bool { - c.is_alphabetic() && !is_vowel(c) -} - -/// Grammar issues in a paragraph of Polish text, in order. -pub fn check(text: &str) -> Vec { - check_with(text, true) -} - -/// [`check`] where `capital_first` says whether the text's first word must start a sentence with a capital -/// (false for a list item, which may be a fragment). -pub fn check_with(text: &str, capital_first: bool) -> Vec { - let ws: Vec<(usize, usize)> = words(text); - let mut v = Vec::new(); - repeated_words(text, &ws, &mut v); - spaces(text, &mut v); - sentence_capitals(text, &ws, capital_first, &mut v); - missing_commas(text, &ws, &mut v); - we_ze(text, &ws, &mut v); - doubled_punctuation(text, &mut v); - v.sort_by_key(|i| (i.start, i.end)); - v -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() - && gap.chars().all(char::is_whitespace) - && w0.chars().all(char::is_alphabetic) - && w0.to_lowercase() == w1.to_lowercase() - && !ALLOWED_REPEATS.contains(&w0.to_lowercase().as_str()) - { - v.push(issue(*a0, *b1, format!("Powtórzone słowo: „{w1}”"), vec![w0.to_string()])); - } - } -} - -fn spaces(text: &str, v: &mut Vec) { - for (i, c) in text.char_indices() { - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - let flag = match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '”' | '»' | '’' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Zbędna spacja przed znakiem interpunkcyjnym".into(), vec![p.to_string()])); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Podwójna spacja".into(), vec![" ".into()])); - } -} - -/// Is `prev`, before a period, an abbreviation, an initial, a number or a Roman numeral (after -/// which a lower-case word doesn't start a sentence)? -fn abbreviation_or_number(prev: &str) -> bool { - prev.chars().count() <= 1 - || prev.chars().any(|c| c.is_ascii_digit()) - || (prev.chars().all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))) - || ABBREVIATIONS.contains(&prev.to_lowercase().as_str()) -} - -const CLOSING: [char; 8] = ['"', '”', '’', '\'', ')', '»', ']', '\u{1}']; -const OPENING: [char; 8] = ['„', '"', '“', '«', '(', '[', '\'', '‚']; - -/// Does the gap between two words end a sentence: a `.`, `!` or `?` (not an ellipsis, not after -/// an abbreviation), then space, with nothing but quotes and brackets around? -fn ends_sentence(gap: &str, prev: &str) -> bool { - let t = gap.trim_start_matches(CLOSING); - let Some(end) = t.chars().next() else { return false }; - if !matches!(end, '.' | '!' | '?') || t.starts_with("..") { - return false; - } - if end == '.' && abbreviation_or_number(prev) { - return false; - } - let rest = t.trim_start_matches(['.', '!', '?']).trim_start_matches(CLOSING); - rest.starts_with(char::is_whitespace) && rest.trim_start().chars().all(|c| OPENING.contains(&c)) -} - -fn sentence_capitals(text: &str, ws: &[(usize, usize)], capital_first: bool, v: &mut Vec) { - // List items (`– jabłka,`) may start in lower case. - let list_item = text.trim_end().ends_with([',', ';']); - for (k, &(a, b)) in ws.iter().enumerate() { - let Some(w) = text.get(a..b) else { continue }; - let starts = if k == 0 { - let before = text.get(..a).unwrap_or(""); - capital_first && !list_item && before.chars().all(|c| c.is_whitespace() || OPENING.contains(&c)) - } else { - let Some(&(pa, pb)) = ws.get(k - 1) else { continue }; - ends_sentence(text.get(pb..a).unwrap_or(""), text.get(pa..pb).unwrap_or("")) - }; - if starts && w.chars().next().is_some_and(char::is_lowercase) && w.chars().all(char::is_alphabetic) { - v.push(issue(a, b, "Wielka litera na początku zdania".into(), vec![capitalize(w)])); - } - } -} - -fn missing_commas(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for k in 1..ws.len() { - let (Some(&(pa, pb)), Some(&(a, b))) = (ws.get(k - 1), ws.get(k)) else { continue }; - let (Some(prev), Some(w), Some(gap)) = (text.get(pa..pb), text.get(a..b), text.get(pb..a)) else { continue }; - if !CLAUSE_OPENERS.contains(&w) || gap.is_empty() || !gap.chars().all(|c| c == ' ' || c == '\u{a0}') { - continue; - } - if !prev.chars().all(char::is_alphabetic) { - continue; - } - let p = prev.to_lowercase(); - let p = p.as_str(); - if NO_COMMA_AFTER.contains(&p) { - continue; - } - if w.starts_with("któr") && (PREPOSITIONS.contains(&p) || p == "to") { - continue; - } - v.push(issue(pb, b, format!("Brak przecinka przed „{w}”"), vec![format!(", {w}")])); - } -} - -fn we_ze(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if gap != " " && gap != "\u{a0}" { - continue; - } - let next = w1.to_lowercase(); - let c: Vec = next.chars().collect(); - let at = |i: usize| c.get(i).copied(); - let (long, wanted) = match w0 { - "w" | "W" => ("we", (at(0) == Some('w') && at(1).is_some_and(is_consonant)) || next == "mnie"), - "z" | "Z" => { - // The first sound: `sz` is one consonant (`z szafy`, but `ze szkoły`). - let after = if at(0) == Some('s') && at(1) == Some('z') { at(2) } else { at(1) }; - let sibilant = matches!(at(0), Some('s' | 'z' | 'ś' | 'ź' | 'ż')); - ("ze", (sibilant && after.is_some_and(is_consonant)) || next == "mną") - } - _ => continue, - }; - if wanted { - let fixed = if w0.starts_with(char::is_uppercase) { capitalize(long) } else { long.to_string() }; - v.push(issue(*a0, *b0, format!("Przed tym słowem piszemy „{long}”"), vec![fixed])); - } - } -} - -fn doubled_punctuation(text: &str, v: &mut Vec) { - let bytes = text.as_bytes(); - let mut i = 0; - while let Some(&b) = bytes.get(i) { - if !matches!(b, b',' | b'.' | b';') { - i += 1; - continue; - } - let run = bytes.get(i..).map_or(0, |r| r.iter().take_while(|&&x| x == b).count()); - let flag = match b { - b'.' => run == 2, - _ => run >= 2, - }; - if flag { - let end = i + run; - let suggestions: Vec = match b { - // `,,` before a word, after a space: a typed opening quote. - b',' if text.get(end..).and_then(|t| t.chars().next()).is_some_and(char::is_alphabetic) - && (i == 0 || text.get(..i).is_some_and(|t| t.ends_with(char::is_whitespace))) => - { - vec!["„".into()] - } - b'.' => vec![".".into(), "…".into()], - _ => vec![char::from(b).to_string()], - }; - v.push(issue(i, end, "Powtórzony znak interpunkcyjny".into(), suggestions)); - } - i += run.max(1); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn messages(t: &str) -> Vec { - check(t).into_iter().map(|i| format!("{}@{}", i.message, &t[i.start..i.end])).collect() - } - - fn clean(t: &str) { - let m = messages(t); - assert!(m.is_empty(), "{t:?}: {m:?}"); - } - - #[test] - fn repeated_words() { - let v = check("Ala ma ma kota i się się cieszy."); - assert_eq!(v.len(), 2, "{v:?}"); - assert_eq!(v[0].message, "Powtórzone słowo: „ma”"); - assert_eq!(v[0].suggestions, ["ma"]); - assert!(v.iter().all(|i| i.lang == ProofLang::Pl && i.kind == IssueKind::Grammar)); - clean("Tak tak, to to samo. Ha ha!"); - } - - #[test] - fn spaces_and_punctuation() { - let m = messages("Ala ma kota , a kot ma Alę . Czy na pewno ?"); - assert!(m.contains(&"Zbędna spacja przed znakiem interpunkcyjnym@ ,".to_string()), "{m:?}"); - assert!(m.contains(&"Zbędna spacja przed znakiem interpunkcyjnym@ .".to_string()), "{m:?}"); - assert!(m.contains(&"Zbędna spacja przed znakiem interpunkcyjnym@ ?".to_string()), "{m:?}"); - assert!(m.contains(&"Podwójna spacja@ ".to_string()), "{m:?}"); - clean("Fajnie :) Naprawdę :-) Albo :D Wartość .5 i wielokropek ..."); - let m = messages("Powiedział ,,tak” i poszedł.. Potem;; koniec."); - assert_eq!(m.iter().filter(|x| x.starts_with("Powtórzony znak")).count(), 3, "{m:?}"); - assert_eq!(check("Powiedział ,,tak”.").iter().find(|i| i.message.starts_with("Powtórzony")).unwrap().suggestions, ["„"]); - clean("Czekał… i czekał... aż do rana."); - } - - #[test] - fn sentence_start_capitals() { - let m = messages("to jest zdanie. a to drugie! czy trzecie? tak."); - assert_eq!(m.iter().filter(|x| x.starts_with("Wielka litera")).count(), 4, "{m:?}"); - assert_eq!(check("ala ma kota.")[0].suggestions, ["Ala"]); - // Abbreviations, initials, ordinals, dates, ellipses and dialogue dashes. - clean("Kupiłem owoce, np. jabłka, gruszki itp. oraz warzywa."); - clean("W 1990 r. powstała firma, m.in. dzięki dotacji."); - clean("Spotkałem J. Kowalskiego i prof. Nowaka przy ul. Długiej."); - clean("Zajął 5. miejsce w XX w. sporcie."); - clean("Czekałem... aż w końcu przyszedł."); - clean("– Naprawdę? – zapytał. – Tak – odpowiedziała."); - clean("Powiedział: „Wracam”. Potem wyszedł."); - // A list item may start in lower case. - clean("jabłka,"); - } - - #[test] - fn missing_commas() { - let m = messages("Wiem że przyjdzie. Mam kota który śpi. Zostałem bo musiałem. Idę aby zdążyć. Nie przyszedł ponieważ padało."); - for want in [ - "Brak przecinka przed „że”@ że", - "Brak przecinka przed „który”@ który", - "Brak przecinka przed „aby”@ aby", - "Brak przecinka przed „ponieważ”@ ponieważ", - ] { - assert!(m.contains(&want.to_string()), "{want}: {m:?}"); - } - assert_eq!(check("Wiem że tak.")[0].suggestions, [", że"]); - // Compound conjunctions, particles, conjunctions and prepositions take no comma there. - clean("Przyszedł, mimo że padało, chyba że nie."); - clean("Zrobię to, tylko jeśli zdążę, i to tak aby nikt nie widział."); - clean("To dom, w którym mieszkam, i ogród, przez który idę."); - clean("Wiem, że przyjdzie, i że zostanie."); - clean("Myślę, że gdyby przyszedł, byłoby dobrze."); - clean("Zachowywał się, jak gdyby nigdy nic."); - clean("Który z nich? To który wybierasz?"); - clean("Po raz nie wiadomo który."); - clean("Dlatego że padało, zostaliśmy."); - } - - #[test] - fn we_and_ze() { - let m = messages("Spotkamy się w wtorek w Wrocławiu z szkoły z mną."); - assert_eq!(m.iter().filter(|x| x.starts_with("Przed tym słowem")).count(), 4, "{m:?}"); - let v = check("Z szkoły wracam."); - assert_eq!(v[0].suggestions, ["Ze"]); - clean("Byłem w wodzie, w wieku dziesięciu lat, z szafą, z siostrą i z zamkiem."); - clean("Wracam ze szkoły we wtorek ze mną."); - } - - #[test] - fn correct_polish_text_is_clean() { - clean( - "Zażółć gęślą jaźń. Chrząszcz brzmi w trzcinie, a źdźbło trawy się kołysze. \ - Przyjdę jutro, chyba że będzie padać. Książka, którą czytam, jest ciekawa.", - ); - clean("Pięćdziesięciu uczniów przyszło, ponieważ ogłoszono konkurs."); - } - - proptest::proptest! { - #[test] - fn never_panics(s in "\\PC{0,80}") { - let _ = check(&s); - } - - #[test] - fn never_panics_on_polish_like_text(s in "[a-ząćęłńóśźżA-Z ,.;:!?„”…\\-]{0,60}") { - for i in check(&s) { - proptest::prop_assert!(i.start <= i.end && i.end <= s.len()); - proptest::prop_assert!(s.is_char_boundary(i.start) && s.is_char_boundary(i.end)); - } - } - } -} diff --git a/crates/proof/src/pl/mod.rs b/crates/proof/src/pl/mod.rs deleted file mode 100644 index 7cde9d0f9..000000000 --- a/crates/proof/src/pl/mod.rs +++ /dev/null @@ -1,193 +0,0 @@ -//! Polish proofing. -//! -//! - **Spelling** against the SJP.PL Hunspell dictionary installed on the system -//! ([`dictionary`], found by [`crate::hunspell`]: `pl_PL.aff`/`pl_PL.dic` from the OS, a -//! package manager or LibreOffice; it isn't bundled). Without it Polish text gets no spelling -//! marks. Capitalized stems are names (`Warszawa`); a sentence may start with any word and -//! headings may be in capitals. Hyphenated compounds are checked part by part -//! (`biało-czerwony`), foreign names inflected with an apostrophe are accepted -//! (`Kennedy'ego`), and the user dictionary applies as in English. -//! - **Suggestions** tuned for Polish ([`suggest()`]): missing diacritics first (`zolw` → `żółw`), then -//! the common spelling confusions (`ż`/`rz`, `u`/`ó`, `h`/`ch`…), a missing space (`napewno` → -//! `na pewno`), one-letter typos, and a dictionary scan for anything further off. -//! - **Grammar** ([`grammar`]): cheap, reliable rules with Polish messages. - -pub mod grammar; -mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// File names of a Polish Hunspell dictionary. -const NAMES: &[&str] = &["pl_PL", "pl-PL", "pl"]; - -/// The Polish dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Polish"); - -/// The Polish dictionary: the installed Hunspell `pl_PL` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available (Polish spelling is off -/// then; see [`has_dictionary`]). -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Use this Polish dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to -/// UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is a Polish dictionary available (spelling is checked)? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Endings Polish adds after an apostrophe to foreign names whose last letter isn't pronounced -/// (`Kennedy'ego`, `Wilde'a`, `Shakespeare'owi`). -const APOSTROPHE_ENDINGS: &[&str] = &["a", "ach", "ami", "e", "ego", "em", "emu", "i", "m", "om", "owi", "owie", "u", "y", "ów", "ą", "ę"]; - -/// Is `word` spelled correctly in Polish? Numbers, single letters, short words in capitals -/// (acronyms: `PKO`, `ZUS`), words with digits and words in other scripts are accepted, and so -/// is every word when no Polish dictionary is installed. -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool { - let Some(w) = crate::spelling::word_to_check(d, word, opts) else { - return true; - }; - let w = w.replace('’', "'"); - if w.contains('-') { - return w.split('-').all(|p| p.chars().count() <= 1 || known(d, p)); - } - known(d, &w) -} - -/// Is one word (no hyphens) in the dictionary, the user dictionary, or a foreign name with a -/// Polish ending after an apostrophe? -fn known(d: &AffixDict, w: &str) -> bool { - if d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) { - return true; - } - if let Some((stem, ending)) = w.split_once('\'') { - return stem.chars().next().is_some_and(char::is_uppercase) && stem.chars().count() >= 2 && APOSTROPHE_ENDINGS.contains(&ending); - } - false -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_dictionary_loads() { - test_dict::install(); - let d = dictionary(); - assert!(has_dictionary()); - assert!(d.len() > 50, "{}", d.len()); - assert_eq!(d.rule_count(), 5, "the test dictionary's affix rules"); - assert!(!d.rep().is_empty() && !d.map().is_empty() && !d.try_chars().is_empty()); - } - - #[test] - fn without_a_dictionary_nothing_is_flagged_and_nothing_suggested() { - let none = AffixDict::default(); - for w in ["żeka", "kturzy", "xqzv", ""] { - assert!(is_correct_with(&none, w), "{w}"); - } - assert!(suggest::suggest_in(&none, "żeka", 5).is_empty()); - assert!(install_dictionary("FLAG long\n", "1\nkot\n").is_err(), "unsupported files are refused"); - } - - #[test] - fn polish_words_are_correct() { - test_dict::install(); - for w in [ - "źdźbło", - "gżegżółka", - "chrząszcz", - "pięćdziesięciu", - "rzeka", - "którzy", - "żółw", - "Zażółć", - "gęślą", - "jaźń", - "konstytucja", - "konstytucyjnego", - "niebieskiego", - "nieładnie", - "przyszliśmy", - "zrobilibyśmy", - "Warszawa", - "Warszawie", - "WARSZAWA", - "Kot", - "biało-czerwony", - "polsko-niemieckiej", - "Kennedy'ego", - "Shakespeare’a", - "PKO", - "2026", - "e-mail", - "w", - "się", - ] { - assert!(is_correct(w), "{w}"); - } - } - - #[test] - fn misspellings_are_flagged() { - test_dict::install(); - for w in ["żeka", "kturzy", "napewno", "wogóle", "zolw", "chuśtawka", "bżoza", "warszawa", "niewiem", "pszyjaciel", "Kennedy'xyz"] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn user_dictionary_applies() { - test_dict::install(); - assert!(!is_correct("zxqpolskisłowo")); - crate::add_word("Zxqpolskisłowo"); - assert!(is_correct("zxqpolskisłowo")); - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Polish checks - /// those words against the dictionary instead of accepting them. - #[test] - fn proofing_options_reach_the_polish_check() { - test_dict::install(); - let lang = crate::ProofLang::Pl; - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("ŻEKA", lang, &defaults), "short capitals are acronyms by default"); - assert!(!crate::is_correct_in_with("ŻEKA", lang, &check_caps)); - assert!(crate::is_correct_in_with("RZEKA", lang, &check_caps)); - assert!(crate::is_correct_in_with("żeka2", lang, &defaults)); - assert!(!crate::is_correct_in_with("żeka2", lang, &check_digits)); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("ŻEKA"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/pl/suggest.rs b/crates/proof/src/pl/suggest.rs deleted file mode 100644 index 7baeaa54f..000000000 --- a/crates/proof/src/pl/suggest.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Polish spelling suggestions, best first: the shared ranking ([`crate::affix_suggest`]) with the -//! words Polish runs into their neighbours (`napewno` → `na pewno`, `kochamcię` → `kocham cię`), -//! letters to try for a dictionary without a TRY line, and final devoicing (`chlep` → `chleb`). - -use crate::affix::AffixDict; -use crate::affix_suggest::{COST_ADD_DIACRITIC, COST_MISTYPE, Split, Tuning}; - -/// Short words often run into the next one (`na pewno`, `w ogóle`, `nie wiem`, `przede -/// wszystkim`). -const LEADING_WORDS: &[&str] = &[ - "a", "i", "o", "u", "w", "z", "we", "ze", "na", "po", "do", "od", "za", "co", "to", "nie", "jak", "tak", "przy", "bez", "pod", "nad", "dla", - "że", "by", "czy", "już", "ani", "przed", "przez", "przede", "ku", "aż", "bo", "ten", "ta", "te", "tym", "tej", "też", "jest", "ode", "beze", - "nade", -]; -/// Short words often run into the one before (`kocham cię`, `daj mi`, `bałem się`). -const TRAILING_WORDS: &[&str] = - &["się", "cię", "mi", "mu", "ci", "go", "ją", "je", "nam", "wam", "im", "by", "że", "no", "mnie", "tobie", "jej", "ich"]; - -/// What Polish adds to its dictionary's tables. -const TUNING: Tuning = Tuning { - split: Split::Near { leading: LEADING_WORDS, trailing: TRAILING_WORDS }, - alphabet: "aioeznrwcysptkmdłuljągbhęśćóżfńźvqx", - mistype_cost, - ..Tuning::DEFAULT -}; - -/// Final devoicing makes a voiced consonant at the end of a word sound voiceless, so writing it -/// voiceless (`chlep` for `chleb`) is a likelier slip than any other typo. -fn mistype_cost(chars: &[char], i: usize, c: char) -> u32 { - let last = i + 1 == chars.len(); - if last && matches!((chars.get(i), c), (Some('p'), 'b') | (Some('t'), 'd') | (Some('k'), 'g')) { COST_ADD_DIACRITIC - 1 } else { COST_MISTYPE } -} - -/// Spelling suggestions for a Polish word, best first, at most `max`, in the word's letter case. -pub fn suggest(word: &str, max: usize) -> Vec { - suggest_in(&super::dictionary(), word, max) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize) -> Vec { - crate::affix_suggest::suggest_in(d, word, max, &TUNING) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::affix_suggest::{fold, weighted_distance}; - - fn has(word: &str, want: &str) { - let s = suggest(word, 8); - assert!(s.iter().any(|x| x == want), "{word} → {s:?}, want {want}"); - } - - #[test] - fn nosuggest_stems_and_forms_are_never_offered() { - crate::affix::tests::assert_nosuggest_is_honoured(suggest_in); - } - - #[test] - fn polish_misspellings_get_the_right_suggestion() { - crate::pl::test_dict::install(); - has("żeka", "rzeka"); - has("kturzy", "którzy"); - has("napewno", "na pewno"); - has("wogóle", "w ogóle"); - has("niewiem", "nie wiem"); - has("zolw", "żółw"); - has("gzegzolka", "gżegżółka"); - has("chuśtawka", "huśtawka"); - has("bżoza", "brzoza"); - has("pszyjaciel", "przyjaciel"); - has("warszawa", "Warszawa"); - has("Żeka", "Rzeka"); - has("ZOLW", "ŻÓŁW"); - has("konstytucija", "konstytucja"); - has("wziąść", "wziąć"); - has("dziewczyan", "dziewczyna"); - } - - #[test] - fn voiced_consonant_suggestion_ranks_first() { - crate::pl::test_dict::install(); - let result = suggest("chlep", 6); - assert_eq!(result.first().map(String::as_str), Some("chleb"), "{result:?}"); - } - - #[test] - fn the_likeliest_fix_comes_first() { - crate::pl::test_dict::install(); - assert_eq!(suggest("kturzy", 3).first().map(String::as_str), Some("którzy")); - assert_eq!(suggest("zolw", 3).first().map(String::as_str), Some("żółw")); - assert_eq!(suggest("napewno", 3).first().map(String::as_str), Some("na pewno")); - assert_eq!(suggest("żeka", 3).first().map(String::as_str), Some("rzeka")); - } - - #[test] - fn edge_cases_stay_empty_or_bounded() { - crate::pl::test_dict::install(); - assert!(suggest("", 5).is_empty()); - assert!(suggest("kot", 0).is_empty()); - assert!(suggest(&"a".repeat(41), 5).is_empty()); - assert!(suggest("dwa słowa", 5).is_empty()); - assert!(suggest("żółw", 5).len() <= 5); - assert!(!suggest("żółw", 5).contains(&"żółw".to_string())); - } - - #[test] - fn distance_weighs_diacritics_half() { - let map = vec![vec!['a', 'ą'], vec!['o', 'ó', 'u']]; - let related = |a: char, b: char| fold(a, &map) == fold(b, &map); - let c = |s: &str| s.chars().collect::>(); - assert_eq!(weighted_distance(&c("ktury"), &c("który"), &related, 9), 1); - assert_eq!(weighted_distance(&c("kot"), &c("kto"), &related, 9), 2); - assert_eq!(weighted_distance(&c("kot"), &c("kotek"), &related, 9), 4); - assert_eq!(weighted_distance(&c("kot"), &c("pies"), &related, 2), 3); - assert_eq!(weighted_distance(&c(""), &c("ab"), &related, 9), 4); - } - - #[test] - fn suggestions_are_fast_enough() { - crate::pl::test_dict::install(); - // Warm the dictionary, then time a worst case (a long word nothing near): the scan round. - let _ = suggest("kot", 1); - let t = std::time::Instant::now(); - for w in ["xqzvwpolskiegoslowa", "pszyjacielowi", "zażółćgęśląjaźń"] { - let _ = suggest(w, 6); - } - let ms = t.elapsed().as_secs_f64() * 1000.0; - eprintln!("3 suggestion calls: {ms:.0} ms"); - assert!(ms < 20_000.0, "{ms} ms"); - } -} diff --git a/crates/proof/src/pl/test_dict.rs b/crates/proof/src/pl/test_dict.rs deleted file mode 100644 index 80c1aa244..000000000 --- a/crates/proof/src/pl/test_dict.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! A tiny synthetic Polish Hunspell dictionary for tests (written for these tests; not taken from -//! any real dictionary). Real dictionaries are installed on the system, never bundled. - -/// Affix rules: a few suffixes and one prefix, plus the suggestion tables the Polish -/// suggestions use (`TRY`, `MAP` for diacritics, `REP` for common confusions). -pub const AFF: &str = "SET UTF-8 -TRY aioeznrwcysżtkpdmłjlubąęgchśóźćń -MAP 9 -MAP aą -MAP cć -MAP eę -MAP lł -MAP nń -MAP oó -MAP uó -MAP sś -MAP zżź -REP 6 -REP ż rz -REP rz ż -REP ch h -REP h ch -REP sz rz -REP ść ć -SFX A Y 1 -SFX A a ie a -SFX B Y 3 -SFX B y ego y -SFX B i iego i -SFX B i iej i -PFX N Y 1 -PFX N 0 nie . -"; - -/// Stems (the count line first, as in Hunspell files). -pub const DIC: &str = "69 -źdźbło -gżegżółka -chrząszcz -chleb -chłep -chlap -chlip -chlup -chlej -pięćdziesięciu -rzeka -którzy -żółw -zażółć -gęślą -jaźń -konstytucja -konstytucyjny/B -niebieski/B -niemiecki/B -ładnie/N -przyszliśmy -zrobilibyśmy -Warszawa/A -kot -biało -czerwony -polsko -mail -w -się -huśtawka -brzoza -przyjaciel -wziąć -dziewczyna -na -pewno -ogóle -nie -wiem -płynie -a -i -o -to -że -tak -jest -brzmi -trzcina/A -napisał -poszedł -spać -bo -wylała -świat -dom -ma -ala -kota -dzień -dobry -jutro -bardzo -dobrze -mamy -czas -ten -"; - -/// Make [`AFF`]/[`DIC`] the Polish dictionary (every test that needs one calls this; they all -/// install the same one, so tests running in parallel agree). -pub fn install() { - if let Err(e) = super::install_dictionary(AFF, DIC) { - panic!("test dictionary: {e}"); - } -} diff --git a/crates/proof/src/pt/grammar.rs b/crates/proof/src/pt/grammar.rs deleted file mode 100644 index 6b72e3226..000000000 --- a/crates/proof/src/pt/grammar.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! Portuguese grammar and punctuation checks: cheap, rule-based and tuned to stay quiet when -//! unsure (a missed mistake costs less than a wrong mark on correct text). Messages are in -//! Portuguese. -//! -//! - **Repeated word** (`ele ele`), except interjections and words Portuguese doubles on purpose -//! (`sim sim`, `não não`, `ora ora`). -//! - **Space before punctuation** (`bem ,`) and **double spaces**; emoticons (` :)`) and -//! ellipses are left alone. -//! - **Capital letter at the start of a sentence**, knowing Portuguese abbreviations (`etc.`, -//! `pág.`, `Prof.`, `Dr.`), initials, ordinal numbers (`5. colocado`), ellipses and dialogue -//! dashes (`— Vai — disse`), after which lower case is correct. -//! - **Doubled punctuation** (`,,`, `..`, `;;`). -//! -//! No comma rule (`que` opens clauses only sometimes) and no English rule (such as `a`/`an`) -//! applies to Portuguese text. - -use crate::{Issue, IssueKind, ProofLang, words}; - -/// Words Portuguese repeats on purpose: interjections, onomatopoeia and emphatic doubling. -const ALLOWED_REPEATS: &[&str] = &[ - "sim", "não", "ora", "olha", "já", "bem", "muito", "vai", "lá", "cá", "aí", "ha", "he", "hi", "oh", "ah", "ai", "ui", "uf", "uxa", "oba", "viva", - "psiu", "olá", "tchau", "xau", "adeus", "devagar", -]; - -/// Abbreviations written with a final period after which a sentence goes on in lower case -/// (lower case, without the period). -const ABBREVIATIONS: &[&str] = &[ - "etc", "ex", "pág", "págs", "séc", "sécs", "fl", "av", "vol", "p", "pp", "ed", "tel", "min", "max", "art", "par", "prof", "profa", "dr", "dra", - "sr", "sra", "srta", "eng", "arq", "op", "cit", "cf", "ibid", "id", "vide", "seg", "ter", "qua", "qui", "sex", "sáb", "dom", "jan", "fev", "mar", - "abr", "mai", "jun", "jul", "ago", "set", "out", "nov", "dez", "s.a", "d.c", "a.c", "pg", "ref", "cod", "dept", "univ", "inst", "lat", "ing", - "esp", "fr", "it", "al", "obs", "att", "obs", -]; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang: ProofLang::Pt } -} - -fn capitalize(w: &str) -> String { - crate::affix::capitalize(w) -} - -/// Is `prev`, before a period, an abbreviation, an initial, a number or a Roman numeral (after -/// which a lower-case word doesn't start a sentence)? -fn abbreviation_or_number(prev: &str) -> bool { - prev.chars().count() <= 1 - || prev.chars().any(|c| c.is_ascii_digit()) - || (prev.chars().all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))) - || ABBREVIATIONS.contains(&prev.to_lowercase().as_str()) -} - -const CLOSING: [char; 8] = ['"', '”', '’', '\'', ')', '»', ']', '\u{1}']; -const OPENING: [char; 8] = ['"', '“', '«', '(', '[', '\'', '‚', '—']; - -/// Does the gap between two words end a sentence: a `.`, `!` or `?` (not an ellipsis, not after -/// an abbreviation), then space, with nothing but quotes and brackets around? -fn ends_sentence(gap: &str, prev: &str) -> bool { - let t = gap.trim_start_matches(CLOSING); - let Some(end) = t.chars().next() else { return false }; - if !matches!(end, '.' | '!' | '?') || t.starts_with("..") { - return false; - } - if end == '.' && abbreviation_or_number(prev) { - return false; - } - let rest = t.trim_start_matches(['.', '!', '?']).trim_start_matches(CLOSING); - rest.starts_with(char::is_whitespace) && rest.trim_start().chars().all(|c| OPENING.contains(&c)) -} - -/// Grammar issues in a paragraph of Portuguese text, in order. -pub fn check(text: &str) -> Vec { - check_with(text, true) -} - -/// [`check`] where `capital_first` says whether the text's first word must start a sentence with a capital -/// (false for a list item, which may be a fragment). -pub fn check_with(text: &str, capital_first: bool) -> Vec { - let ws: Vec<(usize, usize)> = words(text); - let mut v = Vec::new(); - repeated_words(text, &ws, &mut v); - spaces(text, &mut v); - sentence_capitals(text, &ws, capital_first, &mut v); - doubled_punctuation(text, &mut v); - v.sort_by_key(|i| (i.start, i.end)); - v -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() - && gap.chars().all(char::is_whitespace) - && w0.chars().all(char::is_alphabetic) - && w0.to_lowercase() == w1.to_lowercase() - && !ALLOWED_REPEATS.contains(&w0.to_lowercase().as_str()) - { - v.push(issue(*a0, *b1, format!("Palavra repetida: \"{w1}\""), vec![w0.to_string()])); - } - } -} - -fn spaces(text: &str, v: &mut Vec) { - for (i, c) in text.char_indices() { - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - let flag = match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '”' | '’' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Espaço antes da pontuação".into(), vec![p.to_string()])); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Espaço duplo".into(), vec![" ".into()])); - } -} - -fn sentence_capitals(text: &str, ws: &[(usize, usize)], capital_first: bool, v: &mut Vec) { - // List items (`– maçãs,`) may start in lower case. - let list_item = text.trim_end().ends_with([',', ';']); - for (k, &(a, b)) in ws.iter().enumerate() { - let Some(w) = text.get(a..b) else { continue }; - let starts = if k == 0 { - let before = text.get(..a).unwrap_or(""); - capital_first && !list_item && before.chars().all(|c| c.is_whitespace() || OPENING.contains(&c)) - } else { - let Some(&(pa, pb)) = ws.get(k - 1) else { continue }; - ends_sentence(text.get(pb..a).unwrap_or(""), text.get(pa..pb).unwrap_or("")) - }; - if starts && w.chars().next().is_some_and(char::is_lowercase) && w.chars().all(char::is_alphabetic) { - v.push(issue(a, b, "Letra maiúscula no início da frase".into(), vec![capitalize(w)])); - } - } -} - -fn doubled_punctuation(text: &str, v: &mut Vec) { - let bytes = text.as_bytes(); - let mut i = 0; - while let Some(&b) = bytes.get(i) { - if !matches!(b, b',' | b'.' | b';') { - i += 1; - continue; - } - let run = bytes.get(i..).map_or(0, |r| r.iter().take_while(|&&x| x == b).count()); - let flag = match b { - b'.' => run == 2, - _ => run >= 2, - }; - if flag { - let end = i + run; - let suggestions: Vec = match b { - b'.' => vec![".".into(), "…".into()], - _ => vec![char::from(b).to_string()], - }; - v.push(issue(i, end, "Sinal repetido".into(), suggestions)); - } - i += run.max(1); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn messages(t: &str) -> Vec { - check(t).into_iter().map(|i| format!("{}@{}", i.message, &t[i.start..i.end])).collect() - } - - fn clean(t: &str) { - let m = messages(t); - assert!(m.is_empty(), "{t:?}: {m:?}"); - } - - #[test] - fn repeated_words() { - let v = check("Eu eu vi isso e ele ele saiu."); - assert_eq!(v.len(), 2, "{v:?}"); - assert_eq!(v[0].message, "Palavra repetida: \"eu\""); - assert_eq!(v[0].suggestions, ["Eu"]); - assert!(v.iter().all(|i| i.lang == ProofLang::Pt && i.kind == IssueKind::Grammar)); - clean("Sim, sim! Não, não! Ora ora, que surpresa."); - } - - #[test] - fn spaces_and_punctuation() { - let m = messages("Olá , mundo . Tudo bem ?"); - assert!(m.contains(&"Espaço antes da pontuação@ ,".to_string()), "{m:?}"); - assert!(m.contains(&"Espaço antes da pontuação@ .".to_string()), "{m:?}"); - assert!(m.contains(&"Espaço antes da pontuação@ ?".to_string()), "{m:?}"); - assert!(m.contains(&"Espaço duplo@ ".to_string()), "{m:?}"); - clean("Legal :) Muito bom :-) Ou :D Valor .5 e reticências ..."); - let m = messages("Falou,, e saiu.. depois;; fim."); - assert_eq!(m.iter().filter(|x| x.starts_with("Sinal repetido")).count(), 3, "{m:?}"); - clean("Esperei… e esperei... até ele voltar."); - } - - #[test] - fn sentence_start_capitals() { - let m = messages("isso é um teste. a segunda frase! terceira? ok."); - assert_eq!(m.iter().filter(|x| x.starts_with("Letra maiúscula")).count(), 4, "{m:?}"); - assert_eq!(check("ele tem razão.")[0].suggestions, ["Ele"]); - // Abbreviations, initials, ordinals, ellipses and dialogue dashes. - clean("Comprei frutas, etc. e depois saímos."); - clean("Conheci o Dr. Silva na Av. Paulista."); - clean("Zhou ficou em 5. lugar no séc. XX."); - clean("Esperei... e ele apareceu."); - clean("— Vai — disse ele. — Amanhã eu volto."); - clean("Ele disse: \"Volto amanhã\". E saiu."); - // A list item may start in lower case. - clean("maçãs,"); - } - - #[test] - fn correct_portuguese_text_is_clean() { - clean( - "O rato roeu a roupa do rei de Roma. Quem não tem cão caça com gato, \ - e quem tem paciência colhe o que plantou.", - ); - clean("Brasília, 12 de outubro de 2026 — uma data memorável."); - } - - proptest::proptest! { - #[test] - fn never_panics(s in "\\PC{0,80}") { - let _ = check(&s); - } - - #[test] - fn never_panics_on_portuguese_like_text(s in "[a-záàâãéêíóôõúüçA-Z ,.;:!?\"…\\-—]{0,60}") { - for i in check(&s) { - proptest::prop_assert!(i.start <= i.end && i.end <= s.len()); - proptest::prop_assert!(s.is_char_boundary(i.start) && s.is_char_boundary(i.end)); - } - } - } -} diff --git a/crates/proof/src/pt/mod.rs b/crates/proof/src/pt/mod.rs deleted file mode 100644 index ea91dbdb1..000000000 --- a/crates/proof/src/pt/mod.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Portuguese proofing. -//! -//! - **Spelling** against the Hunspell `pt_BR` dictionary installed on the system -//! ([`dictionary`], found by [`crate::hunspell`]: `pt_BR.aff`/`pt_BR.dic` — VERO and its -//! derivatives — from the OS, a package manager or LibreOffice; it isn't bundled). Without it -//! Portuguese text gets no spelling marks. Capitalized stems are names (`Brasília`); a sentence -//! may start with any word and headings may be in capitals. Hyphenated compounds are checked -//! part by part (`guarda-roupa`, `ex-marido`), and the user dictionary applies as in English. -//! - **Suggestions** tuned for Portuguese ([`suggest`]): missing accents first (`coraçao` → -//! `coração`, `saude` → `saúde`), then the dictionary's `REP` confusions (`seje` → `seja`), a -//! missing space, one-letter typos, and a dictionary scan for anything further off. -//! - **Grammar** ([`grammar`]): cheap, reliable rules with Portuguese messages. - -pub mod grammar; -mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// File names of a Portuguese Hunspell dictionary. -const NAMES: &[&str] = &["pt_BR", "pt-BR", "pt"]; - -/// The Portuguese dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Portuguese"); - -/// The Portuguese dictionary: the installed Hunspell `pt_BR` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available (Portuguese spelling is off -/// then; see [`has_dictionary`]). -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Use this Portuguese dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to -/// UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is a Portuguese dictionary available (spelling is checked)? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Is `word` spelled correctly in Portuguese? Numbers, single letters, short words in capitals -/// (acronyms: `USP`, `PT`), words with digits and words in other scripts are accepted, and so -/// is every word when no Portuguese dictionary is installed. -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool { - let Some(w) = crate::spelling::word_to_check(d, word, opts) else { - return true; - }; - if w.contains('-') { - return w.split('-').all(|p| p.chars().count() <= 1 || known(d, p)); - } - known(d, w) -} - -/// Is one word (no hyphens) in the dictionary or the user dictionary? -fn known(d: &AffixDict, w: &str) -> bool { - d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_dictionary_loads() { - test_dict::install(); - let d = dictionary(); - assert!(has_dictionary()); - assert!(d.len() > 50, "{}", d.len()); - assert!(d.rule_count() >= 5, "the test dictionary's affix rules"); - assert!(!d.rep().is_empty() && !d.map().is_empty() && !d.try_chars().is_empty()); - } - - #[test] - fn without_a_dictionary_nothing_is_flagged() { - let none = AffixDict::default(); - for w in ["coraçao", "saude", "xqzv", ""] { - assert!(is_correct_with(&none, w), "{w}"); - } - assert!(install_dictionary("FLAG long\n", "1\nkot\n").is_err(), "unsupported files are refused"); - } - - #[test] - fn portuguese_words_are_correct() { - test_dict::install(); - for w in [ - "coração", - "saudade", - "pássaro", - "saúde", - "árvore", - "escrever", - "computador", - "praia", - "conhecer", - "Brasília", - "São", - "autoestima", - "frequência", - "exceção", - "amanhã", - "vocês", - "coragem", - "parabéns", - "cantava", - "cantando", - "provável", - "prováveis", - "descobrir", - "trabalho", - "CORAGEM", - "USP", - "2026", - "e-mail", - "guarda-roupa", - "ex-marido", - "vice-presidente", - "e", - "não", - ] { - assert!(is_correct(w), "{w}"); - } - } - - #[test] - fn misspellings_are_flagged() { - test_dict::install(); - for w in ["coraçao", "saude", "trbalho", "excrever", "prsidente", "frequencia", "amnha", "cantaando"] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn forbidden_words_are_rejected() { - test_dict::install(); - // `frequencia/F` is listed with FORBIDDENWORD: the old spelling, never correct. - assert!(!is_correct("frequencia") && !is_correct("Frequencia") && !is_correct("FREQUENCIA")); - assert!(is_correct("frequência")); - } - - #[test] - fn user_dictionary_applies() { - test_dict::install(); - assert!(!is_correct("zxqportuguespalavra")); - crate::add_word("Zxqportuguespalavra"); - assert!(is_correct("zxqportuguespalavra")); - } - - #[test] - fn user_dictionary_accepts_a_hyphenated_word_whole() { - test_dict::install(); - let text = "Ele disse que está na Unigranrio-RJ."; - assert_eq!(crate::check_spelling_in(text, crate::ProofLang::Pt).len(), 1, "Unigranrio isn't in the dictionary"); - // Added as the context menu adds it, hyphen and all (#2082). - crate::add_word("Unigranrio-RJ"); - assert!(crate::check_spelling_in(text, crate::ProofLang::Pt).is_empty()); - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Portuguese checks - /// those words against the dictionary instead of accepting them. - #[test] - fn proofing_options_reach_the_portuguese_check() { - test_dict::install(); - let lang = crate::ProofLang::Pt; - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("PRAYA", lang, &defaults), "short capitals are acronyms by default"); - assert!(!crate::is_correct_in_with("PRAYA", lang, &check_caps)); - assert!(crate::is_correct_in_with("PRAIA", lang, &check_caps)); - assert!(crate::is_correct_in_with("praya2", lang, &defaults)); - assert!(!crate::is_correct_in_with("praya2", lang, &check_digits)); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("PRAYA"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/pt/suggest.rs b/crates/proof/src/pt/suggest.rs deleted file mode 100644 index f8f33bff6..000000000 --- a/crates/proof/src/pt/suggest.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Portuguese spelling suggestions, best first: the shared ranking ([`crate::affix_suggest`]) with -//! the words Portuguese runs into their neighbours (`nãoé` → `não é`, `paraela` → `para ela`) and -//! letters to try for a dictionary without a TRY line. - -use crate::affix::AffixDict; -use crate::affix_suggest::{Split, Tuning}; - -/// Short words often run into the next one (`não é`, `da água`, `para isso`). -const LEADING_WORDS: &[&str] = &[ - "a", "o", "e", "é", "de", "do", "da", "dos", "das", "em", "no", "na", "nos", "nas", "por", "para", "com", "sem", "sob", "sobre", "ao", "aos", - "à", "às", "que", "se", "não", "como", "mas", "mais", "já", "até", "entre", "contra", "perante", "desde", "após", "ante", "durante", "mediante", - "segundo", "conforme", "salvo", "fora", "onde", "quando", "bem", "mal", "pois", "logo", "portanto", "porém", "todavia", "contudo", "assim", - "tão", "tanto", "tudo", "nada", "algo", "cada", "outro", "mesmo", "próprio", "ainda", "tampouco", "quase", "talvez", "nunca", "sempre", -]; -/// Short words often run into the one before (`amo-te`, `deu-lhe`; in writing without the -/// hyphen: `amote`, `deulhe`). -const TRAILING_WORDS: &[&str] = &[ - "me", "te", "se", "lhe", "nos", "vos", "lhes", "o", "a", "os", "as", "ele", "ela", "eles", "elas", "isso", "isto", "aquilo", "mim", "ti", "si", - "eu", "tu", "que", "de", "em", "é", -]; - -/// What Portuguese adds to its dictionary's tables. -const TUNING: Tuning = Tuning { - split: Split::Near { leading: LEADING_WORDS, trailing: TRAILING_WORDS }, - alphabet: "aeiousrntmdlcpvgqbzfhjxçãõáéíóúâêôkw", - ..Tuning::DEFAULT -}; - -/// Spelling suggestions for a Portuguese word, best first, at most `max`, in the word's letter case. -pub fn suggest(word: &str, max: usize) -> Vec { - suggest_in(&super::dictionary(), word, max) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize) -> Vec { - crate::affix_suggest::suggest_in(d, word, max, &TUNING) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::affix_suggest::{fold, weighted_distance}; - - fn has(word: &str, want: &str) { - let s = suggest(word, 8); - assert!(s.iter().any(|x| x == want), "{word} → {s:?}, want {want}"); - } - - #[test] - fn nosuggest_stems_and_forms_are_never_offered() { - crate::affix::tests::assert_nosuggest_is_honoured(suggest_in); - } - - #[test] - fn portuguese_misspellings_get_the_right_suggestion() { - crate::pt::test_dict::install(); - has("coraçao", "coração"); - has("saude", "saúde"); - has("seje", "seja"); - has("excessão", "exceção"); - has("previlégio", "privilégio"); - has("prsidente", "presidente"); - has("provavel", "provável"); - has("trbalho", "trabalho"); - has("nãoé", "não é"); - has("paraela", "para ela"); - has("brasília", "Brasília"); - has("Coraçao", "Coração"); - } - - #[test] - fn the_likeliest_fix_comes_first() { - crate::pt::test_dict::install(); - assert_eq!(suggest("coraçao", 3).first().map(String::as_str), Some("coração")); - assert_eq!( - suggest("saude", 3).first().map(String::as_str), - Some("saúde"), - "an accent at the first difference beats one further right (saudê is a word too)" - ); - assert_eq!(suggest("seje", 3).first().map(String::as_str), Some("seja")); - } - - #[test] - fn edge_cases_stay_empty_or_bounded() { - crate::pt::test_dict::install(); - assert!(suggest("", 5).is_empty()); - assert!(suggest("bem", 0).is_empty()); - assert!(suggest(&"a".repeat(41), 5).is_empty()); - assert!(suggest("duas palavras", 5).is_empty()); - assert!(suggest_in(&crate::affix::AffixDict::default(), "coraçao", 5).is_empty()); - assert!(suggest("coração", 5).len() <= 5); - assert!(!suggest("coração", 5).contains(&"coração".to_string())); - } - - #[test] - fn distance_weighs_accents_half() { - let map = vec![vec!['a', 'á', 'à', 'â', 'ã'], vec!['c', 'ç']]; - let related = |a: char, b: char| fold(a, &map) == fold(b, &map); - let c = |s: &str| s.chars().collect::>(); - assert_eq!(weighted_distance(&c("coraçao"), &c("coração"), &related, 9), 1); - assert_eq!(weighted_distance(&c("coracao"), &c("coração"), &related, 9), 2, "ç and ã are two accent slips"); - assert_eq!(weighted_distance(&c("coro"), &c("coro"), &related, 9), 0); - assert_eq!(weighted_distance(&c("bem"), &c("bom"), &related, 9), 2); - assert_eq!(weighted_distance(&c("bem"), &c("bemte"), &related, 9), 4); - assert_eq!(weighted_distance(&c("bem"), &c("ver"), &related, 2), 3); - assert_eq!(weighted_distance(&c(""), &c("ab"), &related, 9), 4); - } - - #[test] - fn suggestions_are_fast_enough() { - crate::pt::test_dict::install(); - // Warm the dictionary, then time a worst case (a long word nothing near): the scan round. - let _ = suggest("bem", 1); - let t = std::time::Instant::now(); - for w in ["xqzvweportuguespalavra", "prsidentes", "paralelepípedo"] { - let _ = suggest(w, 6); - } - let ms = t.elapsed().as_secs_f64() * 1000.0; - eprintln!("3 suggestion calls: {ms:.0} ms"); - assert!(ms < 20_000.0, "{ms} ms"); - } -} diff --git a/crates/proof/src/pt/test_dict.rs b/crates/proof/src/pt/test_dict.rs deleted file mode 100644 index e02be3c16..000000000 --- a/crates/proof/src/pt/test_dict.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! A tiny synthetic Portuguese Hunspell dictionary for tests (written for these tests; not taken -//! from any real dictionary). Real dictionaries are installed on the system, never bundled. -//! -//! It exercises what VERO-like dictionaries use: `FLAG UTF-8` (multi-byte flag characters — -//! `F` marks a forbidden spelling), continuation classes (`provável` → `prováveis`), the -//! suggestion tables (`TRY`, `MAP` for accents, `REP` for common confusions) and one prefix. - -pub const AFF: &str = "SET UTF-8 -FLAG UTF-8 -FORBIDDENWORD F -TRY aeiousrntmdlcpvgqbzfhjxçãõáéíóúâêôkw -MAP 6 -MAP aáàâã -MAP eéê -MAP ií -MAP oóôõ -MAP uú -MAP cç -REP 4 -REP seje seja -REP excessão exceção -REP previlégio privilégio -REP analize análise -SFX A Y 2 -SFX A a ava a -SFX A a ando a -SFX B N 1 -SFX B 0 ável/C . -SFX C N 1 -SFX C vel veis vel -PFX X Y 1 -PFX X 0 des . -"; - -/// Stems (the count line first, as in Hunspell files). -pub const DIC: &str = "76 -coração -saudade -saudê -pássaro -saúde -árvore -escrever -computador -praia -conhecer -Brasília -São -Paulo -guarda -roupa -mail -marido -ex -vice -presidente -autoestima -frequência -frequencia/F -exceção -privilégio -análise -seja -trabalho -canta/A -prov/B -cobrir/X -avião -pássaros -amanhã -vocês -coragem -parabéns -está -ele -disse -existem -bem -não -sim -que -com -para -dos -das -pela -pelo -muito -mais -como -porque -aqui -também -já -ser -ter -ir -ver -dar -e -o -é -água -ela -da -do -de -em -no -na -um -uma -"; - -/// Make [`AFF`]/[`DIC`] the Portuguese dictionary (every test that needs one calls this; they all -/// install the same one, so tests running in parallel agree). -pub fn install() { - if let Err(e) = super::install_dictionary(AFF, DIC) { - panic!("test dictionary: {e}"); - } -} diff --git a/crates/proof/src/ru/extra.rs b/crates/proof/src/ru/extra.rs deleted file mode 100644 index 95397e617..000000000 --- a/crates/proof/src/ru/extra.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! Our supplement of Russian words older Hunspell dictionaries lack (`маркетплейс`, `промокод`, -//! `интеграция`, `бонусный`…), from `assets/spelling/ru-extra.txt`: each base form with its -//! declension, expanded here into every form. - -use std::collections::HashSet; -use std::sync::OnceLock; - -const EXTRA_RU: &str = include_str!("../../../../assets/spelling/ru-extra.txt"); - -/// Every form of the supplement: common words (lower case) and names (lower case too, but they -/// only match a word written with a capital). -struct Extra { - words: HashSet, - names: HashSet, -} - -fn extra() -> &'static Extra { - static E: OnceLock = OnceLock::new(); - E.get_or_init(|| { - let mut e = Extra { words: HashSet::new(), names: HashSet::new() }; - for line in EXTRA_RU.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')) { - let (base, class) = line.split_once('\t').map_or((line, ""), |(b, c)| (b.trim(), c.trim())); - let name = base.chars().next().is_some_and(char::is_uppercase); - let set = if name { &mut e.names } else { &mut e.words }; - for f in forms(base, class) { - set.insert(f.to_lowercase()); - } - } - e - }) -} - -/// Is `w` a form of a supplement word (a name only when written with a capital)? -pub(super) fn contains(w: &str) -> bool { - let e = extra(); - let lower = w.to_lowercase(); - e.words.contains(&lower) || (w.chars().next().is_some_and(char::is_uppercase) && e.names.contains(&lower)) -} - -fn is_velar(c: char) -> bool { - matches!(c, 'г' | 'к' | 'х') -} - -fn is_hushing(c: char) -> bool { - matches!(c, 'ж' | 'ш' | 'ч' | 'щ') -} - -/// The forms of `base` declined as `class` (see the supplement file's header). -fn forms(base: &str, class: &str) -> Vec { - let with = |stem: &str, ends: &[&str]| ends.iter().map(|e| format!("{stem}{e}")).collect::>(); - let last = |s: &str| s.chars().last().unwrap_or(' '); - if let Some(list) = class.strip_prefix('=') { - let mut v: Vec = list.split_whitespace().map(str::to_string).collect(); - v.push(base.to_string()); - return v; - } - match class { - "m" => { - let c = last(base); - let plural = if is_velar(c) || is_hushing(c) { "и" } else { "ы" }; - let gen_pl = if is_hushing(c) { "ей" } else { "ов" }; - let mut v = with(base, &["", "а", "у", "ом", "е", plural, gen_pl, "ам", "ами", "ах"]); - if is_hushing(c) || c == 'ц' { - v.push(format!("{base}ем")); - } - v - } - "f" => { - let Some(stem) = base.strip_suffix('а') else { return vec![base.to_string()] }; - let c = last(stem); - let plural = if is_velar(c) || is_hushing(c) { "и" } else { "ы" }; - let mut v = with(stem, &["а", plural, "е", "у", "ой", "ою", "", "ам", "ами", "ах"]); - if is_hushing(c) || c == 'ц' { - v.extend(with(stem, &["ей", "ею"])); - } - v - } - "fia" => { - base.strip_suffix("ия").map_or_else(|| vec![base.to_string()], |s| with(s, &["ия", "ии", "ию", "ией", "иею", "ий", "иям", "иями", "иях"])) - } - "fs" => base.strip_suffix('ь').map_or_else(|| vec![base.to_string()], |s| with(s, &["ь", "и", "ью", "ей", "ям", "ями", "ях"])), - "no" => base.strip_suffix('о').map_or_else(|| vec![base.to_string()], |s| with(s, &["о", "а", "у", "ом", "е", "", "ам", "ами", "ах"])), - "ovat" => { - // Verbs in -овать (`колеровать`, `администрировать`): the conjugation, the past, the - // imperative and the participles. - let Some(stem) = base.strip_suffix("овать") else { return vec![base.to_string()] }; - let mut v = with( - stem, - &[ - "овать", - "ую", - "уешь", - "ует", - "уем", - "уете", - "уют", - "овал", - "овала", - "овало", - "овали", - "уя", - "уй", - "уйте", - "уется", - "уются", - "уюсь", - "уешься", - "уемся", - "уетесь", - "овался", - "овалась", - "овалось", - "овались", - "оваться", - "овав", - "овавшись", - ], - ); - for participle in ["уемый", "ованный", "ующий", "овавший", "ующийся", "овавшийся"] { - let adj = format!("{stem}{participle}"); - if let Some(reflexive) = adj.strip_suffix("ся") { - v.extend(forms(reflexive, "adj").into_iter().map(|f| format!("{f}ся"))); - } else { - v.extend(forms(&adj, "adj")); - } - } - v.push(format!("{stem}ован")); - v.extend(with(stem, &["ована", "овано", "ованы"])); - v - } - "nie" => { - base.strip_suffix("ие").map_or_else(|| vec![base.to_string()], |s| with(s, &["ие", "ия", "ию", "ием", "ии", "ий", "иям", "иями", "иях"])) - } - "adj" => { - if let Some(stem) = base.strip_suffix("ый").or_else(|| base.strip_suffix("ой")) { - let mut v = with(stem, &["ого", "ому", "ым", "ом", "ая", "ой", "ую", "ое", "ые", "ых", "ыми"]); - v.push(base.to_string()); - return v; - } - let Some(stem) = base.strip_suffix("ий") else { return vec![base.to_string()] }; - let c = last(stem); - let ends: &[&str] = if is_velar(c) { - &["ий", "ого", "ому", "им", "ом", "ая", "ой", "ую", "ое", "ие", "их", "ими"] - } else if is_hushing(c) { - &["ий", "его", "ему", "им", "ем", "ая", "ей", "ую", "ее", "ие", "их", "ими"] - } else { - &["ий", "его", "ему", "им", "ем", "яя", "ей", "юю", "ее", "ие", "их", "ими"] - }; - with(stem, ends) - } - _ => vec![base.to_string()], - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn supplement_words_decline() { - for w in [ - "маркетплейс", - "маркетплейсов", - "маркетплейсах", - "Маркетплейсами", - "чекбоксы", - "промокода", - "кешбэки", - "инфографики", - "оплат", - "отгрузок", - "интеграций", - "соцсетей", - "согласий", - "бонусных", - "газоиспользующего", - "колеруется", - "заколерованный", - "онлайн", - "чат-ботом", - "Яндекса", - "Битриксе", - ] { - assert!(contains(w), "{w}"); - } - // Names need their capital; nonsense isn't in. - assert!(!contains("яндекс")); - assert!(!contains("маркетплейсыы")); - } - - #[test] - fn declension_classes() { - assert!(forms("бренд", "m").contains(&"брендов".to_string())); - assert!(forms("блог", "m").contains(&"блоги".to_string())); - assert!(forms("мерч", "m").contains(&"мерчей".to_string())); - assert!(forms("книга", "f").contains(&"книги".to_string())); - assert!(forms("синий", "adj").contains(&"синяя".to_string())); - assert!(forms("русский", "adj").contains(&"русская".to_string())); - assert!(forms("свежий", "adj").contains(&"свежая".to_string())); - assert_eq!(forms("медиа", ""), ["медиа"]); - let ovat = forms("модерировать", "ovat"); - for f in [ - "модерирую", - "модерирует", - "модерировали", - "модерируется", - "модерируемого", - "модерированной", - "модерирующая", - "модерирующемуся", - "модерирован", - ] { - assert!(ovat.contains(&f.to_string()), "{f}"); - } - assert!(forms("юрлицо", "no").contains(&"юрлиц".to_string())); - } -} diff --git a/crates/proof/src/ru/grammar.rs b/crates/proof/src/ru/grammar.rs deleted file mode 100644 index 70b9a1e62..000000000 --- a/crates/proof/src/ru/grammar.rs +++ /dev/null @@ -1,712 +0,0 @@ -//! Russian grammar and punctuation checks: cheap, rule-based and tuned to stay quiet when unsure -//! (a missed comma costs less than a wrong mark on correct text). Messages are in Russian. -//! -//! - **Repeated word** (`в в`), except words Russian doubles on purpose (`да да`, `так так`, -//! `ну ну`, `ха ха`, `очень очень`). -//! - **Space before punctuation** (`да ,`) and **double spaces**; emoticons (` :)`) and ellipses -//! are left alone. -//! - **Capital letter at the start of a sentence**, knowing Russian abbreviations (`т. е.`, `г.`, -//! `руб.`, `ул.`, `и т. д.`), initials, numbers, ellipses and dialogue dashes, after which lower -//! case is correct. -//! - **Missing comma** before conjunctions and relative pronouns that always open a clause: -//! `что`, `чтобы`, `поскольку`, `если`, `хотя` (not `хотя бы`) and the forms of `который`, and -//! before `потому`/`оттого` in `потому что`. Not after a conjunction or particle (`и что`, -//! `только что`, `даже если`, `так что`, `для того чтобы`), not after a preposition (`за что`, -//! `в котором`: the comma goes before it) and not inside `что-то`. -//! - **Doubled punctuation** (`,,` — often a typed opening quote — `..`, `;;`). -//! -//! No English rule (such as `a`/`an`) applies to Russian text. - -use crate::{Issue, IssueKind, ProofLang, words}; - -/// Words Russian repeats on purpose: interjections, onomatopoeia and emphatic doubling. -const ALLOWED_REPEATS: &[&str] = &[ - "да", - "нет", - "так", - "ну", - "ха", - "хи", - "хе", - "ой", - "ай", - "эй", - "ах", - "ох", - "эх", - "ага", - "угу", - "тук", - "кап", - "бах", - "бум", - "тик", - "так", - "ля", - "очень", - "много", - "давно", - "далеко", - "долго", - "еле", - "чуть", - "тихо", - "быстро", - "скорей", - "скорее", - "иди", - "давай", - "пока", - "ладно", - "вот", - "ещё", - "еще", - "всё", - "все", - "кто", - "что", - "то", -]; - -/// Abbreviations written with a final period after which a sentence goes on in lower case -/// (lower case, without the period). -const ABBREVIATIONS: &[&str] = &[ - "т", - "е", - "д", - "п", - "др", - "пр", - "см", - "ср", - "напр", - "т.е", - "т.д", - "т.п", - "т.к", - "т.н", - "т.о", - "г", - "гг", - "в", - "вв", - "н.э", - "до н.э", - "руб", - "коп", - "тыс", - "млн", - "млрд", - "трлн", - "ул", - "пр-т", - "пер", - "наб", - "пл", - "ш", - "д", - "кв", - "корп", - "стр", - "оф", - "эт", - "под", - "обл", - "р-н", - "пос", - "дер", - "им", - "св", - "проф", - "доц", - "акад", - "канд", - "наук", - "зав", - "зам", - "ред", - "изд", - "сост", - "пер", - "рис", - "табл", - "гл", - "разд", - "ст", - "ч", - "п", - "пп", - "абз", - "прим", - "прил", - "илл", - "с", - "сс", - "т", - "тт", - "вып", - "кн", - "экз", - "шт", - "кг", - "мин", - "сек", - "час", - "мес", - "нед", - "янв", - "фев", - "февр", - "мар", - "апр", - "авг", - "сент", - "окт", - "нояб", - "дек", - "пн", - "вт", - "чт", - "пт", - "сб", - "вс", - "англ", - "нем", - "фр", - "франц", - "лат", - "рус", - "греч", - "итал", - "исп", - "букв", - "перен", - "разг", - "устар", - "ок", - "ок", - "прибл", - "мл", - "ст", - "тел", - "моб", - "факс", - "эл", - "почт", - "вкл", - "искл", - "мм", - "см", - "дм", - "км", - "м", - "л", - "мл", - "т", - "ц", - "млн", - "долл", - "евро", - "др", - "проч", - "etc", - "vs", -]; - -/// Conjunctions and relative pronouns that open a clause: a comma comes before them. -const CLAUSE_OPENERS: &[&str] = &[ - "что", - "чтобы", - "поскольку", - "если", - "хотя", - "который", - "которая", - "которое", - "которые", - "которого", - "которой", - "которому", - "которым", - "котором", - "которую", - "которых", - "которыми", -]; - -/// Words after which a clause opener takes no comma of its own: conjunctions (`и что`, `а если`), -/// particles (`только что`, `даже если`, `лишь бы`), interrogatives and the first halves of -/// compound conjunctions (`потому что`, `так что`, `для того чтобы`, `с тем чтобы`, `несмотря на -/// то что`, `тогда как`, `как будто`). -const NO_COMMA_AFTER: &[&str] = &[ - "и", - "а", - "но", - "да", - "или", - "либо", - "ни", - "не", - "же", - "ли", - "бы", - "б", - "то", - "только", - "лишь", - "именно", - "даже", - "особенно", - "потому", - "оттого", - "поэтому", - "так", - "затем", - "тем", - "того", - "ввиду", - "вследствие", - "несмотря", - "благодаря", - "прежде", - "раньше", - "мере", - "пор", - "чем", - "будто", - "словно", - "точно", - "как", - "если", - "хотя", - "хоть", - "чтобы", - "что", - "кто", - "где", - "когда", - "почему", - "зачем", - "куда", - "откуда", - "сколько", - "разве", - "неужели", - "вот", - "вон", - "ну", - "уже", - "это", - "просто", - "чуть", - "мол", - "ведь", - "вроде", - "тоже", - "также", - "ещё", - "еще", - "всё", - "все", - "почти", - "вовсе", - "ой", - "ах", - "эх", - "ох", - "ага", - "нет", - "конечно", - "наверное", - "может", - "пусть", - "лучше", - "условии", - "случае", - "того", - "ради", - "раз", - "покуда", - "пока", - "после", - "перед", - "лишь", - "едва", - "чуть", - "дабы", - "кабы", - "коли", - "ежели", - "абы", - "вдруг", - "иначе", - "причём", - "причем", - "притом", - "зато", - "однако", - "тогда", - "сейчас", - "теперь", - "сначала", - "сперва", - "иногда", - "часто", - "всегда", - "никогда", - "т", - "др", -]; - -/// Prepositions: a relative pronoun or `что` after one takes the comma before the preposition -/// instead (`дом, в котором`, `то, за что`). -const PREPOSITIONS: &[&str] = &[ - "в", - "во", - "на", - "с", - "со", - "к", - "ко", - "о", - "об", - "обо", - "от", - "ото", - "до", - "из", - "изо", - "за", - "по", - "под", - "подо", - "над", - "надо", - "при", - "про", - "без", - "безо", - "для", - "у", - "через", - "чрез", - "между", - "меж", - "среди", - "из-за", - "из-под", - "около", - "возле", - "вокруг", - "мимо", - "против", - "вместо", - "кроме", - "сквозь", - "вдоль", - "внутри", - "вне", - "после", - "перед", - "пред", - "ради", - "благодаря", - "согласно", - "вопреки", - "навстречу", - "насчёт", - "насчет", - "ввиду", - "вследствие", - "посредством", - "путём", - "путем", - "относительно", - "касательно", - "вслед", - "ко", - "подле", - "близ", - "внутрь", - "поверх", -]; - -fn issue(start: usize, end: usize, message: String, suggestions: Vec) -> Issue { - Issue { start, end, kind: IssueKind::Grammar, message, suggestions, lang: ProofLang::Ru } -} - -fn capitalize(w: &str) -> String { - crate::affix::capitalize(w) -} - -/// Grammar issues in a paragraph of Russian text, in order. -pub fn check(text: &str) -> Vec { - check_with(text, true) -} - -/// [`check`] where `capital_first` says whether the text's first word must start a sentence with a capital -/// (false for a list item, which may be a fragment). -pub fn check_with(text: &str, capital_first: bool) -> Vec { - let ws: Vec<(usize, usize)> = words(text); - let mut v = Vec::new(); - repeated_words(text, &ws, &mut v); - spaces(text, &mut v); - sentence_capitals(text, &ws, capital_first, &mut v); - missing_commas(text, &ws, &mut v); - doubled_punctuation(text, &mut v); - v.sort_by_key(|i| (i.start, i.end)); - v -} - -fn repeated_words(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for pair in ws.windows(2) { - let [(a0, b0), (a1, b1)] = pair else { continue }; - let (Some(w0), Some(w1), Some(gap)) = (text.get(*a0..*b0), text.get(*a1..*b1), text.get(*b0..*a1)) else { continue }; - if !gap.is_empty() - && gap.chars().all(char::is_whitespace) - && w0.chars().all(char::is_alphabetic) - && w0.to_lowercase() == w1.to_lowercase() - && !ALLOWED_REPEATS.contains(&w0.to_lowercase().as_str()) - { - v.push(issue(*a0, *b1, format!("Повтор слова: «{w1}»"), vec![w0.to_string()])); - } - } -} - -fn spaces(text: &str, v: &mut Vec) { - for (i, c) in text.char_indices() { - if c != ' ' { - continue; - } - let rest = text.get(i + 1..).unwrap_or(""); - let mut it = rest.chars(); - let Some(p) = it.next() else { continue }; - let after = it.next(); - let flag = match p { - ',' => true, - // `;)` and `;-)` are emoticons. - ';' => !matches!(after, Some(')' | '(' | '-' | 'P' | 'D')), - // ` ...` is an ellipsis, ` .5` a number. - '.' => !matches!(after, Some('.')) && !after.is_some_and(|a| a.is_ascii_digit()), - // ` :)`, ` :-)`, ` :D` and similar are emoticons; ` ?!` is still a stray space. - ':' | '!' | '?' => after.is_none_or(|a| a.is_whitespace() || matches!(a, '"' | '»' | '”' | '’' | '!' | '?')), - _ => false, - }; - if flag { - v.push(issue(i, i + 1 + p.len_utf8(), "Лишний пробел перед знаком препинания".into(), vec![p.to_string()])); - } - } - for (i, _) in text.match_indices(" ") { - if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) { - continue; - } - v.push(issue(i, i + 2, "Двойной пробел".into(), vec![" ".into()])); - } -} - -/// Is `prev`, before a period, an abbreviation, an initial, a number or a Roman numeral (after -/// which a lower-case word doesn't start a sentence)? -fn abbreviation_or_number(prev: &str) -> bool { - prev.chars().count() <= 1 - || prev.chars().any(|c| c.is_ascii_digit()) - || (prev.chars().all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))) - || ABBREVIATIONS.contains(&prev.to_lowercase().as_str()) -} - -const CLOSING: [char; 8] = ['"', '»', '”', '’', '\'', ')', ']', '\u{1}']; -const OPENING: [char; 8] = ['«', '"', '„', '“', '(', '[', '\'', '‚']; - -/// Does the gap between two words end a sentence: a `.`, `!` or `?` (not an ellipsis, not after -/// an abbreviation), then space, with nothing but quotes and brackets around? -fn ends_sentence(gap: &str, prev: &str) -> bool { - let t = gap.trim_start_matches(CLOSING); - let Some(end) = t.chars().next() else { return false }; - if !matches!(end, '.' | '!' | '?') || t.starts_with("..") { - return false; - } - if end == '.' && abbreviation_or_number(prev) { - return false; - } - let rest = t.trim_start_matches(['.', '!', '?']).trim_start_matches(CLOSING); - rest.starts_with(char::is_whitespace) && rest.trim_start().chars().all(|c| OPENING.contains(&c)) -} - -fn sentence_capitals(text: &str, ws: &[(usize, usize)], capital_first: bool, v: &mut Vec) { - // List items (`– яблоки,`) may start in lower case. - let list_item = !capital_first || text.trim_end().ends_with([',', ';']); - for (k, &(a, b)) in ws.iter().enumerate() { - let Some(w) = text.get(a..b) else { continue }; - let starts = if k == 0 { - let before = text.get(..a).unwrap_or(""); - !list_item && before.chars().all(|c| c.is_whitespace() || OPENING.contains(&c)) - } else { - let Some(&(pa, pb)) = ws.get(k - 1) else { continue }; - ends_sentence(text.get(pb..a).unwrap_or(""), text.get(pa..pb).unwrap_or("")) - }; - if starts && w.chars().next().is_some_and(char::is_lowercase) && w.chars().all(char::is_alphabetic) { - v.push(issue(a, b, "Предложение начинается со строчной буквы".into(), vec![capitalize(w)])); - } - } -} - -fn missing_commas(text: &str, ws: &[(usize, usize)], v: &mut Vec) { - for k in 1..ws.len() { - let (Some(&(pa, pb)), Some(&(a, b))) = (ws.get(k - 1), ws.get(k)) else { continue }; - let (Some(prev), Some(w), Some(gap)) = (text.get(pa..pb), text.get(a..b), text.get(pb..a)) else { continue }; - if gap.is_empty() || !gap.chars().all(|c| c == ' ' || c == '\u{a0}') || !prev.chars().all(char::is_alphabetic) { - continue; - } - let lw = w.to_lowercase(); - let next = ws.get(k + 1).and_then(|&(na, nb)| text.get(na..nb)).map(str::to_lowercase); - let next_gap_plain = ws.get(k + 1).and_then(|&(na, _)| text.get(b..na)).is_some_and(|g| g.chars().all(|c| c == ' ' || c == '\u{a0}')); - // `потому что`, `оттого что`: the comma goes before the first word. - let compound = matches!(lw.as_str(), "потому" | "оттого") && next.as_deref() == Some("что") && next_gap_plain; - if !compound && !CLAUSE_OPENERS.contains(&lw.as_str()) { - continue; - } - // `хотя бы` is a particle (`хотя бы раз`). - if lw == "хотя" && next.as_deref() == Some("бы") { - continue; - } - let p = prev.to_lowercase(); - let p = p.as_str(); - if NO_COMMA_AFTER.contains(&p) || PREPOSITIONS.contains(&p) { - continue; - } - v.push(issue(pb, b, format!("Возможно, пропущена запятая перед «{w}»"), vec![format!(", {w}")])); - } -} - -fn doubled_punctuation(text: &str, v: &mut Vec) { - let bytes = text.as_bytes(); - let mut i = 0; - while let Some(&b) = bytes.get(i) { - if !matches!(b, b',' | b'.' | b';') { - i += 1; - continue; - } - let run = bytes.get(i..).map_or(0, |r| r.iter().take_while(|&&x| x == b).count()); - let flag = match b { - b'.' => run == 2, - _ => run >= 2, - }; - if flag { - let end = i + run; - let suggestions: Vec = match b { - // `,,` before a word, after a space: a typed opening quote. - b',' if text.get(end..).and_then(|t| t.chars().next()).is_some_and(char::is_alphabetic) - && (i == 0 || text.get(..i).is_some_and(|t| t.ends_with(char::is_whitespace))) => - { - vec!["«".into()] - } - b'.' => vec![".".into(), "…".into()], - _ => vec![char::from(b).to_string()], - }; - v.push(issue(i, end, "Повтор знака препинания".into(), suggestions)); - } - i += run.max(1); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn messages(t: &str) -> Vec { - check(t).into_iter().map(|i| format!("{}@{}", i.message, &t[i.start..i.end])).collect() - } - - fn clean(t: &str) { - let m = messages(t); - assert!(m.is_empty(), "{t:?}: {m:?}"); - } - - #[test] - fn repeated_words() { - let v = check("Я пошёл в в магазин и и купил хлеб."); - assert_eq!(v.len(), 2, "{v:?}"); - assert_eq!(v[0].message, "Повтор слова: «в»"); - assert_eq!(v[0].suggestions, ["в"]); - assert!(v.iter().all(|i| i.lang == ProofLang::Ru && i.kind == IssueKind::Grammar)); - clean("Да да, так так. Ха ха! Очень очень давно."); - } - - #[test] - fn spaces_and_punctuation() { - let m = messages("Мы пришли , а он ушёл . Правда ?"); - assert!(m.contains(&"Лишний пробел перед знаком препинания@ ,".to_string()), "{m:?}"); - assert!(m.contains(&"Лишний пробел перед знаком препинания@ .".to_string()), "{m:?}"); - assert!(m.contains(&"Лишний пробел перед знаком препинания@ ?".to_string()), "{m:?}"); - assert!(m.contains(&"Двойной пробел@ ".to_string()), "{m:?}"); - clean("Отлично :) Правда :-) Число .5 и многоточие ..."); - let m = messages("Он сказал ,,да» и ушёл.. Потом;; конец."); - assert_eq!(m.iter().filter(|x| x.starts_with("Повтор знака")).count(), 3, "{m:?}"); - assert_eq!(check("Он сказал ,,да».").iter().find(|i| i.message.starts_with("Повтор знака")).unwrap().suggestions, ["«"]); - clean("Ждал… и ждал... до утра."); - } - - #[test] - fn sentence_start_capitals() { - let m = messages("это первое предложение. а это второе! правда? да."); - assert_eq!(m.iter().filter(|x| x.starts_with("Предложение начинается")).count(), 4, "{m:?}"); - assert_eq!(check("мама мыла раму.")[0].suggestions, ["Мама"]); - // Abbreviations, initials, numbers, ellipses and dialogue dashes. - clean("Купили фрукты: яблоки, груши и т. д. и овощи."); - clean("В 1990 г. открылась фирма, т. е. наша компания."); - clean("Цена 500 руб. за штуку, адрес ул. Ленина, д. 5, кв. 3."); - clean("Встретил А. С. Пушкина и проф. Иванова."); - clean("Ждал... и наконец дождался."); - clean("– Правда? – спросил он. – Да, – ответила она."); - clean("Он сказал: «Вернусь». Потом ушёл."); - clean("яблоки,"); - } - - #[test] - fn missing_commas() { - let m = messages( - "Я знаю что он придёт. Это дом который построил Джек. Пришёл чтобы помочь. Ушёл потому что устал. Приходи если сможешь. Пошёл хотя устал.", - ); - for want in [ - "Возможно, пропущена запятая перед «что»@ что", - "Возможно, пропущена запятая перед «который»@ который", - "Возможно, пропущена запятая перед «чтобы»@ чтобы", - "Возможно, пропущена запятая перед «потому»@ потому", - "Возможно, пропущена запятая перед «если»@ если", - "Возможно, пропущена запятая перед «хотя»@ хотя", - ] { - assert!(m.contains(&want.to_string()), "{want}: {m:?}"); - } - assert_eq!(check("Знаю что да.")[0].suggestions, [", что"]); - // Conjunctions, particles, compound conjunctions and prepositions take no comma there. - clean("Я знаю, что он придёт, и что останется."); - clean("Он только что пришёл, так что всё в порядке."); - clean("Это дом, в котором я живу, и сад, через который иду."); - clean("Я сделаю это, даже если не успею, лишь бы ты был рад."); - clean("Пришёл для того чтобы помочь, а не чтобы мешать."); - clean("Ушёл, потому что устал, и не потому что обиделся."); - clean("Расскажи хотя бы что-нибудь, за что тебя хвалят."); - clean("Что ты делаешь? Который час?"); - clean("Кто-то что-то сказал."); - } - - #[test] - fn correct_russian_text_is_clean() { - clean("Съешь же ещё этих мягких французских булок, да выпей чаю. Мы пришли, когда уже стемнело."); - clean("Книга, которую я читаю, очень интересная, поэтому я её советую."); - } - - proptest::proptest! { - #[test] - fn never_panics(s in "\\PC{0,80}") { - let _ = check(&s); - } - - #[test] - fn never_panics_on_russian_like_text(s in "[а-яёА-ЯЁ ,.;:!?«»…\\-]{0,60}") { - for i in check(&s) { - proptest::prop_assert!(i.start <= i.end && i.end <= s.len()); - proptest::prop_assert!(s.is_char_boundary(i.start) && s.is_char_boundary(i.end)); - } - } - } -} diff --git a/crates/proof/src/ru/hyphen.rs b/crates/proof/src/ru/hyphen.rs deleted file mode 100644 index 33d19b771..000000000 --- a/crates/proof/src/ru/hyphen.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Russian word division, from the rules of Russian spelling (no pattern file): -//! -//! - every part keeps a vowel, and at least two letters stay on each side; -//! - `ь`, `ъ` and `й` stay with the letter before them (`паль-то`, `вой-на`), and a syllable -//! ends after `ъ` (`объ-ём`, `подъ-езд`); -//! - between two vowels with one consonant between them the consonant starts the next syllable -//! (`ма-ма`, `ро-ди-на`); -//! - a cluster of consonants is divided after its first consonant (`сес-тра`, `кар-ман`, -//! `кас-са`, `под-ход`), which also keeps the common prefixes `под-`, `раз-`, `без-` whole; -//! - two vowels side by side may be divided (`по-эт`, `ма-як`). - -use super::is_cyrillic; - -/// Russian vowels. -fn is_vowel(c: char) -> bool { - matches!(c, 'а' | 'е' | 'ё' | 'и' | 'о' | 'у' | 'ы' | 'э' | 'ю' | 'я') -} - -/// Letters that never begin a line: the soft and hard signs and `й`. -fn sticks_left(c: char) -> bool { - matches!(c, 'ь' | 'ъ' | 'й') -} - -/// Break points of a lowercase Russian word (letters only): each is the index of the letter that -/// starts a new line. Empty for words with letters outside the Russian alphabet. -pub fn points(c: &[char]) -> Vec { - let n = c.len(); - if n < 4 || !c.iter().all(|&x| is_cyrillic(x) && x.is_alphabetic()) { - return vec![]; - } - let vowels: Vec = (0..n).filter(|&i| c.get(i).is_some_and(|&x| is_vowel(x))).collect(); - let mut out = vec![]; - for pair in vowels.windows(2) { - let [a, b] = pair else { continue }; - let (start, end) = (a + 1, *b); - // The letters that must stay on the left: signs and `й` right after the vowel or after a - // consonant of the cluster. - let mut cut = if start == end { - // Two vowels side by side. - end - } else if c.get(end - 1) == Some(&'ъ') { - // The hard sign closes its syllable (`объ-ём`, `подъ-езд`). - end - } else { - // The cluster's consonants, skipping signs that stick to the letter before. - let consonants: Vec = (start..end).filter(|&i| c.get(i).is_some_and(|&x| !sticks_left(x))).collect(); - match consonants.as_slice() { - // Only `й` (`май-онез`): after it. - [] => end, - // One consonant: it starts the next syllable (`ма-ма`, `паль-то`). - [only] => *only, - // Several: after the first one (`сес-тра`, `кар-ман`). - [_, second, ..] => *second, - } - }; - // A sign or `й` never starts a line. - while c.get(cut).is_some_and(|&x| sticks_left(x)) && cut < end { - cut += 1; - } - // Cuts come in increasing order (each lies after the previous pair's vowel), so a repeat - // can only be the last one: no scan of the whole list per syllable (#3657). - if cut >= 2 && n - cut >= 2 && out.last().is_none_or(|&l| l < cut) { - out.push(cut); - } - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - - fn h(w: &str) -> String { - let c: Vec = w.chars().collect(); - let p = points(&c); - c.iter().enumerate().map(|(i, ch)| if p.contains(&i) { format!("-{ch}") } else { ch.to_string() }).collect() - } - - #[test] - fn russian_words_divide_by_the_rules() { - assert_eq!(h("мама"), "ма-ма"); - assert_eq!(h("родина"), "ро-ди-на"); - assert_eq!(h("сестра"), "сес-тра"); - assert_eq!(h("карман"), "кар-ман"); - assert_eq!(h("касса"), "кас-са"); - assert_eq!(h("подход"), "под-ход"); - assert_eq!(h("пальто"), "паль-то"); - assert_eq!(h("объём"), "объ-ём"); - assert_eq!(h("подъезд"), "подъ-езд"); - assert_eq!(h("семья"), "се-мья"); - assert_eq!(h("вьюга"), "вью-га"); - assert_eq!(h("объявление"), "объ-яв-ле-ние"); - assert_eq!(h("война"), "вой-на"); - assert_eq!(h("чайник"), "чай-ник"); - assert_eq!(h("поэзия"), "по-э-зия"); - assert_eq!(h("маяк"), "ма-як"); - assert_eq!(h("жидкие"), "жид-кие"); - assert_eq!(h("обои"), "обои"); - assert_eq!(h("декоративная"), "де-ко-ра-тив-ная"); - assert_eq!(h("штукатурка"), "шту-ка-тур-ка"); - } - - #[test] - fn short_words_and_other_scripts_stay_whole() { - for w in ["дом", "ёж", "оса", "iPhone", "дом2", ""] { - let c: Vec = w.chars().collect(); - assert!(points(&c).is_empty(), "{w}"); - } - } - - #[test] - fn no_line_starts_with_a_sign_or_ends_with_one_letter() { - for w in ["объявление", "подъезд", "семья", "вьюга", "майонез", "яйцо", "ауди", "аорта", "оазис", "ёлочка"] - { - let c: Vec = w.chars().collect(); - for p in points(&c) { - assert!(p >= 2 && c.len() - p >= 2, "{w}: {p}"); - assert!(c.get(p).is_some_and(|&x| !sticks_left(x)), "{w}: {p}"); - assert!(c.get(..p).is_some_and(|l| l.iter().any(|&x| is_vowel(x))), "{w}: {p}"); - assert!(c.get(p..).is_some_and(|r| r.iter().any(|&x| is_vowel(x))), "{w}: {p}"); - } - } - } - - proptest::proptest! { - #[test] - fn never_panics(s in "[а-яё]{0,30}") { - let c: Vec = s.chars().collect(); - for p in points(&c) { - proptest::prop_assert!(p >= 2 && p + 2 <= c.len()); - } - } - } -} diff --git a/crates/proof/src/ru/mod.rs b/crates/proof/src/ru/mod.rs deleted file mode 100644 index 67be70858..000000000 --- a/crates/proof/src/ru/mod.rs +++ /dev/null @@ -1,485 +0,0 @@ -//! Russian proofing. -//! -//! - **Spelling** against a Russian Hunspell dictionary installed on the system ([`dictionary`], -//! found by [`crate::hunspell`]: `ru_RU.aff`/`ru_RU.dic` from the OS, a package manager or -//! LibreOffice's `dict-ru`; it isn't bundled). Without it Russian text gets no spelling marks. -//! `ё` is optional, as in ordinary Russian writing: `елка` and `ёлка`, `все` and `всё` are -//! accepted whichever one the dictionary lists. Short words in capitals are abbreviations -//! (`ООО`, `НДС`). Hyphenated words are checked whole first, then by their parts, knowing the -//! particles (`кто-то`, `где-нибудь`, `давай-ка`, `всё-таки`), `кое-`/`кой-`, adverbs with -//! `по-` (`по-русски`, `по-новому`) and compound adjectives whose first part ends in a linking -//! vowel (`северо-западный`, `сине-зелёный`). Compounds written solid are checked the same way -//! (`высокоэластичный`, `газоиспользующий`), and so are adjectives, participles and verbal nouns -//! with `не` (`невскрытый`, `непредоставление`; not verbs: `незнаю` stays wrong). Our supplement -//! adds words older dictionaries lack (`маркетплейс`, `промокод`, `интеграция`: [`extra`]). -//! Words in another script (`iPhone`), with digits or superscripts (`м²`), in camel case -//! (`ВКонтакте`) or abbreviations with an ending (`ГИПа`, `ПДн`) are left alone. -//! - **Suggestions** tuned for Russian ([`suggest()`]): `е`/`ё`, the common confusions (`тся`/ -//! `ться`, `жи`/`ши` written with `ы`, `ча`/`ща` with `я`, voiced and voiceless consonant pairs, -//! doubled consonants), a missing space (`незнаю` → `не знаю`, `вобщем` → `в общем`), one-letter -//! typos, and a dictionary scan for anything further off. -//! - **Grammar** ([`grammar`]): cheap, reliable rules with Russian messages. -//! - **Hyphenation** ([`hyphen`]): the rules of Russian word division, written as code. - -mod extra; -pub mod grammar; -pub mod hyphen; -mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::affix::AffixDict; -use crate::slot::DictSlot; - -pub use suggest::suggest; - -/// File names of a Russian Hunspell dictionary. -const NAMES: &[&str] = &["ru_RU", "ru-RU", "ru"]; - -/// The Russian dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Russian"); - -/// The Russian dictionary: the installed Hunspell `ru_RU` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available (Russian spelling is off -/// then; see [`has_dictionary`]). -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Use this Russian dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to -/// UTF-8), for example one the user picked. On error the current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is a Russian dictionary available (spelling is checked)? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Is `c` a letter of the Cyrillic script (the only one the Russian dictionary covers)? -pub(crate) fn is_cyrillic(c: char) -> bool { - matches!(c, '\u{0400}'..='\u{052F}') -} - -/// Particles joined to a word with a hyphen (`кто-то`, `где-нибудь`, `давай-ка`, `всё-таки`, -/// `он-де`). -const PARTICLES: &[&str] = &["то", "либо", "нибудь", "ка", "таки", "де", "с"]; - -/// Prefixes joined to a pronoun with a hyphen (`кое-кто`, `кое-где`, `кой-какой`). -const PRONOUN_PREFIXES: &[&str] = &["кое", "кой"]; - -/// Words of fewer letters than this written in capitals are taken as abbreviations (`ООО`, `НДС`, -/// `МГУ`, `ГОСТ`). -const ABBREVIATION_LETTERS: usize = 6; - -/// Is `word` spelled correctly in Russian? Numbers, single letters, short words in capitals, -/// words with digits and words in other scripts are accepted, and so is every word when no -/// Russian dictionary is installed. -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words in UPPERCASE off, -/// short words in capitals are checked against the dictionary too, and with Ignore words that -/// contain numbers off, so are words with digits or superscripts. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary (an empty one accepts everything). -pub fn is_correct_with(d: &AffixDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &AffixDict, word: &str, opts: &crate::Options) -> bool { - if d.is_empty() { - return true; // no Russian dictionary installed: no spelling marks - } - let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '-'); - let w = w.trim_matches('-'); - let n = w.chars().count(); - if n <= 1 || (opts.ignore_numbers && w.chars().any(char::is_numeric)) || !w.chars().any(char::is_alphabetic) { - return true; - } - if opts.ignore_uppercase && w.chars().all(|c| !c.is_lowercase()) && n <= ABBREVIATION_LETTERS { - return true; - } - // Camel case (`ВКонтакте`, `МирКрасок`): a brand or an identifier, not a dictionary word. - let cs: Vec = w.chars().collect(); - if cs.windows(2).any(|p| matches!(p, [a, b] if a.is_lowercase() && b.is_uppercase())) { - return true; - } - // An abbreviation with lower-case letters or an ending (`ГИПа`, `ПДн`, `ИСПДн`). - if n <= 10 && cs.iter().take(2).all(|c| c.is_uppercase()) && cs.iter().any(|c| c.is_lowercase()) { - return true; - } - // Another script, or a mix of scripts (`iPhone`, `SMS-ка`): not this dictionary's to judge. - if w.chars().any(|c| c.is_alphabetic() && !is_cyrillic(c)) { - return true; - } - if known(d, w) { - return true; - } - if w.contains('-') { - return hyphenated(d, w, opts); - } - solid_compound(d, w) -} - -/// Endings of adjectives, participles and nouns that are written together with `не` -/// (`невскрытый`, `неисполнение`, `неготовность`). Verb endings aren't among them: `не` with a -/// verb is written apart (`не знаю`). -const NE_ENDINGS: &[&str] = &[ - "ый", - "ий", - "ой", - "ая", - "яя", - "ое", - "ее", - "ые", - "ие", - "ого", - "его", - "ому", - "ему", - "ым", - "ую", - "юю", - "ых", - "их", - "ыми", - "ими", - "ние", - "ния", - "нию", - "нием", - "нии", - "ость", - "ости", - "остью", - "остей", - "остям", - "остями", - "остях", -]; - -/// Prefixes Russian joins to any word (`ультрастойкий`, `супермаркет`, `экопакет`). -const WORD_PREFIXES: &[&str] = &[ - "ультра", - "супер", - "гипер", - "мега", - "микро", - "макро", - "мульти", - "мини", - "макси", - "анти", - "псевдо", - "квази", - "контр", - "сверх", - "полу", - "само", - "авто", - "видео", - "аудио", - "фото", - "радио", - "теле", - "эко", - "био", - "нано", - "агро", - "медиа", - "интернет", - "веб", - "бизнес", - "онлайн", - "спец", - "гос", - "мед", - "юр", - "орг", - "проф", - "соц", -]; - -/// Prefixes that make a verb perfective (`с-генерировать`, `от-модерировать`, `пере-загрузить`). -const VERB_PREFIXES: &[&str] = &[ - "с", "со", "за", "по", "про", "на", "вы", "от", "ото", "пере", "под", "подо", "при", "раз", "рас", "из", "ис", "у", "о", "об", "обо", "до", - "недо", "пред", "в", "во", "вз", "вс", -]; - -/// Endings of verb forms (infinitive, present, past, reflexive). -const VERB_ENDINGS: &[&str] = &[ - "ть", "ться", "ти", "чь", "ю", "ешь", "ет", "ем", "ете", "ют", "ишь", "ит", "им", "ите", "ят", "ал", "ала", "ало", "али", "ил", "ила", "ило", - "или", "ся", "сь", -]; - -/// A word written solid from known parts: `не` with an adjective, participle or verbal noun -/// (`невскрытой`), or two stems joined by a linking vowel (`высокоэластичная`, `газоиспользующего`). -fn solid_compound(d: &AffixDict, w: &str) -> bool { - let lower = w.to_lowercase(); - let chars: Vec = lower.chars().collect(); - let n = chars.len(); - if !(7..=40).contains(&n) { - return false; - } - if let Some(rest) = lower.strip_prefix("не") - && rest.chars().count() >= 4 - && NE_ENDINGS.iter().any(|e| rest.ends_with(e)) - && known(d, rest) - { - return true; - } - // A productive prefix (`ультрастойкий`, `экопакет`, `микроцемент`) on a known word. - if WORD_PREFIXES.iter().any(|p| lower.strip_prefix(p).is_some_and(|rest| rest.chars().count() >= 4 && known(d, rest))) { - return true; - } - // A verb with a perfective prefix (`сгенерировать`, `отмодерировали`) whose unprefixed form is - // known. - if VERB_PREFIXES.iter().any(|p| { - lower.strip_prefix(p).is_some_and(|rest| rest.chars().count() >= 5 && VERB_ENDINGS.iter().any(|e| rest.ends_with(e)) && known(d, rest)) - }) { - return true; - } - (4..=n.saturating_sub(4)).any(|i| { - let (Some(left), Some(right)) = (chars.get(..i), chars.get(i..)) else { return false }; - if !matches!(left.last(), Some('о' | 'е')) { - return false; - } - let left: String = left.iter().collect(); - let right: String = right.iter().collect(); - known(d, &right) && (known(d, &left) || linking_stem(d, &left)) - }) -} - -/// Is `part`, ending in a linking vowel, a known word's stem (`северо` → `север`, `сине` → -/// `синий`, `газо` → `газ`)? -fn linking_stem(d: &AffixDict, part: &str) -> bool { - let Some(stem) = part.strip_suffix(['о', 'е', 'О', 'Е']) else { return false }; - stem.chars().count() >= 2 && ["", "ый", "ий", "ой", "ь"].iter().any(|end| known(d, &format!("{stem}{end}"))) -} - -/// Is one word in the dictionary or the user dictionary, with `ё` optional? -fn known(d: &AffixDict, w: &str) -> bool { - if d.check(w) || crate::in_user_dictionary(&w.to_lowercase()) || extra::contains(w) { - return true; - } - // `ё` written as `е` is ordinary Russian: try the dictionary's `ё` spelling… - let without = w.replace('ё', "е").replace('Ё', "Е"); - if without != w && (d.check(&without) || crate::in_user_dictionary(&without.to_lowercase())) { - return true; - } - // …and `е` where the dictionary has `ё` (one or two of them: `Черемушки`, `еще`). - let e_positions: Vec = without.char_indices().filter(|(_, c)| matches!(c, 'е' | 'Е')).map(|(i, _)| i).collect(); - if e_positions.is_empty() || e_positions.len() > 4 { - return false; - } - let swap = |s: &str, i: usize| -> Option { - let c = s.get(i..)?.chars().next()?; - let yo = if c == 'Е' { "Ё" } else { "ё" }; - Some(format!("{}{}{}", s.get(..i)?, yo, s.get(i + c.len_utf8()..)?)) - }; - for (k, &i) in e_positions.iter().enumerate() { - let Some(one) = swap(&without, i) else { continue }; - if d.check(&one) { - return true; - } - for &j in e_positions.get(k + 1..).unwrap_or_default() { - // `ё` is two bytes like `е`, so later offsets still hold. - if swap(&one, j).is_some_and(|two| d.check(&two)) { - return true; - } - } - } - false -} - -/// A hyphenated word that isn't in the dictionary whole: its parts, with the particles, -/// prefixes and linking vowels Russian joins with a hyphen. -fn hyphenated(d: &AffixDict, w: &str, opts: &crate::Options) -> bool { - let parts: Vec<&str> = w.split('-').collect(); - if parts.iter().any(|p| p.is_empty()) || parts.len() > 4 { - return false; - } - let lower = w.to_lowercase(); - let lp: Vec<&str> = lower.split('-').collect(); - // `кто-то`, `где-нибудь`, `всё-таки`: a word and its particles. - let first_particle = lp.iter().skip(1).position(|p| PARTICLES.contains(p)).map(|i| i + 1); - if let Some(k) = first_particle - && lp.iter().skip(k).all(|p| PARTICLES.contains(p)) - { - let base = parts.get(..k).map(|b| b.join("-")).unwrap_or_default(); - return known(d, &base) || (base.contains('-') && hyphenated(d, &base, opts)); - } - match lp.as_slice() { - // `кое-кто`, `кой-какой`. - [prefix, rest] if PRONOUN_PREFIXES.contains(prefix) => return parts.get(1).is_some_and(|r| known(d, r)), - // `по-русски` (from `русский`), `по-новому`, `по-моему`. - ["по", rest] => { - let Some(r) = parts.get(1) else { return false }; - return known(d, r) || ((rest.ends_with("ски") || rest.ends_with("цки") || rest.ends_with("ьи")) && known(d, &format!("{r}й"))); - } - _ => {} - } - // Compounds part by part: `северо-западный`, `сине-зелёный`, `интернет-магазин`, `из-за`. - let last = parts.len() - 1; - parts.iter().enumerate().all(|(i, p)| { - // An abbreviation part (`ИИ-консультант`, `SMS-рассылка`) or a known word. - if known(d, p) || (opts.ignore_uppercase && p.chars().all(|c| !c.is_lowercase()) && p.chars().count() <= ABBREVIATION_LETTERS) { - return true; - } - // A first part with a linking vowel: the stem (`северо` → `север`) or its adjective - // (`сине` → `синий`, `бело` → `белый`). - i != last && linking_stem(d, p) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_dictionary_loads() { - test_dict::install(); - let d = dictionary(); - assert!(has_dictionary()); - assert!(d.len() > 50, "{}", d.len()); - } - - #[test] - fn without_a_dictionary_nothing_is_flagged_and_nothing_suggested() { - let none = AffixDict::default(); - for w in ["превет", "сдесь", "xqzv", ""] { - assert!(is_correct_with(&none, w), "{w}"); - } - assert!(suggest::suggest_in(&none, "превет", 5).is_empty()); - } - - #[test] - fn russian_words_are_correct() { - test_dict::install(); - for w in [ - "привет", - "Привет", - "ПРИВЕТ", - "дом", - "дома", - "домами", - "красный", - "красного", - "красной", - "читать", - "читаем", - "читаются", - "ёлка", - "елка", - "всё", - "все", - "ещё", - "еще", - "Москва", - "Москве", - "кто-то", - "где-нибудь", - "что-либо", - "давай-ка", - "всё-таки", - "все-таки", - "кое-кто", - "по-русски", - "по-новому", - "северо-западный", - "сине-зелёный", - "сине-зеленый", - "из-за", - "ООО", - "НДС", - "2026", - "5-й", - "iPhone", - "e-mail", - "в", - "м²", - "ВКонтакте", - "МирКрасок", - "ГИПа", - "ПДн", - "ИИ-консультант", - "маркетплейсов", - "промокода", - "невскрытой", - "некрасного", - "непредоставления", - "высокоэластичная", - "краснозелёный", - "Пн-Пт", - "ультракрасный", - "суперновый", - "прочитать", - "администрируется", - "юрлица", - ] { - assert!(is_correct(w), "{w}"); - } - } - - #[test] - fn misspellings_are_flagged() { - test_dict::install(); - for w in [ - "превет", - "сдесь", - "жыть", - "чясто", - "незнаю", - "нечитаю", - "вобщем", - "москва", - "здраствуй", - "кто-нибуть", - "по-руски", - "северо-зопадный", - "ПРИВЕТТТ", - "высокозопадный", - ] { - assert!(!is_correct(w), "{w}"); - } - } - - #[test] - fn user_dictionary_applies() { - test_dict::install(); - assert!(!is_correct("зкщфрусскоеслово")); - crate::add_word("Зкщфрусскоеслово"); - assert!(is_correct("зкщфрусскоеслово")); - } - - /// With Ignore words in UPPERCASE or Ignore words that contain numbers off, Russian checks - /// those words against the dictionary instead of accepting them. - #[test] - fn proofing_options_reach_the_russian_check() { - test_dict::install(); - let lang = crate::ProofLang::Ru; - let defaults = crate::Options::default(); - let check_caps = crate::Options { ignore_uppercase: false, ..defaults }; - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("ПРЕВЕТ", lang, &defaults), "short capitals are abbreviations by default"); - assert!(!crate::is_correct_in_with("ПРЕВЕТ", lang, &check_caps)); - assert!(crate::is_correct_in_with("ПРИВЕТ", lang, &check_caps)); - assert!(crate::is_correct_in_with("ИИ-консультант", lang, &defaults)); - assert!(!crate::is_correct_in_with("ИИ-консультант", lang, &check_caps), "a part in capitals is checked too"); - assert!(crate::is_correct_in_with("превет2", lang, &defaults)); - assert!(!crate::is_correct_in_with("превет2", lang, &check_digits)); - assert!(!crate::is_correct_in_with("м²", lang, &check_digits), "a superscript is a number too"); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("ПРЕВЕТ"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/ru/suggest.rs b/crates/proof/src/ru/suggest.rs deleted file mode 100644 index 135f4152f..000000000 --- a/crates/proof/src/ru/suggest.rs +++ /dev/null @@ -1,258 +0,0 @@ -//! Russian spelling suggestions, best first: the shared ranking ([`crate::affix_suggest`]) with -//! Russian's own letter groups and confusions, since Russian Hunspell dictionaries carry no `MAP` -//! or `REP` tables (`е`/`ё` first, then unstressed vowels, voiced and voiceless pairs, `жы` → -//! `жи`, `тся`/`ться`, dropped or doubled consonants; up to three letters changed), and its rule -//! for a missing space (`незнаю` → `не знаю`, `вобщем` → `в общем`). - -use crate::affix::AffixDict; -use crate::affix_suggest::{COST_OTHER_MAP, COST_SPLIT, COST_SPLIT_LONG, Split, Tuning}; - -/// Letters confused with each other: a slip inside a group is cheaper than another typo. -const MAP: &[&str] = &["её", "оа", "еия", "зс", "дт", "бп", "вф", "гк", "жш", "ьъ", "еэ", "ыи"]; -/// Common confusions: a written sequence and what was meant. -const REP: &[(&str, &str)] = &[ - ("жы", "жи"), - ("шы", "ши"), - ("чя", "ча"), - ("щя", "ща"), - ("чю", "чу"), - ("щю", "щу"), - ("тся", "ться"), - ("ться", "тся"), - ("сч", "щ"), - ("щ", "сч"), - ("нн", "н"), - ("н", "нн"), - ("сс", "с"), - ("с", "сс"), - ("лл", "л"), - ("л", "лл"), - ("тт", "т"), - ("т", "тт"), - ("мм", "м"), - ("м", "мм"), - ("рр", "р"), - ("р", "рр"), - ("ье", "ъе"), - ("ъе", "ье"), - ("раст", "рост"), - ("рост", "раст"), - ("лож", "лаг"), - ("лаг", "лож"), - // Unpronounced consonants: `здраствуй`, `чуствовать`, `солнце`, `лесница`, `праздник`. - ("аст", "авст"), - ("уст", "увст"), - ("лнц", "лц"), - ("лц", "лнц"), - ("сн", "стн"), - ("стн", "сн"), - ("зн", "здн"), - ("здн", "зн"), - ("ц", "тс"), - ("тс", "ц"), -]; -/// Short words often run into the next one (`не знаю`, `в общем`, `на верху`, `потому что`). -const LEADING_WORDS: &[&str] = &[ - "не", - "ни", - "в", - "во", - "на", - "с", - "со", - "к", - "ко", - "о", - "об", - "обо", - "от", - "ото", - "до", - "из", - "изо", - "за", - "по", - "под", - "над", - "при", - "про", - "без", - "для", - "у", - "а", - "и", - "но", - "да", - "же", - "как", - "так", - "то", - "что", - "уже", - "еще", - "ещё", - "все", - "всё", - "потому", - "чтобы", - "это", -]; -/// Prepositions among [`LEADING_WORDS`]. -const PREPOSITIONS: &[&str] = &[ - "в", "во", "на", "с", "со", "к", "ко", "о", "об", "обо", "от", "ото", "до", "из", "изо", "за", "по", "под", "над", "при", "про", "без", "для", - "у", -]; -/// Short words often run into the one before (`мог бы`, `он же`, `так ли`). -const TRAILING_WORDS: &[&str] = &["ли", "же", "бы", "ж", "б", "что"]; -/// The Russian alphabet, when the dictionary has no `TRY` line. -const ALPHABET: &str = "оеаинтсрвлкмдпуяыьгзбчйхжшюцщэфъё"; - -/// `е` written for `ё`, or the other way round, is barely a slip. -const COST_YO: u32 = 3; -/// A one- or two-letter preposition run into the next word (`вдоме` → `в доме`). -const COST_SPLIT_PREPOSITION: u32 = 17; - -/// What Russian adds to its dictionary's tables. -const TUNING: Tuning = - Tuning { split: Split::Custom(split), alphabet: ALPHABET, map: MAP, rep: REP, rep_cost: 9, max_map_changes: 3, map_cost, ..Tuning::DEFAULT }; - -/// The cost of changing `from` to `to` within a letter group: `е`/`ё` cheapest. -fn map_cost(from: char, to: char) -> u32 { - if matches!((from, to), ('е', 'ё') | ('ё', 'е')) { COST_YO } else { COST_OTHER_MAP } -} - -/// A missing space: cheap after a short leading word or before a short trailing one, dearer after -/// a short preposition or between two longer words, never leaving a stray short half. -fn split(a: &str, b: &str) -> Option { - let long = |p: &str| p.chars().count() >= 3; - // A preposition takes a noun, not a verb (`с генерировать` isn't a fix for - // `сгенерировать`); after one or two letters a split is likelier wrong than a typo. - let preposition = PREPOSITIONS.contains(&a); - if preposition && ["ть", "ться", "ти", "чь"].iter().any(|e| b.ends_with(e)) { - return None; - } - if preposition && a.chars().count() <= 2 && LEADING_WORDS.contains(&a) && long(b) { - Some(COST_SPLIT_PREPOSITION) - } else if (LEADING_WORDS.contains(&a) && long(b)) || (long(a) && TRAILING_WORDS.contains(&b)) { - Some(COST_SPLIT) - } else if long(a) && long(b) { - Some(COST_SPLIT_LONG) - } else { - None - } -} - -/// Spelling suggestions for a Russian word, best first, at most `max`, in the word's letter case. -pub fn suggest(word: &str, max: usize) -> Vec { - suggest_in(&super::dictionary(), word, max) -} - -/// [`suggest`] against a given dictionary. -pub fn suggest_in(d: &AffixDict, word: &str, max: usize) -> Vec { - crate::affix_suggest::suggest_in(d, word, max, &TUNING) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn has(word: &str, want: &str) { - let s = suggest(word, 8); - assert!(s.iter().any(|x| x == want), "{word} → {s:?}, want {want}"); - } - - /// Russian's own costs: `е`/`ё` is the cheapest slip, so a `ё` and one more group slip - /// (made-up `нёснаю`) beat a missing space; a REP confusion beats a `ё` and a group slip; - /// a preposition is never split off a verb, and after a one-letter one a typo is likelier. - #[test] - fn russian_costs_rank_its_slips() { - let dict = |words: &[&str]| AffixDict::from_hunspell("", &format!("{}\n{}\n", words.len(), words.join("\n")), &[]).unwrap(); - assert_eq!(suggest_in(&dict(&["не", "знаю", "нёснаю"]), "незнаю", 1), ["нёснаю"]); - assert_eq!(suggest_in(&dict(&["еча", "ёчи"]), "ечя", 1), ["еча"]); - assert!(!suggest_in(&dict(&["с", "генерировать"]), "сгенерировать", 8).contains(&"с генерировать".into())); - let s = suggest_in(&dict(&["в", "доме"]), "вдоме", 4); - assert_eq!(s.first().map(String::as_str), Some("доме"), "{s:?}"); - assert!(s.contains(&"в доме".into()), "{s:?}"); - } - - /// A word the dictionary marks NOSUGGEST (a vulgarism, a rare form) is correct but never - /// offered, in any round or letter case. Russian's MAP and REP tables are its own, so the - /// shared Latin-script check doesn't reach it. - #[test] - fn nosuggest_words_are_never_offered() { - let offered = |aff: &str, input: &str| { - let d = AffixDict::from_hunspell(aff, "2\nпривет/N\nпредмет/N\n", &[]).unwrap(); - suggest_in(&d, input, 16) - }; - assert!(offered("SET UTF-8\n", "превет").contains(&"привет".into()), "offered without the flag declared"); - for input in ["превет", "ПРЕВЕТ", "предмот", "приветт", "привте", "привет"] { - let s = offered("SET UTF-8\nNOSUGGEST N\n", input); - assert!(!s.iter().any(|w| w.to_lowercase().contains("привет") || w.to_lowercase().contains("предмет")), "{input}: {s:?}"); - } - } - - #[test] - fn russian_misspellings_get_the_right_suggestion() { - crate::ru::test_dict::install(); - has("превет", "привет"); - has("сдесь", "здесь"); - has("жыть", "жить"); - has("чясто", "часто"); - has("незнаю", "не знаю"); - has("вобщем", "в общем"); - has("здраствуй", "здравствуй"); - has("москва", "Москва"); - has("Превет", "Привет"); - has("ПРЕВЕТ", "ПРИВЕТ"); - has("читаеться", "читается"); - has("краснного", "красного"); - has("западнный", "западный"); - assert!(!suggest("сгенерировать", 8).contains(&"с генерировать".to_string())); - } - - #[test] - fn the_likeliest_fix_comes_first() { - crate::ru::test_dict::install(); - assert_eq!(suggest("превет", 3).first().map(String::as_str), Some("привет")); - assert_eq!(suggest("незнаю", 3).first().map(String::as_str), Some("не знаю")); - assert_eq!(suggest("жыть", 3).first().map(String::as_str), Some("жить")); - assert_eq!(suggest("зеленый", 3).first().map(String::as_str), Some("зелёный")); - } - - #[test] - fn edge_cases_stay_empty_or_bounded() { - crate::ru::test_dict::install(); - assert!(suggest("", 5).is_empty()); - assert!(suggest("дом", 0).is_empty()); - assert!(suggest(&"а".repeat(41), 5).is_empty()); - assert!(suggest("два слова", 5).is_empty()); - assert!(suggest("привет", 5).len() <= 5); - assert!(!suggest("привет", 5).contains(&"привет".to_string())); - } - - #[test] - fn distance_weighs_letter_groups_half() { - use crate::affix_suggest::{fold, weighted_distance}; - let map: Vec> = MAP.iter().map(|g| g.chars().collect()).collect(); - let related = |a: char, b: char| fold(a, &map) == fold(b, &map); - let c = |s: &str| s.chars().collect::>(); - assert_eq!(weighted_distance(&c("сдесь"), &c("здесь"), &related, 9), 1); - assert_eq!(weighted_distance(&c("ёлка"), &c("елка"), &related, 9), 1); - assert_eq!(weighted_distance(&c("дом"), &c("дмо"), &related, 9), 2); - assert_eq!(weighted_distance(&c("дом"), &c("домами"), &related, 9), 6); - assert_eq!(weighted_distance(&c("дом"), &c("книга"), &related, 2), 3); - } - - #[test] - fn suggestions_are_fast_enough() { - crate::ru::test_dict::install(); - let _ = suggest("дом", 1); - let t = std::time::Instant::now(); - for w in ["ыкщзвотрусскогословаааа", "здраствуйте", "краснннного"] { - let _ = suggest(w, 6); - } - let ms = t.elapsed().as_secs_f64() * 1000.0; - eprintln!("3 suggestion calls: {ms:.0} ms"); - assert!(ms < 20_000.0, "{ms} ms"); - } -} diff --git a/crates/proof/src/ru/test_dict.rs b/crates/proof/src/ru/test_dict.rs deleted file mode 100644 index 947f35520..000000000 --- a/crates/proof/src/ru/test_dict.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! A tiny synthetic Russian Hunspell dictionary for tests (written for these tests; not taken from -//! any real dictionary). Real dictionaries are installed on the system, never bundled. - -/// Affix rules: the endings of a hard masculine noun, a feminine noun in `-а`, hard and soft -/// adjectives and a verb in `-ать`, plus the letters suggestions try. -pub const AFF: &str = "SET UTF-8 -TRY оеаинтсрвлкмдпуяыьгзбчйхжшюцщэфъё -SFX A Y 9 -SFX A 0 а . -SFX A 0 у . -SFX A 0 ом . -SFX A 0 е . -SFX A 0 ы . -SFX A 0 ов . -SFX A 0 ам . -SFX A 0 ами . -SFX A 0 ах . -SFX D Y 5 -SFX D а ы а -SFX D а е а -SFX D а у а -SFX D а ой а -SFX D а ою а -SFX B Y 11 -SFX B ый ого ый -SFX B ый ому ый -SFX B ый ым ый -SFX B ый ом ый -SFX B ый ая ый -SFX B ый ой ый -SFX B ый ую ый -SFX B ый ое ый -SFX B ый ые ый -SFX B ый ых ый -SFX B ый ыми ый -SFX E Y 8 -SFX E ий его ий -SFX E ий ему ий -SFX E ий им ий -SFX E ий ей ий -SFX E ий яя ий -SFX E ий ее ий -SFX E ий ие ий -SFX E ий их ий -SFX C Y 11 -SFX C ть ю ть -SFX C ть ешь ть -SFX C ть ет ть -SFX C ть ем ть -SFX C ть ете ть -SFX C ть ют ть -SFX C ть ются ть -SFX C ть ется ть -SFX C ть л ть -SFX C ть ла ть -SFX C ть ться ть -"; - -/// Stems (the count line first, as in Hunspell files). -pub const DIC: &str = "62 -привет -здесь -здравствуй -жить -часто -знаю -знать/C -не -в -общем -общий/E -дом/A -стол/A -город/A -Москва/D -книга/D -красный/B -новый/B -белый/B -западный/B -синий/E -зелёный/B -русский -английский -читать/C -работать/C -кто -где -что -давай -всё -все -ещё -ёлка/D -север/A -из -за -на -и -а -но -или -он -она -я -ты -мы -вы -это -так -как -когда -который/B -чтобы -потому -если -пришёл -сказал -вскрытый/B -высоко -эластичный/B -консультант/A -"; - -/// Install the test dictionary as the Russian one. -pub fn install() { - if let Err(e) = super::install_dictionary(AFF, DIC) { - panic!("test dictionary: {e}"); - } -} diff --git a/crates/proof/src/slot.rs b/crates/proof/src/slot.rs deleted file mode 100644 index 6255ae5a5..000000000 --- a/crates/proof/src/slot.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! The installed Hunspell dictionary of a proofing language, in one shape for every language: -//! found on first use, replaced with [`DictSlot::install`] and forgotten with [`DictSlot::reload`]. - -use std::path::{Path, PathBuf}; -use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; - -use crate::affix::AffixDict; - -/// A dictionary a language reads: the affix engine's ([`AffixDict`]) or Turkish's own. -pub(crate) trait Dictionary: Default + Send + Sync { - /// The installed `.aff` and `.dic` files, read. - fn load(aff: &Path, dic: &Path) -> Result; - /// Hunspell `.aff` and `.dic` text (decoded to UTF-8), parsed. - fn parse(aff: &str, dic: &str) -> Result; - /// No words: the language has no spelling marks. - fn is_empty(&self) -> bool; -} - -impl Dictionary for AffixDict { - fn load(aff: &Path, dic: &Path) -> Result { - crate::hunspell::load(aff, dic) - } - - fn parse(aff: &str, dic: &str) -> Result { - AffixDict::from_hunspell(aff, dic, &[]) - } - - fn is_empty(&self) -> bool { - AffixDict::is_empty(self) - } -} - -/// What every language's slot answers, whatever its dictionary type (the language table in -/// lib.rs holds them side by side). -pub(crate) trait Slot: Sync { - /// See [`DictSlot::name`]. - fn name(&self) -> &'static str; - /// See [`DictSlot::installed_path`]. - fn installed_path(&self) -> Option; - /// See [`DictSlot::has_dictionary`]. - fn has_dictionary(&self) -> bool; - /// See [`DictSlot::reload`]. - fn reload(&self); -} - -impl Slot for DictSlot { - fn name(&self) -> &'static str { - DictSlot::name(self) - } - - fn installed_path(&self) -> Option { - DictSlot::installed_path(self) - } - - fn has_dictionary(&self) -> bool { - DictSlot::has_dictionary(self) - } - - fn reload(&self) { - DictSlot::reload(self); - } -} - -/// The dictionary in use and whether it came from [`DictSlot::install`] rather than a file on -/// disk (it then has no file to report, even if a matching file also happens to exist): one lock, -/// so a reload and an install can't interleave their two fields. -struct State { - dict: Option>, - in_memory: bool, - /// Counts reloads, so a load that started before one is not kept. - reloads: u64, -} - -/// One language's dictionary. -pub(crate) struct DictSlot { - /// File names the dictionary may have, the usual one first (also the key of its load error). - names: &'static [&'static str], - /// The language's English name, for the log. - english: &'static str, - state: RwLock>, -} - -impl DictSlot { - pub(crate) const fn new(names: &'static [&'static str], english: &'static str) -> Self { - DictSlot { names, english, state: RwLock::new(State { dict: None, in_memory: false, reloads: 0 }) } - } - - fn read(&self) -> RwLockReadGuard<'_, State> { - self.state.read().unwrap_or_else(PoisonError::into_inner) - } - - fn write(&self) -> RwLockWriteGuard<'_, State> { - self.state.write().unwrap_or_else(PoisonError::into_inner) - } - - /// The Hunspell name the dictionary is known by (`pl_PL`): the first of its file names. - pub(crate) fn name(&self) -> &'static str { - self.names.first().copied().unwrap_or_default() - } - - /// The file names the dictionary may have, the usual one first. - #[cfg(test)] - pub(crate) fn names(&self) -> &'static [&'static str] { - self.names - } - - /// The dictionary: the installed one (read on first use), one given with [`Self::install`], or - /// an empty one when none is available (the language's spelling is off then). - pub(crate) fn get(&self) -> Arc { - let reloads = { - let s = self.read(); - if let Some(d) = s.dict.as_ref() { - return d.clone(); - } - s.reloads - }; - self.keep(reloads, Arc::new(self.load_installed())) - } - - /// Keep a dictionary loaded after `reloads` reloads. If the folder was reloaded since, the - /// load may have read the old folder: it serves this one check and the next one loads again. - fn keep(&self, reloads: u64, loaded: Arc) -> Arc { - let mut s = self.write(); - if s.reloads != reloads { - return s.dict.clone().unwrap_or(loaded); - } - // Another thread (or an install) may have filled it meanwhile: that one wins. - s.dict.get_or_insert(loaded).clone() - } - - fn load_installed(&self) -> D { - let name = self.name(); - let Some((aff, dic)) = crate::hunspell::find(self.names) else { - crate::hunspell::clear_load_error(name); - log::info!("{} spelling: no Hunspell {name} dictionary installed", self.english); - return D::default(); - }; - crate::hunspell::note_load(name, &dic, D::load(&aff, &dic)).unwrap_or_else(|e| { - log::warn!("{} spelling: {e}", self.english); - D::default() - }) - } - - /// Use this dictionary from now on (Hunspell `.aff` and `.dic` text, decoded to UTF-8), for - /// example one the user picked. On error the current dictionary stays. - pub(crate) fn install(&self, aff: &str, dic: &str) -> Result<(), String> { - let d = D::parse(aff, dic)?; - let mut slot = self.write(); - slot.dict = Some(Arc::new(d)); - slot.in_memory = true; - Ok(()) - } - - /// Forget the dictionary, so the next check looks for an installed one again (after - /// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). - pub(crate) fn reload(&self) { - let mut slot = self.write(); - slot.dict = None; - slot.in_memory = false; - slot.reloads = slot.reloads.wrapping_add(1); - } - - /// The `.dic` file of the installed dictionary, searching the dictionaries folder and the - /// system locations now (File › Options › Proofing shows it); `None` when there is none, or - /// when the dictionary in use was given with [`Self::install`] and so has no file. - pub(crate) fn installed_path(&self) -> Option { - if self.read().in_memory { - return None; - } - crate::hunspell::find(self.names).map(|(_, dic)| dic) - } - - /// Is a dictionary available (spelling is checked)? - pub(crate) fn has_dictionary(&self) -> bool { - !self.get().is_empty() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - static SLOT: DictSlot = DictSlot::new(&["zz_SLOT_TEST"], "Test"); - - #[test] - fn an_installed_dictionary_has_no_file_until_reloaded() { - assert!(!SLOT.has_dictionary(), "nothing installed under this name"); - assert!(SLOT.install("FLAG long\n", "1\nkot\n").is_err(), "unsupported files are refused"); - assert!(!SLOT.has_dictionary(), "a refused install keeps the current one"); - SLOT.install("", "1\nkot\n").unwrap(); - assert!(SLOT.has_dictionary() && SLOT.get().check("kot")); - assert_eq!(SLOT.installed_path(), None); - SLOT.reload(); - assert!(!SLOT.has_dictionary(), "reloaded from disk, where there is none"); - assert_eq!(SLOT.name(), "zz_SLOT_TEST"); - } - - /// A load that started before a dictionaries-folder change is not kept after its reload. - #[test] - fn a_load_from_before_a_reload_is_not_kept() { - let slot: DictSlot = DictSlot::new(&["zz_SLOT_RELOAD_TEST"], "Test"); - let before = slot.read().reloads; - slot.reload(); - let _ = slot.keep(before, Arc::new(AffixDict::default())); - assert!(slot.read().dict.is_none(), "the next check loads from the new folder"); - let now = slot.read().reloads; - let _ = slot.keep(now, Arc::new(AffixDict::default())); - assert!(slot.read().dict.is_some(), "a load since the reload is kept"); - } -} diff --git a/crates/proof/src/spelling.rs b/crates/proof/src/spelling.rs deleted file mode 100644 index ab5e520e9..000000000 --- a/crates/proof/src/spelling.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! The start of every Latin-script dictionary language's spelling check (Czech, Croatian, -//! Indonesian, Italian, Polish, Portuguese, Spanish): the words that need no lookup, under the -//! File › Options › Proofing choices. Each language goes on from there with its own rules. - -use crate::Options; -use crate::affix::AffixDict; - -/// Is `c` a letter of the Latin script (the only one these dictionaries cover)? -fn latin(c: char) -> bool { - c.is_alphabetic() && (c < '\u{0250}' || ('\u{1E00}'..='\u{1EFF}').contains(&c)) -} - -/// `word` trimmed of quotes and apostrophes, when `d` must say whether it is right; `None` when it -/// is right without a lookup: there is no dictionary (no spelling marks), the word is a single -/// letter or has no letters, it has digits or is a short word in capitals (an acronym) and the -/// options skip those, or it is in another script. -pub(crate) fn word_to_check<'a>(d: &AffixDict, word: &'a str, opts: &Options) -> Option<&'a str> { - if d.is_empty() { - return None; - } - let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '\'' && c != '’'); - let w = w.trim_matches(['\'', '’']); - let n = w.chars().count(); - if n <= 1 || (opts.ignore_numbers && w.chars().any(|c| c.is_ascii_digit())) || !w.chars().any(char::is_alphabetic) { - return None; - } - if opts.ignore_uppercase && w.chars().all(|c| !c.is_lowercase()) && n <= 6 { - return None; - } - w.chars().any(latin).then_some(w) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn words_needing_no_lookup() { - let d = AffixDict::from_hunspell("", "1\nkot\n", &[]).unwrap(); - let defaults = Options::default(); - assert_eq!(word_to_check(&AffixDict::default(), "kot", &defaults), None, "no dictionary"); - for w in ["", "a", "2026", "PKO", "B2B", "日本語", "--"] { - assert_eq!(word_to_check(&d, w, &defaults), None, "{w}"); - } - assert_eq!(word_to_check(&d, "„kot”", &defaults), Some("kot")); - assert_eq!(word_to_check(&d, "'kot'", &defaults), Some("kot")); - let strict = Options { ignore_uppercase: false, ignore_numbers: false, ..defaults }; - assert_eq!(word_to_check(&d, "PKO", &strict), Some("PKO")); - assert_eq!(word_to_check(&d, "B2B", &strict), Some("B2B")); - assert_eq!(word_to_check(&d, "PRZEDSIĘBIORSTWO", &defaults), Some("PRZEDSIĘBIORSTWO"), "only short capitals are acronyms"); - } -} diff --git a/crates/proof/src/tr/dict.rs b/crates/proof/src/tr/dict.rs deleted file mode 100644 index 8cbacf02e..000000000 --- a/crates/proof/src/tr/dict.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Turkish Hunspell dictionary parser with `FLAG num` support. -//! -//! Turkish uses `FLAG num`, storing thousands of numerical suffix flags (0..6465). -//! This engine parses `/usr/share/hunspell/tr_TR.aff` and `.dic` safely without panics. - -use std::collections::HashMap; - -/// A suffix rule for Turkish. -#[derive(Clone, Debug)] -pub struct TrSuffixRule { - pub strip: Box, - pub add: Box, - pub cond: Box, -} - -/// Dedicated Turkish Hunspell dictionary engine. -#[derive(Clone, Debug, Default)] -pub struct TrDict { - /// Stem -> list of numerical flags it carries. - stems: HashMap>, - /// Flag number -> list of suffix rules. - rules: HashMap>, - /// Suffix 'add' string -> list of (flag, rule_index). - suffix_index: HashMap>, - /// Longest suffix 'add' string in characters. - max_suffix_len: usize, -} - -impl crate::slot::Dictionary for TrDict { - fn load(aff: &std::path::Path, dic: &std::path::Path) -> Result { - super::load(aff, dic) - } - - fn parse(aff: &str, dic: &str) -> Result { - TrDict::from_hunspell(aff, dic) - } - - fn is_empty(&self) -> bool { - TrDict::is_empty(self) - } -} - -impl TrDict { - /// Returns true if empty (no dictionary loaded). - pub fn is_empty(&self) -> bool { - self.stems.is_empty() - } - - /// Load from `.aff` and `.dic` contents. - pub fn from_hunspell(aff: &str, dic: &str) -> Result { - let mut rules: HashMap> = HashMap::new(); - let mut max_suffix_len = 0; - - for line in aff.lines() { - let parts: Vec<&str> = line.split_whitespace().collect(); - // SFX [] - if parts.len() >= 4 && parts[0] == "SFX" { - let Ok(flag) = parts[1].parse::() else { continue }; - // Skip header line: `SFX Y/N ` - if parts.len() == 4 && (parts[2] == "Y" || parts[2] == "N") && parts[3].parse::().is_ok() { - continue; - } - let strip = if parts[2] == "0" { "" } else { parts[2] }; - let add = if parts[3] == "0" { "" } else { parts[3] }; - let cond = if parts.len() > 4 { parts[4] } else { "." }; - - max_suffix_len = max_suffix_len.max(add.chars().count()); - - rules.entry(flag).or_default().push(TrSuffixRule { strip: strip.into(), add: add.into(), cond: cond.into() }); - } - } - - let mut suffix_index: HashMap> = HashMap::new(); - for (&flag, r_list) in &rules { - for (idx, r) in r_list.iter().enumerate() { - if let Ok(idx_u16) = u16::try_from(idx) { - suffix_index.entry(r.add.to_string()).or_default().push((flag, idx_u16)); - } - } - } - - let mut stems: HashMap> = HashMap::new(); - for (i, line) in dic.lines().enumerate() { - if i == 0 && line.trim().parse::().is_ok() { - continue; - } - let line = line.trim(); - if line.is_empty() { - continue; - } - if let Some((word, flags_part)) = line.split_once('/') { - let flags: Vec = flags_part.split(',').filter_map(|f| f.parse::().ok()).collect(); - stems.insert(word.to_string(), flags.into_boxed_slice()); - } else { - stems.insert(line.to_string(), Box::new([])); - } - } - - Ok(TrDict { stems, rules, suffix_index, max_suffix_len }) - } - - /// Check if word is correct (stem or affixed). - pub fn contains(&self, word: &str) -> bool { - // Longer input is never a dictionary word, and isn't scanned (#3625). - if self.is_empty() || word.is_empty() || word.len() > crate::affix::MAX_WORD_BYTES { - return false; - } - - // Direct stem hit - if self.stems.contains_key(word) { - return true; - } - - // Try stripping suffixes matching the tail - let word_len = word.chars().count(); - let char_indices: Vec<(usize, char)> = word.char_indices().collect(); - - // Check each possible suffix length up to max_suffix_len - for cut_chars in 0..=self.max_suffix_len.min(word_len) { - let byte_idx = if cut_chars == 0 { word.len() } else { char_indices[word_len - cut_chars].0 }; - - let add = &word[byte_idx..]; - let base = &word[..byte_idx]; - - if let Some(matches) = self.suffix_index.get(add) { - for &(flag, rule_idx) in matches { - let Some(rule_list) = self.rules.get(&flag) else { continue }; - let Some(rule) = rule_list.get(rule_idx as usize) else { continue }; - - // Check condition on root - let mut candidate = String::with_capacity(base.len() + rule.strip.len()); - candidate.push_str(base); - candidate.push_str(&rule.strip); - - if self.stems.get(&candidate).is_some_and(|f_list| f_list.contains(&flag)) { - return true; - } - } - } - } - - false - } -} diff --git a/crates/proof/src/tr/grammar.rs b/crates/proof/src/tr/grammar.rs deleted file mode 100644 index b17e89cb9..000000000 --- a/crates/proof/src/tr/grammar.rs +++ /dev/null @@ -1,359 +0,0 @@ -//! Turkish grammar and punctuation checks: cheap, rule-based and tuned to stay quiet when unsure. -//! Messages are in Turkish. -//! -//! - **Repeated word** (`çok çok`), except idiomatic reduplications and doublings (`ALLOWED_REPEATS`: -//! `yavaş yavaş`, `tıkır tıkır`, `adım adım`). -//! - **Space before punctuation** (`ev ,`) and **double spaces**. -//! - **Capital letter at the start of a sentence**, knowing Turkish abbreviations (`vb.`, `vs.`, -//! `prof.`, `dr.`, `mad.`, `yy.`, `s.`…) after which lowercase or numbers may continue. -//! - **No comma before/after conjunctions**: `ve`, `veya`, `yahut`, `ile`, `de`, `da`, `hem ... hem`, -//! `ne ... ne` conjunctions should not have commas immediately before or after them. -//! - **Doubled punctuation** (`..`, `,,`, `;;`). - -use crate::{Issue, IssueKind, ProofLang, words}; - -/// Turkish reduplications and emphatic word pairings that are intentionally repeated. -const ALLOWED_REPEATS: &[&str] = &[ - "yavaş", - "hızlı", - "koşa", - "güle", - "adım", - "kara", - "tıkır", - "şakır", - "akın", - "ağır", - "açık", - "ayrı", - "bire", - "bir", - "boş", - "bölük", - "buram", - "cana", - "can", - "cılk", - "çifter", - "çit", - "dalga", - "damla", - "deste", - "didik", - "dizi", - "dolu", - "deste", - "döne", - "eğri", - "enine", - "epey", - "fıkır", - "fırıl", - "gece", - "gıcır", - "gide", - "günü", - "güzel", - "halk", - "harıl", - "hık", - "hım", - "horul", - "ılık", - "ışıl", - "için", - "içten", - "iğne", - "iri", - "iyiden", - "iyi", - "kaba", - "kaç", - "kat", - "kaynar", - "kelli", - "kese", - "kıtır", - "kıvrım", - "koca", - "kol", - "kucak", - "kütür", - "mırıl", - "öbek", - "parça", - "patır", - "pıtır", - "sabah", - "salına", - "salkım", - "sap", - "sara", - "sarı", - "serin", - "sıra", - "sırım", - "sıkı", - "sırılsıklam", - "tane", - "teker", - "tek", - "tıpış", - "top", - "tutam", - "uzun", - "üçer", - "üst", - "yalan", - "yana", - "yan", - "yarım", - "yer", - "yudum", - "yığın", - "yürekten", - "yüz", - "zaman", - "zor", -]; - -/// Turkish abbreviations that end with a period and after which lower case may continue. -const ABBREVIATIONS: &[&str] = &[ - "vb", "vs", "vd", "bkz", "bknz", "ör", "örnek", "mad", "md", "s", "sf", "sn", "dk", "sa", "yy", "m", "km", "cm", "mm", "gr", "kg", "mg", "dr", - "prof", "doç", "yrd", "av", "ist", "ank", "izm", "cad", "sok", "mah", "apt", "no", "tel", "faks", "tl", "kr", "krş", "a.ş", "ltd", "şti", "t.c", - "tbmm", "meb", "möt", "mö", "ms", -]; - -/// Conjunctions that must not be preceded by a comma. -const CONJUNCTIONS_NO_PRE_COMMA: &[&str] = &["ve", "veya", "yahut", "velev", "veyahut", "de", "da"]; - -/// Conjunctions that must not be followed by a comma. -const CONJUNCTIONS_NO_POST_COMMA: &[&str] = &["ve", "veya", "yahut", "veyahut", "hem", "ne", "ya", "gerek", "ister", "de", "da"]; - -/// Check grammar and punctuation in a Turkish text block. -pub fn check(text: &str) -> Vec { - check_with(text, true) -} - -/// [`check`] where `capital_first` says whether the text's first word must start a sentence with a capital -/// (false for a list item, which may be a fragment). -pub fn check_with(text: &str, capital_first: bool) -> Vec { - let mut v = Vec::new(); - let ws = words(text); - - // 1. Repeated words (`kelime kelime`) - for pair in ws.windows(2) { - let (w1, w2) = match (text.get(pair[0].0..pair[0].1), text.get(pair[1].0..pair[1].1)) { - (Some(a), Some(b)) => (a, b), - _ => continue, - }; - let gap = match text.get(pair[0].1..pair[1].0) { - Some(g) => g, - None => continue, - }; - let only_spaces = gap.chars().all(|c| c == ' ' || c == '\t'); - let lower1 = w1.to_lowercase(); - let lower2 = w2.to_lowercase(); - if only_spaces && lower1 == lower2 && !ALLOWED_REPEATS.contains(&lower1.as_str()) { - v.push(Issue { - start: pair[1].0, - end: pair[1].1, - kind: IssueKind::Grammar, - message: "Yinelenen sözcük".into(), - suggestions: Vec::new(), - lang: ProofLang::Tr, - }); - } - } - - // 2. Spaces before punctuation and double spaces - let chars: Vec<(usize, char)> = text.char_indices().collect(); - for i in 0..chars.len() { - let (idx, c) = chars[i]; - if matches!(c, ',' | '.' | '?' | '!' | ';' | ':') && i > 0 && chars[i - 1].1 == ' ' { - let prev_sp = chars[i - 1].0; - // Leave emoticons and ellipses alone - let is_dot_in_dots = c == '.' && (i + 1 < chars.len() && chars[i + 1].1 == '.'); - if !is_dot_in_dots { - v.push(Issue { - start: prev_sp, - end: idx + c.len_utf8(), - kind: IssueKind::Grammar, - message: "Noktalama işaretinden önce boşluk bırakılmaz".into(), - suggestions: vec![c.to_string()], - lang: ProofLang::Tr, - }); - } - } - // Double space - if c == ' ' && i + 1 < chars.len() && chars[i + 1].1 == ' ' && (i == 0 || chars[i - 1].1 != ' ') { - let mut end = idx + 1; - while end < text.len() && text.as_bytes().get(end) == Some(&b' ') { - end += 1; - } - v.push(Issue { - start: idx, - end, - kind: IssueKind::Grammar, - message: "Fazladan boşluk".into(), - suggestions: vec![" ".into()], - lang: ProofLang::Tr, - }); - } - } - - // 3. Comma before or after conjunctions (`ali, ve veli` or `ve, veli`) - for (i, &(w_start, w_end)) in ws.iter().enumerate() { - let word = match text.get(w_start..w_end) { - Some(w) => w.to_lowercase(), - None => continue, - }; - // Check comma before conjunction - if CONJUNCTIONS_NO_PRE_COMMA.contains(&word.as_str()) - && i > 0 - && let Some(between) = text.get(ws[i - 1].1..w_start) - && let Some(comma_pos) = between.find(',') - { - let actual_pos = ws[i - 1].1 + comma_pos; - v.push(Issue { - start: actual_pos, - end: actual_pos + 1, - kind: IssueKind::Grammar, - message: format!("'{word}' bağlacından önce virgül konulmaz"), - suggestions: vec![String::new()], - lang: ProofLang::Tr, - }); - } - // Check comma after conjunction - if CONJUNCTIONS_NO_POST_COMMA.contains(&word.as_str()) - && i + 1 < ws.len() - && let Some(between) = text.get(w_end..ws[i + 1].0) - && let Some(comma_pos) = between.find(',') - { - let actual_pos = w_end + comma_pos; - v.push(Issue { - start: actual_pos, - end: actual_pos + 1, - kind: IssueKind::Grammar, - message: format!("'{word}' bağlacından sonra virgül konulmaz"), - suggestions: vec![String::new()], - lang: ProofLang::Tr, - }); - } - } - - // 4. Sentence capitalization knowing Turkish abbreviations - for (i, &(w_start, w_end)) in ws.iter().enumerate() { - if i == 0 { - // First word in paragraph - if capital_first - && let Some(w) = text.get(w_start..w_end) - && w.chars().next().is_some_and(char::is_lowercase) - { - let cap = turkish_capitalize(w); - v.push(Issue { - start: w_start, - end: w_end, - kind: IssueKind::Grammar, - message: "Cümle büyük harfle başlar".into(), - suggestions: vec![cap], - lang: ProofLang::Tr, - }); - } - continue; - } - - // Check if previous character sequence ended a sentence - let prev_end = ws[i - 1].1; - let prev_word = text.get(ws[i - 1].0..prev_end).unwrap_or(""); - if let Some(gap) = text.get(prev_end..w_start) { - let trimmed = gap.trim(); - if (trimmed.ends_with('.') || trimmed.ends_with('?') || trimmed.ends_with('!')) - && !trimmed.contains("...") - && !ABBREVIATIONS.contains(&prev_word.to_lowercase().as_str()) - && let Some(w) = text.get(w_start..w_end) - && w.chars().next().is_some_and(char::is_lowercase) - { - let cap = turkish_capitalize(w); - v.push(Issue { - start: w_start, - end: w_end, - kind: IssueKind::Grammar, - message: "Cümle büyük harfle başlar".into(), - suggestions: vec![cap], - lang: ProofLang::Tr, - }); - } - } - } - - v.sort_by_key(|issue| (issue.start, issue.end)); - v -} - -/// Capitalize according to Turkish rules (`i` -> `İ`, `ı` -> `I`). -pub fn turkish_capitalize(s: &str) -> String { - let mut chars = s.chars(); - let Some(first) = chars.next() else { return String::new() }; - let first_cap = match first { - 'i' => 'İ', - 'ı' => 'I', - other => other.to_uppercase().next().unwrap_or(other), - }; - let mut out = String::with_capacity(s.len() + 1); - out.push(first_cap); - out.push_str(chars.as_str()); - out -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn clean_turkish_text_produces_no_issues() { - let text = "Bu bir test cümlesidir. Kitap ve defter aldım. Çocuk yavaş yavaş yürüdü."; - assert!(check(text).is_empty()); - } - - #[test] - fn repeated_words_flagged_except_allowed() { - let text = "Gitti gitti ama bulamadı."; - let issues = check(text); - assert_eq!(issues.len(), 1); - assert_eq!(issues[0].message, "Yinelenen sözcük"); - - // Allowed repeat - let text2 = "Çocuk yavaş yavaş ilerliyordu."; - assert!(check(text2).is_empty()); - } - - #[test] - fn space_before_punctuation_flagged() { - let text = "Merhaba , dünya!"; - let issues = check(text); - assert_eq!(issues.len(), 1); - assert_eq!(issues[0].message, "Noktalama işaretinden önce boşluk bırakılmaz"); - } - - #[test] - fn comma_around_conjunctions_flagged() { - let text = "Elma, ve armut."; - let issues = check(text); - assert_eq!(issues.len(), 1); - assert!(issues[0].message.contains("virgül konulmaz")); - - let text2 = "Bana kalırsa bu mesele de, haklı olan taraf bizdik."; - let issues2 = check(text2); - assert_eq!(issues2.len(), 1); - assert!(issues2[0].message.contains("'de' bağlacından sonra virgül konulmaz")); - } - - #[test] - fn sentence_capitalization_respects_abbreviations() { - let text = "Dr. ahmet geldi. Fakat geç kaldı."; - let issues = check(text); - // "Dr. ahmet" does not flag "ahmet" because "Dr." is in ABBREVIATIONS. - assert!(issues.is_empty()); - } -} diff --git a/crates/proof/src/tr/hyphen.rs b/crates/proof/src/tr/hyphen.rs deleted file mode 100644 index a8a481088..000000000 --- a/crates/proof/src/tr/hyphen.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Turkish hyphenation by rule ([`ProofLang::Tr`](crate::ProofLang::Tr)). -//! -//! Turkish words are split at syllables according to Turkish Language Association (TDK) rules. -//! Because Turkish syllable boundaries are fully regular, they can be determined directly by rule -//! without needing external pattern files or word lists: -//! - Turkish vowels: `a`, `e`, `ı`, `i`, `o`, `ö`, `u`, `ü` (both lowercase and uppercase). -//! - A single consonant between two vowels joins the following syllable (`ba-lık`, `ö-ğren-ci`). -//! - Of two consonants between two vowels, the first stays with the preceding syllable and the -//! second joins the next (`alt-lık`, `sev-gi`, `tür-kçe`). -//! - Of three consonants between two vowels, the first two stay with the preceding syllable and the -//! third joins the next (`türk-çe`, `sürp-riz`). -//! - In compound words, syllable division follows pronunciation across the boundary (`ba-şöğ-ret-men`, -//! `il-ko-kul`), which matches the general vowel/consonant distribution rules. -//! -//! A single letter left at the end or start of a line is ruled out by the paragraph's -//! [`Limits`](crate::hyphen::Limits). - -/// Is `c` a Turkish vowel? -pub fn is_vowel(c: char) -> bool { - matches!(c, 'a' | 'e' | 'ı' | 'i' | 'o' | 'ö' | 'u' | 'ü' | 'A' | 'E' | 'I' | 'İ' | 'O' | 'Ö' | 'U' | 'Ü') -} - -/// Is `c` a Turkish letter? -pub(crate) fn is_letter(c: char) -> bool { - c.is_alphabetic() -} - -/// Break points of a Turkish word (char indices; a hyphen goes before that char). -/// Empty for words with fewer than two vowels or containing non-alphabetic characters. -pub fn points(word: &[char]) -> Vec { - if word.len() < 2 || !word.iter().all(|&c| is_letter(c)) { - return Vec::new(); - } - let vowels: Vec = (0..word.len()).filter(|&i| is_vowel(word[i])).collect(); - if vowels.len() < 2 { - return Vec::new(); - } - let mut out = Vec::new(); - for pair in vowels.windows(2) { - let (v1, v2) = (pair[0], pair[1]); - if v2 <= v1 { - continue; - } - let num_consonants = v2 - v1 - 1; - let pt = match num_consonants { - 0 => v1 + 1, - 1 => v2 - 1, - 2 => v1 + 2, - _ => v2 - 1, - }; - if pt > v1 && pt <= v2 { - out.push(pt); - } - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - - fn split(w: &str) -> String { - let chars: Vec = w.chars().collect(); - let pts = points(&chars); - let mut out = String::new(); - for (i, c) in chars.iter().enumerate() { - if pts.contains(&i) { - out.push('-'); - } - out.push(*c); - } - out - } - - #[test] - fn syllables_follow_turkish_rules() { - assert_eq!(split("balık"), "ba-lık"); - assert_eq!(split("araba"), "a-ra-ba"); - assert_eq!(split("öğrenci"), "öğ-ren-ci"); - assert_eq!(split("altlık"), "alt-lık"); - assert_eq!(split("sevgi"), "sev-gi"); - assert_eq!(split("türkçe"), "türk-çe"); - assert_eq!(split("saat"), "sa-at"); - assert_eq!(split("şiir"), "şi-ir"); - assert_eq!(split("ilkokul"), "il-ko-kul"); - assert_eq!(split("başöğretmen"), "ba-şöğ-ret-men"); - } - - #[test] - fn short_or_non_words_stay_unsplit() { - assert_eq!(split(""), ""); - assert_eq!(split("a"), "a"); - assert_eq!(split("ve"), "ve"); - assert_eq!(split("Türk"), "Türk"); - assert_eq!(split("123"), "123"); - assert_eq!(split("araba-ev"), "araba-ev"); - } -} diff --git a/crates/proof/src/tr/mod.rs b/crates/proof/src/tr/mod.rs deleted file mode 100644 index 967148d8b..000000000 --- a/crates/proof/src/tr/mod.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Turkish proofing. -//! -//! - **Spelling** against the Hunspell `tr_TR` dictionary installed on the system -//! ([`dictionary`], found by [`crate::hunspell`]: `tr_TR.aff`/`tr_TR.dic` from the OS, a -//! package manager or LibreOffice; it is not bundled). Without it Turkish text gets no spelling -//! marks. Capitalized stems are proper nouns (`Ankara`); foreign names inflected with an apostrophe -//! are accepted (`WordCraft'ta`), and the user dictionary applies as in English. -//! - **Suggestions** tuned for Turkish ([`suggest`]): ASCII-diacritic restoration (`turkce` -> `Türkçe`), -//! common confusions (`yalnış` -> `yanlış`), detached particles (`geldimi` -> `geldi mi`), and typos. -//! - **Grammar** ([`grammar`]): high-precision rules (reduplications, comma around conjunctions, -//! sentence start with abbreviations). -//! - **Hyphenation** ([`hyphen`]): syllable division by Turkish Language Association (TDK) rules. - -pub mod dict; -pub mod grammar; -pub mod hyphen; -pub mod suggest; -#[cfg(test)] -pub(crate) mod test_dict; - -use std::sync::Arc; - -use crate::slot::DictSlot; - -pub use dict::TrDict; -pub use suggest::suggest; - -/// File names of a Turkish Hunspell dictionary. -const NAMES: &[&str] = &["tr_TR", "tr-TR", "tr"]; - -/// The Turkish dictionary, found on first use (see [`DictSlot`]). -pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Turkish"); - -/// The Turkish dictionary: the installed Hunspell `tr_TR` (read on first use), one given with -/// [`install_dictionary`], or an empty one when none is available. -pub fn dictionary() -> Arc { - DICT.get() -} - -/// Read a Turkish Hunspell pair from disk: size-capped and decoded per the `.aff`'s `SET` line, -/// like the other languages' dictionaries (#3661). -pub(crate) fn load(aff: &std::path::Path, dic: &std::path::Path) -> Result { - let (aff_text, dic_text) = crate::hunspell::decode(&crate::hunspell::read_capped(aff)?, &crate::hunspell::read_capped(dic)?)?; - TrDict::from_hunspell(&aff_text, &dic_text) -} - -/// Use this Turkish dictionary from now on (Hunspell `.aff` and `.dic` text). On error the -/// current dictionary stays. -pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { - DICT.install(aff, dic) -} - -/// Is a Turkish dictionary available? -pub fn has_dictionary() -> bool { - DICT.has_dictionary() -} - -/// Is `word` spelled correctly in Turkish? -pub fn is_correct(word: &str) -> bool { - is_correct_with(&dictionary(), word) -} - -/// [`is_correct`] with the File › Options › Proofing choices: with Ignore words that contain -/// numbers off, words with digits are checked against the dictionary too. -pub fn is_correct_with_options(word: &str, opts: &crate::Options) -> bool { - check(&dictionary(), word, opts) -} - -/// [`is_correct`] against a given dictionary. -pub fn is_correct_with(d: &TrDict, word: &str) -> bool { - check(d, word, &crate::Options::default()) -} - -/// [`is_correct_with_options`] against a given dictionary. -fn check(d: &TrDict, word: &str, opts: &crate::Options) -> bool { - if d.is_empty() { - return true; // No Turkish dictionary installed: no spelling marks - } - let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '\'' && c != '’'); - let w = w.trim_matches(['\'', '’']); - let n = w.chars().count(); - if n <= 1 || (opts.ignore_numbers && w.chars().any(|c| c.is_ascii_digit())) || !w.chars().any(char::is_alphabetic) { - return true; - } - // Check user dictionary first - if crate::in_user_dictionary(&suggest::turkish_to_lower(w)) || crate::in_user_dictionary(&w.to_lowercase()) { - return true; - } - - // Direct lookup or lowercase lookup - if d.contains(w) { - return true; - } - let lower = suggest::turkish_to_lower(w); - if d.contains(&lower) { - return true; - } - - // Handle apostrophe for proper nouns (e.g., `Ankara'da`, `WordCraft'ta`) - if let Some((stem, _suffix)) = w.split_once(['\'', '’']) - && (d.contains(stem) || d.contains(&suggest::turkish_to_lower(stem))) - { - return true; - } - - // Compound words separated by hyphens (e.g., `afyon-karahisar`) - if w.contains('-') && w.split('-').all(|part| part.is_empty() || check(d, part, opts)) { - return true; - } - - false -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn without_dictionary_everything_is_correct() { - let empty = TrDict::default(); - assert!(is_correct_with(&empty, "herhangi")); - assert!(is_correct_with(&empty, "xyzabc")); - } - - #[test] - fn apostrophe_and_compounds_are_handled() { - let empty = TrDict::default(); - assert!(is_correct_with(&empty, "Ankara'da")); - assert!(is_correct_with(&empty, "açık-mavi")); - } - - #[test] - fn installed_dictionary_checks_words() { - test_dict::install(); - assert!(is_correct("kitap")); - assert!(is_correct("kitaplar")); - assert!(is_correct("evde")); - assert!(is_correct("olacaklardan")); - assert!(is_correct("sonra")); - assert!(!is_correct("sorna")); - assert!(!is_correct("kitapppp")); - assert!(!is_correct("evdekişşş")); - - // Test user dictionary - assert!(!is_correct("benimbilmedigimkelime")); - crate::add_word("benimbilmedigimkelime"); - assert!(is_correct("benimbilmedigimkelime")); - } - - /// With Ignore words that contain numbers off, Turkish checks words with digits against the - /// dictionary instead of accepting them, hyphen parts included. - #[test] - fn proofing_options_reach_the_turkish_check() { - test_dict::install(); - let lang = crate::ProofLang::Tr; - let defaults = crate::Options::default(); - let check_digits = crate::Options { ignore_numbers: false, ..defaults }; - assert!(crate::is_correct_in_with("kitapp2", lang, &defaults)); - assert!(!crate::is_correct_in_with("kitapp2", lang, &check_digits)); - assert!(!crate::is_correct_in_with("ev-kitapp2", lang, &check_digits)); - assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct"); - assert!(is_correct("kitapp2"), "direct checks keep the defaults"); - } -} diff --git a/crates/proof/src/tr/suggest.rs b/crates/proof/src/tr/suggest.rs deleted file mode 100644 index 08874420e..000000000 --- a/crates/proof/src/tr/suggest.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! Turkish spelling suggestions, ranked by likelihood. -//! -//! Candidates are generated and ranked by cost: -//! 1. Capitalization / case adjustments (especially dotted/dotless I: `istanbul` -> `İstanbul`). -//! 2. Missing Turkish diacritics / ASCII substitutions (most frequent Turkish typing slip): -//! `c` <-> `ç`, `g` <-> `ğ`, `i` <-> `ı`, `o` <-> `ö`, `s` <-> `ş`, `u` <-> `ü` (`turkce` -> `Türkçe`). -//! 3. Common spelling mistakes and phonetic confusions (`REP` table): -//! `yalnış` -> `yanlış`, `yanlız` -> `yalnız`, `herşey` -> `her şey`, `bir kaç` -> `birkaç`. -//! 4. Missing space for detached particles and conjunctions (`bende` -> `ben de`, `geldinmi` -> `geldin mi`). -//! 5. Single typo (Damerau-Levenshtein distance <= 2). - -use std::collections::HashSet; - -use crate::tr::dict::TrDict; - -/// Common misspellings and their canonical replacements in Turkish. -const COMMON_REPLACEMENTS: &[(&str, &str)] = &[ - ("yalnış", "yanlış"), - ("yanlız", "yalnız"), - ("herşey", "her şey"), - ("herkez", "herkes"), - ("bir kaç", "birkaç"), - ("birşey", "bir şey"), - ("tabiki", "tabii ki"), - ("orjinal", "orijinal"), - ("kiprik", "kirpik"), - ("şöför", "şoför"), - ("sarmısak", "sarımsak"), - ("makina", "makine"), - ("ünvan", "unvan"), - ("rastlantı", "rastlantı"), - ("labaratuvar", "laboratuvar"), - ("laboratuar", "laboratuvar"), - ("kravat", "kravat"), - ("gardolap", "gardırop"), - ("egzoz", "egzoz"), - ("egzozt", "egzoz"), - ("egsoz", "egzoz"), - ("gelcen", "geleceksin"), - ("gelecen", "geleceksin"), - ("gitcen", "gideceksin"), - ("gidecen", "gideceksin"), - ("yapcan", "yapacaksın"), - ("yapacan", "yapacaksın"), - ("edecen", "edeceksin"), - ("deyil", "değil"), -]; - -/// Longest word (in letters) given suggestions. Every candidate is a fresh copy of the word, so -/// the cost grows with the square of its length (#3625); Turkish words run longer than most -/// (`Çekoslovakyalılaştıramadıklarımızdanmışsınız` is 43 letters), so the cap is above the other -/// languages' 40. -const MAX_LETTERS: usize = 64; - -/// Produce spelling suggestions for a misspelled Turkish word. -pub fn suggest(word: &str, max: usize) -> Vec { - let dict = super::dictionary(); - suggest_with(&dict, word, max) -} - -/// Suggest with a given dictionary. -pub fn suggest_with(d: &TrDict, word: &str, max: usize) -> Vec { - let n = word.chars().count(); - if n == 0 || n > MAX_LETTERS || max == 0 { - return Vec::new(); - } - - let mut suggestions = Vec::new(); - let mut seen = HashSet::new(); - - let lower = turkish_to_lower(word); - - // 1. Check direct common replacement table - for &(wrong, right) in COMMON_REPLACEMENTS { - if lower == wrong && seen.insert(right.to_string()) { - suggestions.push(right.to_string()); - if suggestions.len() >= max { - return suggestions; - } - } - } - // Only the table takes more than one word (`bir kaç`). - if word.contains(char::is_whitespace) { - return suggestions; - } - - // 2. Diacritic substitutions (`c` -> `ç`, `g` -> `ğ`, `ı` -> `i`, `o` -> `ö`, `s` -> `ş`, `u` -> `ü`) - if !d.is_empty() { - let variants = generate_diacritic_variants(&lower); - for cand in variants { - if cand != lower && d.contains(&cand) && seen.insert(cand.clone()) { - suggestions.push(match_casing(word, &cand)); - if suggestions.len() >= max { - return suggestions; - } - } - } - } - - // 3. Detached suffix check: `-de`, `-da`, `-mi`, `-mı`, `-mu`, `-mü` - for suffix in ["de", "da", "te", "ta", "mi", "mı", "mu", "mü"] { - if lower.ends_with(suffix) && lower.len() > suffix.len() + 1 { - let stem = &lower[..lower.len() - suffix.len()]; - if d.is_empty() || d.contains(stem) { - let cand = format!("{stem} {suffix}"); - if seen.insert(cand.clone()) { - suggestions.push(match_casing(word, &cand)); - if suggestions.len() >= max { - return suggestions; - } - } - } - } - } - - // 4. Single typo (transposition, deletion, insertion, substitution) - if !d.is_empty() { - let chars: Vec = lower.chars().collect(); - let n = chars.len(); - - // 4a. Transposition (adjacent swap: `sorna` -> `sonra`) - for i in 0..n.saturating_sub(1) { - if chars.get(i) != chars.get(i + 1) { - let mut swapped = chars.clone(); - swapped.swap(i, i + 1); - let cand: String = swapped.into_iter().collect(); - if d.contains(&cand) && seen.insert(cand.clone()) { - suggestions.push(match_casing(word, &cand)); - if suggestions.len() >= max { - return suggestions; - } - } - } - } - - // Turkish alphabet for insertions and substitutions - const TR_ALPHABET: &[char] = &[ - 'a', 'b', 'c', 'ç', 'd', 'e', 'f', 'g', 'ğ', 'h', 'ı', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'ö', 'p', 'r', 's', 'ş', 't', 'u', 'ü', 'v', - 'y', 'z', - ]; - - // 4b. Deletion (one character left out: `kitapp` -> `kitap`) - for i in 0..n { - let mut deleted = chars.clone(); - deleted.remove(i); - let cand: String = deleted.into_iter().collect(); - if d.contains(&cand) && seen.insert(cand.clone()) { - suggestions.push(match_casing(word, &cand)); - if suggestions.len() >= max { - return suggestions; - } - } - } - - // 4c. Substitution (one wrong letter: `yolculuk` -> ...) - for i in 0..n { - for &c in TR_ALPHABET { - if chars.get(i) != Some(&c) { - let mut substituted = chars.clone(); - if let Some(target) = substituted.get_mut(i) { - *target = c; - } - let cand: String = substituted.into_iter().collect(); - if d.contains(&cand) && seen.insert(cand.clone()) { - suggestions.push(match_casing(word, &cand)); - if suggestions.len() >= max { - return suggestions; - } - } - } - } - } - - // 4d. Insertion (one missing letter: `kitp` -> `kitap`) - for i in 0..=n { - for &c in TR_ALPHABET { - let mut inserted = chars.clone(); - inserted.insert(i, c); - let cand: String = inserted.into_iter().collect(); - if d.contains(&cand) && seen.insert(cand.clone()) { - suggestions.push(match_casing(word, &cand)); - if suggestions.len() >= max { - return suggestions; - } - } - } - } - } - - // 5. Fallback: capitalization - let cap = super::grammar::turkish_capitalize(&lower); - if cap != word && (d.is_empty() || d.contains(&cap)) && seen.insert(cap.clone()) { - suggestions.push(cap); - } - - suggestions -} - -/// Convert ASCII letters to Turkish diacritic equivalents. -fn generate_diacritic_variants(s: &str) -> Vec { - let mut results = vec![String::new()]; - for c in s.chars() { - let (alt1, alt2) = match c { - 'c' => ('c', Some('ç')), - 'ç' => ('ç', Some('c')), - 'g' => ('g', Some('ğ')), - 'ğ' => ('ğ', Some('g')), - 'i' => ('i', Some('ı')), - 'ı' => ('ı', Some('i')), - 'o' => ('o', Some('ö')), - 'ö' => ('ö', Some('o')), - 's' => ('s', Some('ş')), - 'ş' => ('ş', Some('s')), - 'u' => ('u', Some('ü')), - 'ü' => ('ü', Some('u')), - other => (other, None), - }; - - let branch_mult = if alt2.is_some() { 2 } else { 1 }; - if results.len() * branch_mult > 1024 { - // Cap search branch explosion - for res in &mut results { - res.push(c); - } - continue; - } - - let mut next_gen = Vec::with_capacity(results.len() * branch_mult); - for existing in &results { - let mut s1 = existing.clone(); - s1.push(alt1); - next_gen.push(s1); - if let Some(c2) = alt2 { - let mut s2 = existing.clone(); - s2.push(c2); - next_gen.push(s2); - } - } - results = next_gen; - } - results -} - -/// Turkish lowercase with dotted/dotless I support. -pub fn turkish_to_lower(s: &str) -> String { - let mut out = String::with_capacity(s.len()); - for c in s.chars() { - match c { - 'I' => out.push('ı'), - 'İ' => out.push('i'), - other => { - for lc in other.to_lowercase() { - out.push(lc); - } - } - } - } - out -} - -/// Match original casing on suggestion. -fn match_casing(orig: &str, cand: &str) -> String { - if orig.chars().all(char::is_uppercase) { - cand.to_uppercase() - } else if orig.chars().next().is_some_and(char::is_uppercase) { - super::grammar::turkish_capitalize(cand) - } else { - cand.to_string() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn common_replacements_are_suggested() { - let sugs = suggest("yalnış", 3); - assert!(sugs.contains(&"yanlış".to_string())); - - let sugs2 = suggest("herkez", 3); - assert!(sugs2.contains(&"herkes".to_string())); - } - - #[test] - fn detached_suffix_is_suggested() { - let sugs = suggest("geldinmi", 3); - assert!(sugs.contains(&"geldin mi".to_string())); - } - - #[test] - fn overlong_words_get_no_suggestions_promptly() { - let d = TrDict::from_hunspell("SET UTF-8\nSFX 1 Y 1\nSFX 1 0 lar .\n", "2\nkitap/1\nsonra\n").unwrap(); - assert!(suggest_with(&d, "sorna", 3).contains(&"sonra".to_string())); - assert!(suggest_with(&d, "kitaplr", 3).contains(&"kitaplar".to_string())); - let t = std::time::Instant::now(); - let long = "a".repeat(100_000); - assert!(suggest_with(&d, &long, 8).is_empty()); - assert!(!d.contains(&long) && !d.contains(&format!("kitap{long}"))); - assert!(!super::super::is_correct_with(&d, &long)); - // Tens of milliseconds with the caps; without them this ran for over 5 minutes. - assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed()); - } - - #[test] - fn two_words_get_only_the_tables_replacement() { - let d = TrDict::from_hunspell("SET UTF-8\n", "2\nbirkaç\nsonra\n").unwrap(); - assert_eq!(suggest_with(&d, "bir kaç", 3), vec!["birkaç".to_string()]); - assert_eq!(suggest_with(&d, "Bir kaç", 3), vec!["birkaç".to_string()]); - assert!(suggest_with(&d, "sorna sorna", 3).is_empty()); - } - - #[test] - fn turkish_to_lower_handles_i() { - assert_eq!(turkish_to_lower("IŞIK"), "ışık"); - assert_eq!(turkish_to_lower("İSTANBUL"), "istanbul"); - } - - #[test] - fn typo_transposition_is_suggested() { - crate::tr::test_dict::install(); - let sugs = suggest("sorna", 3); - assert!(sugs.contains(&"sonra".to_string()), "sorna should suggest sonra, got {sugs:?}"); - - let sugs_gelcen = suggest("gelcen", 3); - assert!(sugs_gelcen.contains(&"geleceksin".to_string()), "gelcen should suggest geleceksin, got {sugs_gelcen:?}"); - } -} diff --git a/crates/proof/src/tr/test_dict.rs b/crates/proof/src/tr/test_dict.rs deleted file mode 100644 index de4a13195..000000000 --- a/crates/proof/src/tr/test_dict.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! A tiny synthetic Turkish Hunspell dictionary for tests (written for these tests; not taken from -//! any real dictionary). Real dictionaries are installed on the system, never bundled. - -/// No affix rules: the forms the tests look up are listed whole. -pub const AFF: &str = "SET UTF-8\n"; - -/// The words the Turkish tests expect to be known. -pub const DIC: &str = "7 -kitap -kitaplar -ev -evde -olacaklardan -sonra -geldin -"; - -pub fn install() { - if let Err(e) = super::install_dictionary(AFF, DIC) { - panic!("test dictionary: {e}"); - } -} diff --git a/crates/proof/src/vi.rs b/crates/proof/src/vi.rs deleted file mode 100644 index 1abf7d4aa..000000000 --- a/crates/proof/src/vi.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Telling Vietnamese text apart from English, so a document typed in Vietnamese under the default -//! English proofing language isn't covered in red underlines (as Word's "detect language -//! automatically" does). There are no Vietnamese proofing tools yet, so Vietnamese text is treated -//! like any language without them: no spelling or grammar marks. -//! -//! The test is orthographic. Vietnamese writes most syllables with letters no other Latin-script -//! language shares: the horn letters (`ơ ư`), `ĩ ũ`, and the tone-marked vowels of the Latin -//! Extended Additional block (`ạ ả ấ ầ … ỹ`), precomposed or as combining hook above, dot below -//! and horn. French and Portuguese share `â ê ô à é`, Romanian `ă` and Croatian, Serbian and -//! Bosnian `đ`, so those alone don't count. - -/// Does `c` occur in Vietnamese words but (practically) nowhere else? -fn vietnamese_letter(c: char) -> bool { - matches!(c, 'ơ' | 'Ơ' | 'ư' | 'Ư' | 'ĩ' | 'Ĩ' | 'ũ' | 'Ũ' | '\u{1EA0}'..='\u{1EF9}' | '\u{0309}' | '\u{0323}' | '\u{031B}') -} - -/// Is `word` written with a Vietnamese-only letter (`Việt`, `ơn`)? Such a word is never an -/// English misspelling. -pub fn is_vietnamese_word(word: &str) -> bool { - let combining = |c: char| matches!(c, '\u{0300}'..='\u{036F}'); - // Two marks stacked on one letter (decomposed `ế` = e + circumflex + acute) are Vietnamese too. - word.chars().any(vietnamese_letter) || word.chars().zip(word.chars().skip(1)).any(|(a, b)| combining(a) && combining(b)) -} - -/// Does a paragraph read as Vietnamese? At least two of its words (any, in a heading of up to three -/// words) carry a Vietnamese-only letter, and they are at least a sixth of its words: Vietnamese syllables without -/// such a letter (`anh`, `con`, `ba`) are common, while an English paragraph quoting one -/// Vietnamese name stays English. -pub fn looks_vietnamese(text: &str) -> bool { - let mut total = 0usize; - let mut marked = 0usize; - for w in text.split(|c: char| !(c.is_alphanumeric() || matches!(c, '\u{0300}'..='\u{036F}'))).filter(|w| !w.is_empty()) { - total += 1; - if is_vietnamese_word(w) { - marked += 1; - } - } - marked > 0 && (marked >= 2 || total <= 3) && marked * 6 >= total -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn vietnamese_words() { - for w in ["Việt", "ơn", "những", "Ước", "mũi", "Nghĩa", "tie\u{0302}\u{0301}ng", "nho\u{031B}"] { - assert!(is_vietnamese_word(w), "{w}"); - } - // Shared with French, Portuguese, Spanish: not enough on their own. - // Romanian ă and Croatian đ are Vietnamese letters too, but not Vietnamese-only. - for w in ["café", "être", "côte", "água", "señor", "anh", "the", "mănânc", "acasă", "Đurđa", "đak", "đi"] { - assert!(!is_vietnamese_word(w), "{w}"); - } - } - - #[test] - fn paragraphs() { - assert!(looks_vietnamese("Tôi yêu tiếng Việt và những bài thơ của nó.")); - assert!(looks_vietnamese("Hà Nội")); - assert!(looks_vietnamese("Việt")); - // English quoting one Vietnamese name, and French, stay as they are. - assert!(!looks_vietnamese("The meeting with Mr Nguyễn is on Tuesday at the main office.")); - assert!(!looks_vietnamese("Le café de la côte est très bon.")); - assert!(!looks_vietnamese("")); - assert!(!looks_vietnamese("anh em")); - // Romanian and Croatian paragraphs, full of ă and đ, aren't Vietnamese. - assert!(!looks_vietnamese("Mă duc acasă să mănânc cu fratele meu, că e târziu.")); - assert!(!looks_vietnamese("Đurđa i Đorđe su se vratili kući, a Đakovo je daleko.")); - } -} - -#[cfg(test)] -mod checker_tests { - use crate::{Options, ProofLang, check_text_with}; - - #[test] - fn vietnamese_paragraphs_under_english_get_no_marks() { - let text = "Hôm nay tôi đi học, trời rất đẹp và mát mẻ."; - let spans = [(0..text.len(), ProofLang::En)]; - assert!(check_text_with(text, &spans, &Options::default(), true, false).is_empty()); - // No language at all (the document default) behaves the same. - assert!(check_text_with(text, &[], &Options::default(), true, false).is_empty()); - } - - #[test] - fn english_keeps_its_marks_and_skips_vietnamese_names() { - let text = "We met Nguyễn yesterdy at the office."; - let issues = check_text_with(text, &[(0..text.len(), ProofLang::En)], &Options::default(), false, false); - let flagged: Vec<&str> = issues.iter().filter_map(|i| text.get(i.start..i.end)).collect(); - assert_eq!(flagged, ["yesterdy"]); - } -} diff --git a/docs/architecture.md b/docs/architecture.md index faa82e3dd..968f521d1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # WordCraft architecture -> **Last reviewed:** 2026-10-28 · **Last updated:** 2026-10-28 · **Change:** major (crate and layer inventory, source counts, proofing languages and module boundaries re-measured from this checkout; previously minor: `proof` is per language: English and Polish; previously major: first committed version; `plan/architecture.md` is local-only) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-28 · **Last updated:** 2026-11-01 · **Change:** minor (proofing moved out: `wordcraft-proof` is craft-libs' `craft-proof`, its Language data craft-i18n's `language-data/` embedded from `CRAFT_I18N_DIR`; previously major: crate and layer inventory, source counts, proofing languages and module boundaries re-measured from this checkout; previously minor: `proof` is per language: English and Polish; previously major: first committed version; `plan/architecture.md` is local-only) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 How WordCraft is built today. The longer design notes in `plan/architecture.md` are gitignored and local to the original author's machine; this file is the @@ -22,7 +22,7 @@ crate name links to its source tree. | L1 | [`doc`](../crates/doc/src) | 23,719 | Document model: stories, paragraphs, runs, tables, sections, styles, numbering, fields, notes, comments, revisions and equations; edit primitives and bidi | | L1 | [`fonts`](../crates/fonts/src) | 7,088 | Font database, metrics, outlines, HarfRust shaping, Word font substitution and OpenType MATH | | L1 | [`metafile`](../crates/metafile/src) | 3,614 | Shared Windows Metafile parsing and drawing primitives used by format readers and renderers | -| L1 | [`proof`](../crates/proof/src) | 16,326 | Fifteen spelling dictionaries: English (US, GB, CA), Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish (Spain, Mexico, Latin America, Uruguay); language modules provide spelling, suggestions and grammar, while Bulgarian and German have hyphenation only | +| L1 (external) | [`craft-proof`](https://github.com/storytold/craft-libs/tree/main/crates/proof) (package alias `wordcraft-proof`) | — | Proofing moved to craft-libs (CP1, 2026-11): fifteen spelling dictionaries: English (US, GB, CA), Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish (Spain, Mexico, Latin America, Uruguay); language modules provide spelling, suggestions and grammar, while Bulgarian and German have hyphenation only. Its word lists and patterns are craft-i18n's `language-data/`, embedded from `CRAFT_I18N_DIR` at the commit in `craft-i18n.rev` (CI and releases set `CRAFT_I18N_REQUIRED=1`); without them English spelling falls back to an installed Hunspell English or craft-proof's built-in Moby list, and pattern hyphenation is off (Bulgarian, Turkish and Russian hyphenate by rule) | | L2 | [`docbin`](../crates/docbin/src) | 8,815 | Word 97–2003 `.doc` reader (FIB, piece table, FKPs, SPRMs, stylesheet, lists, tables, notes, pictures) over a CFB container | | L2 | [`docx`](../crates/docx/src) | 37,376 | OOXML WordprocessingML reader/writer (Transitional and Strict), OMML, custom properties and preserved VBA parts | | L2 | [`formats`](../crates/formats/src) | 23,058 | TXT, Markdown, HTML, RTF, ODT and LaTeX import/export through the shared document model | @@ -69,6 +69,7 @@ crate name links to its source tree. | Date | Change | Summary | |---|---|---| +| 2026-11-01 | minor | `proof` left the workspace: it is `craft-proof` in storytold/craft-libs (with its history), and the spelling and hyphenation data is craft-i18n's `language-data/` (`CRAFT_I18N_DIR`); `assets/spelling` and `assets/hyphenation` are gone | | 2026-10-28 | major | Recounted workspace crates, layers and Rust source lines; refreshed proofing languages and documented current engine/layout module boundaries and source links | | 2026-10-10 | minor | `proof`: proofing languages (English, Polish), affix engine, Polish patterns; layout and engine route each run by its language | | 2026-10-10 | major | First committed architecture doc, from the crates' sources and `AGENTS.md` | diff --git a/docs/gaps.md b/docs/gaps.md index d55af8c67..97446b0f8 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where WordCraft falls short of Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** trivial (DOCX table rows and cells inside selected alternate-content branches survive open and repeated saves (#5219); broader corpus qualification and readiness estimates unchanged); previously trivial (arrowheads keep their kind, width and length and are drawn at Word's sizes; no ranked gap changed); previously trivial (.doc default font from the stylesheet's standard font, #5066; no ranked gap changed); previously trivial (a middle or bottom cell of a row split across pages lines up on the first page; no ranked gap changed); previously trivial (tab leader dots spaced and placed as in Word; no ranked gap changed); previously trivial (a list level above counts its first number at the first deeper item; no ranked gap changed); previously trivial (only the PDF sent to the printer leaves out link annotations; Save these pages as PDF keeps them, #4734; no ranked gap changed); previously trivial (documents with links print: the print job leaves out link annotations, #4734; no ranked gap changed; previously trivial: aligned-copy review fixes retain the document axis through scrolling and include secondary selections (#4764)); previously trivial (connector rerouting indexes laid-out object positions once per batch (#3631); previously trivial (custom-property bookmark-link preservation, #4767); previously trivial (Relative font-size commands preserve mixed sizes in the selection (#4903); existing button steps and one-point shortcuts are unchanged; readiness estimates unchanged); previously trivial (Arabic Backspace removes one combining mark and keeps its base letter (#4757); previously trivial (note conversion reference-run membership uses one ordered walk, #4750); previously trivial (DOCX comment-ID save lookups); previously trivial (gap 19: RTF reads and writes WMF and EMF pictures, #4259); previously trivial (#4658 bounds nested Markdown reference-label work); previously trivial (HTML import keeps preformatted lines with CR or CRLF input newlines, #4810); previously trivial (Recipient labels continue across product-sized sheets instead of stopping after the first sheet (#4880)); previously trivial (View windows: several open documents and Switch Windows in the engine; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: automatic Asian/Latin and Asian/number spacing laid out, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: numbered headings, #1793; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: #8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-02 · **Change:** trivial (DOCX table rows and cells inside selected alternate-content branches survive open and repeated saves (#5219); broader corpus qualification and readiness estimates unchanged); previously trivial (gap 8 points at craft-proof in craft-libs, its data in craft-i18n, #5202; no ranked gap changed); previously trivial (arrowheads keep their kind, width and length and are drawn at Word's sizes; no ranked gap changed); previously trivial (.doc default font from the stylesheet's standard font, #5066; no ranked gap changed); previously trivial (a middle or bottom cell of a row split across pages lines up on the first page; no ranked gap changed); previously trivial (tab leader dots spaced and placed as in Word; no ranked gap changed); previously trivial (a list level above counts its first number at the first deeper item; no ranked gap changed); previously trivial (only the PDF sent to the printer leaves out link annotations; Save these pages as PDF keeps them, #4734; no ranked gap changed); previously trivial (documents with links print: the print job leaves out link annotations, #4734; no ranked gap changed; previously trivial: aligned-copy review fixes retain the document axis through scrolling and include secondary selections (#4764)); previously trivial (connector rerouting indexes laid-out object positions once per batch (#3631); previously trivial (custom-property bookmark-link preservation, #4767); previously trivial (Relative font-size commands preserve mixed sizes in the selection (#4903); existing button steps and one-point shortcuts are unchanged; readiness estimates unchanged); previously trivial (Arabic Backspace removes one combining mark and keeps its base letter (#4757); previously trivial (note conversion reference-run membership uses one ordered walk, #4750); previously trivial (DOCX comment-ID save lookups); previously trivial (gap 19: RTF reads and writes WMF and EMF pictures, #4259); previously trivial (#4658 bounds nested Markdown reference-label work); previously trivial (HTML import keeps preformatted lines with CR or CRLF input newlines, #4810); previously trivial (Recipient labels continue across product-sized sheets instead of stopping after the first sheet (#4880)); previously trivial (View windows: several open documents and Switch Windows in the engine; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: automatic Asian/Latin and Asian/number spacing laid out, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: numbered headings, #1793; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: #8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Highlight update:** 2026-10-25 · Highlight shortcut toggles the last chosen colour off when the whole selection has it; explicit colour commands remain setters (#4085). Readiness estimates unchanged. > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** trivial (#26: MHT opens and `w:altChunk` is brought in; previously trivial: aligned-copy review fixes retain the document axis through scrolling and include secondary selections (#4764)); previously trivial (#15: bibliography sources keep a corporate author, #4848); previously trivial (Table Properties applies to the selected rows, columns and cells, #4857); previously trivial (Relative font-size commands preserve mixed sizes in the selection (#4903); existing button steps and one-point shortcuts are unchanged; readiness estimates unchanged); previously trivial (Arabic Backspace removes one combining mark and keeps its base letter (#4757); previously trivial (Recipient labels continue across product-sized sheets instead of stopping after the first sheet (#4880)); previously trivial (#7: Custom Document Properties dialog, #2982; previously trivial: egui 0.37 migration: the interface catalogs stay shared and pinned by `craft-i18n.rev` at `9205108cfdced5177706264038848b68b2422d2b` (27 languages, English fallback); input-method handling has new regressions but managed CI passed, and native IME acceptance plus native-speaker catalog review remain open; previously trivial: Markdown code spans keep line-broken text literal, #3738; previously trivial: small follow-ups from the field, table and AutoFormat batch, #4232; previously trivial: highlight target includes selected paragraph marks, #4085; previously trivial: full-target highlight check beyond 10,000 runs, #4085; previously trivial: Exact-spacing table contact uses the specified line height rather than paragraph-mark metrics (#3977); previously trivial: cu20we: restrict pre-spacing float origins to paragraph anchors; document cap helpers and regressions; previously trivial: a command that changes nothing is not an edit: no undo step, no unsaved changes, no relayout, and the redo stack survives it; previously minor: #8 narrowed: Spanish proofing (Spain, Mexico, Latin America, Uruguay) from the installed Hunspell dictionaries, #3360, #3553; previously trivial: gap 10: group connector rerouting preserves transformed free and unresolved endpoints under nonuniform scaling; obstacle routing still missing; readiness estimates unchanged; previously trivial: a word with no break opportunity in it no longer makes the layout quadratic, #3412; previously trivial: the ranked gap list held two rows each for #19, #20 and #21; each gap appears once now; a document with many relationships opens in the time its part takes, #3411; previously trivial: the file commands that change the document are checked on a protected document, the protection of a protected document needs an explicit stop, the session author can't change through `file.setAuthor` under protection (the CLI's `--author` uses the same protection check), and the post-check compares the settings and the metadata; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: automatic Asian/Latin and Asian/number spacing laid out, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: numbered headings, #1793; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: #8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 @@ -72,7 +72,7 @@ Text context menus now include the existing formatting mini toolbar (#2959), inc | 6 | **Print dialog depth**: desktop printer choice, copies, collation, duplex and grayscale submit through craft-print; File › Print retains preview, page ranges and odd/even (#641) and scales pages to another paper size (#2108); driver tray/options and pages per sheet remain; web prints through the browser (#209) | B | `ui.print` → `apps/wordcraft/src/print.rs`; issue #15 | Everyone who prints | 5–8 | [hardware-parity.md](hardware-parity.md) | | 7 | **Dialog depth**: Word-observed spelling/grammar task pane added (shared review commands, current-story traversal; see [spelling-review.md](spelling-review.md)); 61 modal dialogs vs Word's ~100 (Custom Document Properties from File › Info, #2982; Drop Cap Options matches Word's menu and dialog; Split Cells and Convert Text to Table match Word's observed limits and defaults; Format Page Numbers carries Word's number formats and Continue from previous section / Start at, #1113) | B | `Dialog` enum in `crates/ui-egui/src/dialogs.rs`; Cell Margins (#1960) now opens with current defaults and applies only confirmed edits | Power users reach for dialogs the ribbon alone doesn't cover | 11–19 | [ui-parity.md](ui-parity.md) | | 9 | **Password-protected `.doc` files with an AES header in the Word 97-2003 format** can't be opened: [MS-OFFCRYPTO] defines AES only for encrypted packages, so these are refused with a clear message. Encrypted packages open under any Word extension (`.docx`, `.doc`, `.dot`…) with either agile (Word 2010+) or Standard (Word 2007, AES-ECB) encryption; protected `.docx` saves with agile encryption (#55, #281, gap 9) | | `crates/docx/src/crypt.rs` (agile and Standard); `crates/docbin/src/crypt.rs` opens XOR/RC4/RC4 CryptoAPI `.doc` with a password (#1079). All synthetic fixtures; untested on Word-written files | Corporate and legal users with old files | 0 (refused by design; nothing left to build) | [file-format-parity.md](file-format-parity.md) | -| 8 | **Proofing covers English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish only**; no dictionaries or grammar for the other ~41 languages Word proofs (text in them is left unmarked), and no hyphenation for them except Bulgarian and German | | `crates/proof` `ProofLang::PROOFED`: English (American, British-spelling and Canadian, each flagging the other's spellings, #1281), Polish, Portuguese (pt-BR via the installed VERO dictionary, #797), Italian (it-IT via the installed Hunspell dictionary), Croatian (hr-HR, 2026-10-13), Turkish (tr_TR spelling, grammar, TDK hyphenation, 2026-10-13) Russian (the installed `ru_RU` dictionary, 2026-10-12) Indonesian (the installed `id_ID` dictionary, #498, 2026-10-16) and Spanish (es-ES, es-MX, es-419 and es-UY via the installed RLA-ES dictionaries, Uruguayan `vos` forms, #3360, #3553, 2026-10-22), a language per run; Bulgarian (#826) and German (TeX patterns, 2026-10-14) have hyphenation only; open PRs add Danish (#723), Russian (#894) and Czech (#1048); Word ships 120 proofing tools; issues #25, #40, #100 open | Every writer outside English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish | 20–40 | [localization-parity.md](localization-parity.md) | +| 8 | **Proofing covers English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish only**; no dictionaries or grammar for the other ~41 languages Word proofs (text in them is left unmarked), and no hyphenation for them except Bulgarian and German | | craft-proof (storytold/craft-libs `crates/proof`, data in craft-i18n `language-data/`) `ProofLang::PROOFED`: English (American, British-spelling and Canadian, each flagging the other's spellings, #1281), Polish, Portuguese (pt-BR via the installed VERO dictionary, #797), Italian (it-IT via the installed Hunspell dictionary), Croatian (hr-HR, 2026-10-13), Turkish (tr_TR spelling, grammar, TDK hyphenation, 2026-10-13) Russian (the installed `ru_RU` dictionary, 2026-10-12) Indonesian (the installed `id_ID` dictionary, #498, 2026-10-16) and Spanish (es-ES, es-MX, es-419 and es-UY via the installed RLA-ES dictionaries, Uruguayan `vos` forms, #3360, #3553, 2026-10-22), a language per run; Bulgarian (#826) and German (TeX patterns, 2026-10-14) have hyphenation only; open PRs add Danish (#723), Russian (#894) and Czech (#1048); Word ships 120 proofing tools; issues #25, #40, #100 open | Every writer outside English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish | 20–40 | [localization-parity.md](localization-parity.md) | | 10 | **Objects**: text in a freely rotated text box (free rotation and flips landed, #332; live rotation preview while dragging fixed for pictures and shapes without text stories, #2277; text direction, alignment and linked text boxes landed, #369; contour wrap and wrap side landed, #354), shape effects, WordArt 3-D effects and connector routing around shapes (WordArt effects and warps and glued connectors landed, #416), dragging members inside a Drawing Canvas (the canvas landed, #344) | | [parity-checklist.md](parity-checklist.md) (Shape Format 11/11, Layout 22/22); group and ungroup landed (#267) | Newsletters, flyers, reports with diagrams | 20–30 | [target-app-parity.md](target-app-parity.md) | | 11 | **Interface languages**: 3 of the 12 key languages missing (hi, ar, id) and pt-PT (French landed, #729; Vietnamese, #1434; Korean, #1216; 21 interface languages in `crates/ui-egui/src/i18n`); open PRs for Arabic (#737, #1042, #1032), Indonesian (#572) and pt-PT (#1028); no mirrored RTL interface yet; ~5% of strings still English in the translated catalogs | | [localization-parity.md](localization-parity.md) | Most of the world's writers | 50–80 | [localization-parity.md](localization-parity.md) | | 12 | **Draw tab / ink**: pens, pencil, highlighter, eraser, Select and Hide Ink landed (#307); Lasso Select, Add Pen, Ink to Shape and Ink Replay landed (#395); Ink to Shape also recognises single-stroke arrows (a line plus a V head drawn in one stroke becomes a straight connector with an arrowhead); two-stroke arrows (a line and a separate V or closed-triangle head) and one-stroke arrows with a closed triangle head landed (#1778); missing: Ink to Math, pressure, rendering/editing Word's own ink (drawing-level and direct run-level InkML content parts now survive DOCX save) | | [parity-checklist.md](parity-checklist.md) (Draw 10/11) | Pen and tablet users | 4–8 | [hardware-parity.md](hardware-parity.md) | @@ -158,6 +158,7 @@ Table rows and cells inside `mc:AlternateContent` use the same selected-branch p |---|---|---| | 2026-11-02 | trivial | #26 narrowed: MHT opens and `w:altChunk` content is brought in on open; saving MHT remains | | 2026-11-01 | trivial | Preserve table rows and cells inside selected alternate-content branches (#5219); readiness estimates unchanged. | +| 2026-11-01 | trivial | Gap 8's evidence names craft-proof (storytold/craft-libs `crates/proof`) and craft-i18n `language-data/` instead of WordCraft's removed `crates/proof` (#5202). No ranked gap changed. | | 2026-11-01 | trivial | Arrowheads keep their kind, width and length (DrawingML `a:headEnd`/`a:tailEnd` `type`/`w`/`len`, VML `startarrow`/`endarrow` with `…arrowwidth`/`…arrowlength`), read and written back, and are drawn 2, 3 or 5 times the line's width long and wide (small, medium, large), a line under 2 points as a 2-point one, as measured in Word for Windows; every kind is still drawn as a triangle; no ranked gap changed; readiness estimates unchanged | | 2026-10-31 | trivial | The .doc reader's default font is the stylesheet's standard font, not the font table's first entry (#5066); no ranked gap changed; readiness estimates unchanged | | 2026-11-01 | trivial | A middle or bottom cell of a row split across pages lines up on the first page; no ranked gap changed; readiness estimates unchanged | diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 200498eb5..f6e5b9681 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,6 +1,6 @@ # Localization parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** trivial (word count counts each Han ideograph and kana as a word, #4547; previously trivial: the Traditional Chinese interface draws hanzi to the Taiwan standard with an installed Traditional Chinese font first: PingFang TC, Microsoft JhengHei, Noto Sans CJK TC; previously minor: the egui 0.37 migration preserves the equation “Delete Around Caret in Equation” translation in all 27 catalogs through the shared catalog pin; previously minor: Spanish proofing: es-ES, es-MX, es-419 and es-UY from the installed Hunspell dictionaries, Uruguayan `vos` forms, opening `¿`/`¡` checks, #3360, #3553; previously minor: the interface catalogs moved to storytold/craft-i18n and are looked up through the `craft-i18n` crate, embedded at build time from the commit in `craft-i18n.rev`; previously trivial: interface-language tests: a missing translation never fails a test, `cargo xtask i18n-report` in `cargo xtask ci` prints the strings each language lacks, and lookup and template tests use inline fixture catalogs or the pseudo-language; previously trivial: interface audit: missing catalog entries are reported per language and don't fail the audit; previously minor: disabled-control reasons in ribbon hints translated in every catalog, #2678; previously trivial: Slovenian interface catalog: every key of every other catalog, 2,614 entries; previously minor: Romanian interface catalog; previously trivial: the File › Home greeting is translated in every catalog; previously trivial: Japanese row: ruby (Phonetic Guide) is supported and HTML export/import keeps it, #1681; previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously minor: English dialects: en-GB, en-CA, en-AU and the other British-spelling Englishes proof their own spellings and flag the other dialect's, #1281, #1045; previously minor: how to install the Hunspell dictionaries on Windows, macOS and Linux: `docs/spelling-dictionaries.md`; previously minor: Read Aloud speaks each sentence in its own language, #1843; previously minor: Indonesian proofing: id-ID spelling from the installed Hunspell dictionary, dictionary-driven suggestions, generic checks with Indonesian messages, #498; previously trivial: print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** trivial (proofing file paths point at craft-proof in craft-libs and craft-i18n `language-data/`, #5202); previously trivial (word count counts each Han ideograph and kana as a word, #4547; previously trivial: the Traditional Chinese interface draws hanzi to the Taiwan standard with an installed Traditional Chinese font first: PingFang TC, Microsoft JhengHei, Noto Sans CJK TC; previously minor: the egui 0.37 migration preserves the equation “Delete Around Caret in Equation” translation in all 27 catalogs through the shared catalog pin; previously minor: Spanish proofing: es-ES, es-MX, es-419 and es-UY from the installed Hunspell dictionaries, Uruguayan `vos` forms, opening `¿`/`¡` checks, #3360, #3553; previously minor: the interface catalogs moved to storytold/craft-i18n and are looked up through the `craft-i18n` crate, embedded at build time from the commit in `craft-i18n.rev`; previously trivial: interface-language tests: a missing translation never fails a test, `cargo xtask i18n-report` in `cargo xtask ci` prints the strings each language lacks, and lookup and template tests use inline fixture catalogs or the pseudo-language; previously trivial: interface audit: missing catalog entries are reported per language and don't fail the audit; previously minor: disabled-control reasons in ribbon hints translated in every catalog, #2678; previously trivial: Slovenian interface catalog: every key of every other catalog, 2,614 entries; previously minor: Romanian interface catalog; previously trivial: the File › Home greeting is translated in every catalog; previously trivial: Japanese row: ruby (Phonetic Guide) is supported and HTML export/import keeps it, #1681; previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously minor: English dialects: en-GB, en-CA, en-AU and the other British-spelling Englishes proof their own spellings and flag the other dialect's, #1281, #1045; previously minor: how to install the Hunspell dictionaries on Windows, macOS and Linux: `docs/spelling-dictionaries.md`; previously minor: Read Aloud speaks each sentence in its own language, #1843; previously minor: Indonesian proofing: id-ID spelling from the installed Hunspell dictionary, dictionary-driven suggestions, generic checks with Indonesian messages, #498; previously trivial: print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 Interface languages, script support and proofing per language. How translation works (adding a language, the embedded catalogs): `crates/ui-egui/src/i18n.rs`. The catalogs, their format and @@ -128,9 +128,9 @@ Set Proofing Language › Set As Default keeps the chosen language in user prefe | Language | Spelling | Suggestions | Grammar | Hyphenation | |---|---|---|---|---| -| English: American (`en-US`, other regions, text with no language), British spelling (`en-GB`, `en-AU`, `en-NZ`, `en-IE`, `en-ZA`, `en-IN`…), Canadian (`en-CA`) | Moby word list (public domain, ~160k words) with inflection rules, plus our list of ~280 American/British pairs (`assets/spelling/en-variants.txt`) with inflections by rule: each English flags the other dialect's spellings (`colour` in en-US; `color`, `center`, `analyze` in en-GB; `organise` in en-CA), British English accepts `-ize` and `-ise` (Oxford spelling), and a dialect's form of a known word is right even if the list lacks it (`realisation`). No system dictionary needed | the dialect's own spelling first (`colour` → `color`), then edit distance with the shared suggestions in the dialect's spelling | repeated word, `a`/`an`, sentence capital, space before punctuation, double space | Moby dictionary, our Liang patterns, heuristic | -| Russian (`ru`) | A Russian Hunspell dictionary installed on the system (`ru_RU` from the OS, a package manager, LibreOffice's `dict-ru` or the `WORDCRAFT_DICTIONARIES` folder; UTF-8, KOI8-R or Windows-1251; not bundled, so no Russian spelling marks without it), read by the same affix engine; `ё` optional (`елка`/`ёлка`, `еще`/`ещё`); hyphenated words whole, then by parts (`кто-то`, `по-русски`, `кое-где`, `северо-западный`, `ИИ-консультант`); solid compounds (`высокоэластичный`), `не` with adjectives, participles and verbal nouns (not verbs), productive prefixes (`ультра-`, `эко-`) and prefixed verbs; abbreviations with endings (`ГИПа`) and camel case left alone; our supplement of ~200 modern words with their declensions (`assets/spelling/ru-extra.txt`: `маркетплейс`, `промокод`, `интеграция`, `колеровать`) | `ё`/`е` first, then slips within letter groups (unstressed vowels, voiced and voiceless pairs, `ь`/`ъ`), the common confusions (`жы`, `чя`, `тся`/`ться`, doubled consonants, unpronounced consonants: `здраствуй` → `здравствуй`), a missing space (`незнаю` → `не знаю`; not a preposition before a verb), one typo, then a stem scan | Russian messages: repeated word, space before punctuation, double space, sentence capital (knows `т. е.`, `г.`, `руб.`, `ул.`, initials, numbers, dialogue dashes), possibly missing comma before `что`/`чтобы`/`если`/`хотя`/`поскольку`/`который` and before `потому что` (not after conjunctions and particles such as `и`, `только`, `даже`, `так`, `для того`; not after a preposition; not in `хотя бы`, `что-то`), doubled punctuation | The rules of Russian word division as code (no pattern file): a vowel on each side, two letters at least, `ь`/`ъ`/`й` kept with the letter before, a break after `ъ`, a consonant cluster divided after its first consonant | -| Polish (`pl`) | The Hunspell `pl_PL` dictionary installed on the system (OS package, Homebrew, LibreOffice, the folder set in File › Options › Proofing › Dictionaries folder, or `WORDCRAFT_DICTIONARIES`; not bundled, so no Polish spelling marks without it; the same pane shows which file each language found), read at runtime by a Hunspell-style affix engine (`crates/proof/src/affix.rs`); names capitalized, compounds part by part, `Kennedy'ego` | missing diacritics first (`zolw` → `żółw`), the dictionary's confusion table (`żeka` → `rzeka`, `chuśtawka` → `huśtawka`), missing space (`napewno` → `na pewno`), one typo, then a stem scan within two typos | Polish messages: repeated word, space before punctuation, double space, sentence capital (knows `np.`, `r.`, initials, ordinals, dialogue dashes), missing comma before `że`/`który`/`ponieważ`/`gdyż`/`aby`/`żeby`/`jeśli`/`jeżeli`/`gdyby`/`lecz` (not after `mimo`, `chyba`, `tak`, prepositions…), `we`/`ze` (`we wtorek`, `ze szkoły`), doubled punctuation | TeX Polish patterns (Kołodziejska, Jackowski, Ryćko; MIT option) with their 20 exception words; a Polish syllable heuristic for letters outside the alphabet | +| English: American (`en-US`, other regions, text with no language), British spelling (`en-GB`, `en-AU`, `en-NZ`, `en-IE`, `en-ZA`, `en-IN`…), Canadian (`en-CA`) | Moby word list (public domain, ~160k words) with inflection rules, plus our list of ~280 American/British pairs (craft-i18n [`language-data/en-us/en-variants.txt`](https://github.com/storytold/craft-i18n/blob/main/language-data/en-us/en-variants.txt)) with inflections by rule: each English flags the other dialect's spellings (`colour` in en-US; `color`, `center`, `analyze` in en-GB; `organise` in en-CA), British English accepts `-ize` and `-ise` (Oxford spelling), and a dialect's form of a known word is right even if the list lacks it (`realisation`). No system dictionary needed | the dialect's own spelling first (`colour` → `color`), then edit distance with the shared suggestions in the dialect's spelling | repeated word, `a`/`an`, sentence capital, space before punctuation, double space | Moby dictionary, our Liang patterns, heuristic | +| Russian (`ru`) | A Russian Hunspell dictionary installed on the system (`ru_RU` from the OS, a package manager, LibreOffice's `dict-ru` or the `WORDCRAFT_DICTIONARIES` folder; UTF-8, KOI8-R or Windows-1251; not bundled, so no Russian spelling marks without it), read by the same affix engine; `ё` optional (`елка`/`ёлка`, `еще`/`ещё`); hyphenated words whole, then by parts (`кто-то`, `по-русски`, `кое-где`, `северо-западный`, `ИИ-консультант`); solid compounds (`высокоэластичный`), `не` with adjectives, participles and verbal nouns (not verbs), productive prefixes (`ультра-`, `эко-`) and prefixed verbs; abbreviations with endings (`ГИПа`) and camel case left alone; our supplement of ~200 modern words with their declensions (craft-i18n [`language-data/ru/ru-extra.txt`](https://github.com/storytold/craft-i18n/blob/main/language-data/ru/ru-extra.txt): `маркетплейс`, `промокод`, `интеграция`, `колеровать`) | `ё`/`е` first, then slips within letter groups (unstressed vowels, voiced and voiceless pairs, `ь`/`ъ`), the common confusions (`жы`, `чя`, `тся`/`ться`, doubled consonants, unpronounced consonants: `здраствуй` → `здравствуй`), a missing space (`незнаю` → `не знаю`; not a preposition before a verb), one typo, then a stem scan | Russian messages: repeated word, space before punctuation, double space, sentence capital (knows `т. е.`, `г.`, `руб.`, `ул.`, initials, numbers, dialogue dashes), possibly missing comma before `что`/`чтобы`/`если`/`хотя`/`поскольку`/`который` and before `потому что` (not after conjunctions and particles such as `и`, `только`, `даже`, `так`, `для того`; not after a preposition; not in `хотя бы`, `что-то`), doubled punctuation | The rules of Russian word division as code (no pattern file): a vowel on each side, two letters at least, `ь`/`ъ`/`й` kept with the letter before, a break after `ъ`, a consonant cluster divided after its first consonant | +| Polish (`pl`) | The Hunspell `pl_PL` dictionary installed on the system (OS package, Homebrew, LibreOffice, the folder set in File › Options › Proofing › Dictionaries folder, or `WORDCRAFT_DICTIONARIES`; not bundled, so no Polish spelling marks without it; the same pane shows which file each language found), read at runtime by a Hunspell-style affix engine (craft-proof [`src/affix.rs`](https://github.com/storytold/craft-libs/blob/main/crates/proof/src/affix.rs) in craft-libs); names capitalized, compounds part by part, `Kennedy'ego` | missing diacritics first (`zolw` → `żółw`), the dictionary's confusion table (`żeka` → `rzeka`, `chuśtawka` → `huśtawka`), missing space (`napewno` → `na pewno`), one typo, then a stem scan within two typos | Polish messages: repeated word, space before punctuation, double space, sentence capital (knows `np.`, `r.`, initials, ordinals, dialogue dashes), missing comma before `że`/`który`/`ponieważ`/`gdyż`/`aby`/`żeby`/`jeśli`/`jeżeli`/`gdyby`/`lecz` (not after `mimo`, `chyba`, `tak`, prepositions…), `we`/`ze` (`we wtorek`, `ze szkoły`), doubled punctuation | TeX Polish patterns (Kołodziejska, Jackowski, Ryćko; MIT option) with their 20 exception words; a Polish syllable heuristic for letters outside the alphabet | | Portuguese (`pt`, any region) | The Hunspell `pt_BR` dictionary installed on the system — VERO and its derivatives (same places as Polish; not bundled, LGPL/MPL), read at runtime by the same affix engine, which reads its `FLAG UTF-8`, 106 flags, continuation classes and `FORBIDDENWORD` spellings; names capitalized, compounds part by part (`guarda-roupa`, `ex-marido`) | missing accents first (`coraçao` → `coração`, `saude` → `saúde`), the dictionary's confusion table (`seje` → `seja`, `excessão` → `exceção`), missing space (`nãoé` → `não é`), one typo, then an accent-folding stem scan | Portuguese messages: repeated word (the emphatic doubles are allowed), space before punctuation, double space, sentence capital (knows `etc.`, `pág.`, `Dr.`, initials, ordinals, dialogue dashes), doubled punctuation; no comma rule — `que` opens a clause only sometimes | TeX Portuguese patterns (Rezende, Almeida, Araujo, Benevides; BSD 3-clause) with their 2 exception words | | Czech (`cs`, `cs-CZ`, `ces`, `cze`) | The system Hunspell `cs_CZ` dictionary, read at runtime (Debian/Ubuntu: `hunspell-cs`; the same system, LibreOffice and `WORDCRAFT_DICTIONARIES` search paths as Polish). No dictionary data is bundled; without an installed dictionary spelling stays off | suggestions from the loaded dictionary's MAP/REP/TRY/KEY tables, missing spaces and a stem scan; short-word splits rank after deleting an extra letter; NOSUGGEST stems and their derived forms are accepted when typed but excluded from suggestions | Czech messages (including Czech quotation marks) for repeated words, space before punctuation and double spaces only; digit-space-colon-space-digit ratios keep their spaces; no Czech-specific grammar rules | existing English fallback, as for `Other`; no Czech patterns bundled (the TeX/LibreOffice Czech patterns are GPL-2) | | Italian (`it`, any region) | The Hunspell `it_IT` dictionary installed on the system (same places as Polish and Portuguese; not bundled — the upstream file is GPL-licensed), read at runtime by the same affix engine; elided forms check the word after the apostrophe (`l'amico`, `un'idea`, `c'è`, `dov'è`, `senz'altro`), truncations keep their apostrophe (`po'`, `mo'`), hyphenated compounds part by part | missing accents first (`caffe` → `caffè`, `perche` → `perché`), the dictionary's confusion table, missing space (`perme` → `per me`), one typo, then an accent-folding stem scan | Italian messages: repeated word (emphatic doubles like `piano piano` are allowed), space before punctuation, double space, sentence capital (knows `ecc.`, `dott.`, `prof.`, initials, ordinals), doubled punctuation, `un pò` → `un po'`, `qual'è` → `qual è`, `E'` → `È`, `un'altro` → `un altro` and `un altra` → `un'altra` (gender from the ending, masculine `-ista`/`-ma`/`-cida`… left alone); these work without a dictionary; no comma rule | TeX Italian patterns (Beccari; MIT option), no exception words | @@ -227,6 +227,7 @@ WordCraft keeps 2,54 cm). | Date | Change | Summary | |---|---|---| +| 2026-11-01 | trivial | Proofing paths updated for the move: the affix engine is craft-proof's `src/affix.rs` in storytold/craft-libs, the English variants and Russian supplement are craft-i18n `language-data/` files (#5202). | | 2026-10-28 | trivial | Traditional Chinese interface: an installed Traditional Chinese font (PingFang TC, Microsoft JhengHei, Noto Sans CJK TC; the TC face of a pan-CJK collection) goes before the embedded Noto Sans CJK SC, whose mainland glyph shapes looked wrong to Taiwan readers; native only, wasm unchanged; estimates unchanged | | 2026-10-24 | minor | Preserved “Delete Around Caret in Equation” in all 27 shared WordCraft catalogs; the native/release pin and Flatpak source advance together. | | 2026-10-23 | minor | Interface catalogs moved to storytold/craft-i18n (`locales//wordcraft.tsv`), looked up through craft-libs' `craft-i18n` crate and embedded from the pinned commit (`craft-i18n.rev`); strings other Crafting Apps already translate in the shared catalog now show translated; the "Open" paragraph-spacing preset no longer shows the file verb | @@ -280,7 +281,7 @@ WordCraft keeps 2,54 cm). | 2026-10-11 | minor | Hungarian interface catalog (2,419 entries, built-in style names shown translated; covers everything the interface audit finds), locale selection, saved preference and font coverage checks | | 2026-10-11 | minor | Turkish interface catalog (1,307 entries), locale selection, saved preference, Turkish casing in command search and font coverage checks; catalog sizes re-counted | | 2026-10-17 | trivial | English spelling (#1694): `-ically` adverbs come from their `-ic` adjective ("specifically", "scientifically"), so they're no longer flagged; other words don't take `-ally` ("desktopally" is still flagged) | -| 2026-10-11 | trivial | English spelling (#520): our supplement `assets/spelling/en-extra.txt` adds ~220 common modern words the Moby list lacks ("desktop", "download", "email"…) | +| 2026-10-11 | trivial | English spelling (#520): our supplement `assets/spelling/en-extra.txt` (since CP1.3 craft-i18n `language-data/en-us/en-extra.txt`) adds ~220 common modern words the Moby list lacks ("desktop", "download", "email"…) | | 2026-10-10 | minor | Estonian interface catalog, locale selection, saved preference and font coverage checks; count labels put the noun first so they read right at 1 | | 2026-10-10 | minor | Serbian (Cyrillic and Latin) interface merged (#250) | | 2026-10-10 | major | First version: twelve-language table, catalog coverage measured, Word's 30 UI languages and 120 proofing tools listed | diff --git a/docs/localization-tr.md b/docs/localization-tr.md index 4c7dfb290..6969c54e7 100644 --- a/docs/localization-tr.md +++ b/docs/localization-tr.md @@ -52,7 +52,7 @@ test checks dotted and dotless I under Turkish casing with fixture labels, and t and ASCII command ids still match. The shared checks (catalog validation and the missing-translation report, `cargo xtask i18n-report`) include Turkish automatically. -Turkish proofing (`crates/proof/src/tr`) provides spelling against installed Hunspell `tr_TR` +Turkish proofing (`crates/proof/src/tr` in storytold/craft-libs, the `craft-proof` crate) provides spelling against installed Hunspell `tr_TR` dictionaries, spelling suggestions tuned for Turkish diacritics and detached particles, rule-based grammar checks (reduplications, conjunction commas, capitalization), and syllable hyphenation by TDK rules. diff --git a/docs/spelling-dictionaries.md b/docs/spelling-dictionaries.md index 70354e7e0..62c62e827 100644 --- a/docs/spelling-dictionaries.md +++ b/docs/spelling-dictionaries.md @@ -1,6 +1,6 @@ # Installing spelling dictionaries -> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-23 · **Change:** minor (ISO 8859-9 Turkish dictionaries are read, #3692; previously minor: Spanish: `es_ES`, `es_MX`, `es_419` and `es_UY` with their fallbacks, #3360, #3553; previously trivial: every Hunspell language shows (no dictionary), not only Polish and Portuguese, and Indonesian is listed, #3150; previously trivial: ISO 8859-1 dictionaries are read, #2824) +> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-11-02 · **Change:** trivial (the Flatpak's own dictionaries folder; previously trivial: WordCraft's own dictionaries folder is searched on macOS and Linux too, as on Windows, so a dictionary dropped there works without a setting; previously minor: ISO 8859-9 Turkish dictionaries are read, #3692; previously minor: Spanish: `es_ES`, `es_MX`, `es_419` and `es_UY` with their fallbacks, #3360, #3553; previously trivial: every Hunspell language shows (no dictionary), not only Polish and Portuguese, and Indonesian is listed, #3150; previously trivial: ISO 8859-1 dictionaries are read, #2824) English spelling works out of the box. Polish, Russian, Portuguese, Czech, Italian, Croatian, Turkish, Indonesian and Spanish spelling use a standard **Hunspell** dictionary (an `.aff` and a `.dic` file) that @@ -61,8 +61,14 @@ The first folder holding a matching pair wins: | System | Folders | |---|---| | Windows | `%LOCALAPPDATA%\WordCraft\dictionaries`, `%LOCALAPPDATA%\hunspell`, `%APPDATA%\WordCraft\dictionaries`, `%APPDATA%\hunspell`, LibreOffice's `share\extensions\dict-*` under `Program Files` (on any drive) | -| macOS | `~/Library/Spelling`, `/Library/Spelling`, `/opt/homebrew/share/hunspell`, `/usr/local/share/hunspell`, `/Applications/LibreOffice.app/Contents/Resources/extensions/dict-*` | -| Linux and BSD | `~/.local/share/hunspell`, `/usr/share/hunspell`, `/usr/share/myspell`, `/usr/share/myspell/dicts`, `/usr/local/share/hunspell`, `/usr/local/share/myspell`, LibreOffice's `share/extensions/dict-*` under `/usr/lib/libreoffice` or `/opt/libreoffice` | +| macOS | `~/Library/Application Support/WordCraft/dictionaries`, `~/Library/Spelling`, `/Library/Spelling`, `/opt/homebrew/share/hunspell`, `/usr/local/share/hunspell`, `/Applications/LibreOffice.app/Contents/Resources/extensions/dict-*` | +| Linux and BSD | `$XDG_DATA_HOME/wordcraft/dictionaries` (default `~/.local/share/wordcraft/dictionaries`), `~/.local/share/hunspell`, `/usr/share/hunspell`, `/usr/share/myspell`, `/usr/share/myspell/dicts`, `/usr/local/share/hunspell`, `/usr/local/share/myspell`, LibreOffice's `share/extensions/dict-*` under `/usr/lib/libreoffice` or `/opt/libreoffice` | + +The Flatpak runs in a sandbox: its `$XDG_DATA_HOME` is +`~/.var/app/ai.storyteller.wordcraft/data`, so its own folder is +`~/.var/app/ai.storyteller.wordcraft/data/wordcraft/dictionaries`, and it can't see the host's +`/usr/share/hunspell` or `~/.local/share/hunspell` (the paths above are for the other builds). +Put dictionaries in that folder. The web version can't read these folders, so these languages aren't spell-checked there. @@ -140,8 +146,10 @@ Never commit dictionary files to this repository: they stay on the user's system | Date | Change | Summary | |---|---|---| +| 2026-11-02 | trivial | The Flatpak's own dictionaries folder (`~/.var/app/ai.storyteller.wordcraft/data/wordcraft/dictionaries`); the host's folders are outside its sandbox | | 2026-10-22 | minor | Spanish (#3360, #3553): the file names for Spain, Mexico, Latin America and Uruguay and their fallbacks | | 2026-10-22 | trivial | Italian, Croatian, Turkish, Russian, Czech and Indonesian without a dictionary now show **(no dictionary)** (or **(dictionary unreadable)**) too, as this page already said; before, only Polish and Portuguese did. Indonesian is listed with its file names (#3150) | +| 2026-11-01 | trivial | WordCraft's own `dictionaries` folder under the user's application data is searched on macOS (`~/Library/Application Support/WordCraft/dictionaries`) and Linux/BSD (`$XDG_DATA_HOME/wordcraft/dictionaries`) too, before the system folders, as it was on Windows (craft-proof; ported from storytold/craft-libs#31 by brzuchal) | | 2026-10-23 | trivial | ISO 8859-9 (`SET ISO8859-9`) dictionaries are read, so an older Turkish `tr_TR` loads without converting it to UTF-8; the Turkish dictionary is read through the same size-capped reader and `SET` decoding as the others (#3692) | | 2026-10-18 | trivial | ISO 8859-1 (`SET ISO8859-1`) dictionaries are read, so the original VERO `pt_BR` works without converting it to UTF-8 (#2824) | | 2026-10-18 | minor | An installed dictionary that can't be read shows **(dictionary unreadable)** in the status bar, with the file and reason in its tooltip and in File › Options › Proofing (#2830) | diff --git a/packaging/linux/flatpak/ai.storyteller.wordcraft.yml b/packaging/linux/flatpak/ai.storyteller.wordcraft.yml index 11337ddda..c3c52b2d0 100644 --- a/packaging/linux/flatpak/ai.storyteller.wordcraft.yml +++ b/packaging/linux/flatpak/ai.storyteller.wordcraft.yml @@ -122,9 +122,10 @@ modules: - corpus - build-flatpak - .flatpak-builder - # The interface translations at the commit in craft-i18n.rev (a test keeps the two equal). + # The interface translations and Language data at the commit in craft-i18n.rev (a test keeps + # the two equal). - type: git url: https://github.com/storytold/craft-i18n - commit: 9205108cfdced5177706264038848b68b2422d2b + commit: 062a6375c322c88b96691a17f7b5fed71c51f7a8 dest: craft-i18n - cargo-sources.json diff --git a/xtask/src/layers.rs b/xtask/src/layers.rs index ee0e69e47..d7f4b702f 100644 --- a/xtask/src/layers.rs +++ b/xtask/src/layers.rs @@ -38,7 +38,6 @@ pub const TABLE: &[(&str, Class)] = &[ ("doc", Class::Layer(1)), ("fonts", Class::Layer(1)), ("metafile", Class::Layer(1)), - ("proof", Class::Layer(1)), ("layout", Class::Layer(2)), ("docx", Class::Layer(2)), ("docbin", Class::Layer(2)),