Skip to content
WM KeyboardWM Keyboard
Accessibility

Grammar check

Grammar checking with Harper on the phone, GiellaLT's checkers for the Sámi languages and Faroese, or LanguageTool on a server for more than 30 languages.

Grammar check runs your writing past Harper, the same open-source engine behind harper-ls, and surfaces fixable issues right above the keys as you type. Harper runs on the phone, so by default nothing you type leaves it to get checked. For other languages than English you can switch on LanguageTool, which checks on a server instead, and for North, Lule, South and Inari Sámi and Faroese you can download GiellaLT’s own checker, which runs on the phone.

Screenshot pending
The Grammar panel open above the key rows, with a spelling issue and its fix chips.

Harper checks English spelling, grammar, and style: agreement errors, punctuation and capitalization slips, redundant or repeated words, malapropisms, and a long tail of style nits. It reports twenty fine-grained kinds of issue, which the panel groups into four color-coded categories so an issue reads at a glance:

  • Correctness: spelling, typo, grammar, agreement, capitalization, punctuation, word boundaries, confused words, misheard phrases, usage
  • Clarity: readability, redundancy, repetition, word choice
  • Engagement: enhancement, style, miscellaneous
  • Delivery: formatting, regionalism, nonstandard

Any of those twenty can be switched off, either one at a time or a whole category at once. See Filtering what you see.

Each issue also lints against a specific English dialect: American (the default), British, Canadian, or Australian. You pick that independently of whatever language or layout you’re typing with, since Harper only understands English. See Options below for where to change it.

WM Keyboard  Tools  Grammar check  Checkers

The Checkers list at the top of the settings page says who checks your writing. It is a provider list, so rows can be dragged into another order, switched off, set up or removed. It starts with two rows:

  • Harper, on the phone and switched on. English only.
  • LanguageTool, switched off. It checks grammar, spelling and style in more than 30 languages, among them German, French, Spanish, Portuguese, Dutch, Polish, Ukrainian and Russian, but it runs on a server. Its row says where your text goes: Sends your text to api.languagetool.org.

A check goes to the first checker that is on and knows the language of the layout you’re typing with. Harper knows English; LanguageTool knows every language it has, and a language it lacks goes as auto, so the server works out what the text is. With the list as it starts, then, English stays on the phone and the other languages go to LanguageTool once you switch it on. Drag LanguageTool above Harper to have it check English too. When the first checker cannot answer (Harper isn’t in this build, the server is down), the next one that knows the language answers instead. With no checker that knows the language, Harper checks anyway, as Grammar check always did.

While LanguageTool answers, the panel’s title reads Grammar · LanguageTool, or the nickname you gave the server. Its issues use the same cards, the same four categories and the same filter as Harper’s: each LanguageTool category maps onto one of the twenty kinds (its typos onto Spelling or Typo, its casing onto Capitalization, its confused words onto Usage, and so on). English checks follow the dialect chip: American, British, Canadian or Australian.

Every check sends the text in the field, its language code, your Premium username and key if you set them, and the two options below. Nothing else. The panel checks after each pause in typing, but never sooner than 1.2 seconds after the last key while LanguageTool is first in line. Fix all, a fix chip and the Grammar selection action re-check the result the same way.

These are never sent:

  • a password field, or any field an app marks as one
  • anything typed in incognito, whether you turned it on or the field asked for it
  • anything while data saver holds Online grammar check

In each of those cases Harper still checks English, and for other languages the panel says why nothing is checked. Every request appears in Network activity as LanguageTool.

Tap the LanguageTool row to set it up:

FieldWhat it does
Server addressEmpty for LanguageTool’s public server, api.languagetool.org. Or the address of a server you run yourself, for instance http://192.168.1.20:8081
Username and API keyA LanguageTool Premium account. Give both or neither. With both and no address, checks go to the Premium server, api.languagetoolplus.com
Picky modeAlso report the style and typography issues the server leaves out by default
Your first languageA code such as de or fr. LanguageTool then also flags false friends from that language

Test asks the server for its list of languages, so a typo in an address shows up there rather than in the panel.

LanguageTool’s server is free software (LGPL), so you can run your own on a computer at home, or anywhere else, from the jar or one of the Docker images. Your text then never reaches a third party. Add it with Add a provider / LanguageTool: each server is its own row, with its own address and nickname. This is the same in every edition, F-Droid’s included.

The public server takes 20 checks a minute, 75 KB of text a minute and 20 KB a check. Grammar check keeps to that: a long note is cut into pieces of at most 18 KB at paragraph ends, then sentence ends, then spaces, and a piece already checked isn’t sent again until it changes. If a minute’s allowance is used up, the next check waits for it when that takes seconds, and the panel says the server is busy when it would take longer. Edit the text and it tries again. A Premium account or your own server is sent bigger pieces, up to 60 KB, with no pacing.

WM Keyboard  Tools  Grammar check

GiellaLT, the language technology group at UiT The Arctic University of Norway, writes grammar checkers for the languages it works on. Five have a published release, and the grammar settings list them under GiellaLT grammar checkers at the Advanced level, each with a Download button:

LanguageDownload
North Sámi1.5 GB
Lule Sámi375 MB
Faroese353 MB
South Sámi290 MB
Inari Sámi186 MB

Each is one file holding the whole checker: its tokeniser, its rules, the speller it suggests spellings from and its messages. It is kept as downloaded and read in place, so its size is what it takes on the phone. The download is checked against the release’s SHA-256 before it replaces anything.

With a checker downloaded, it checks first whenever the keyboard is in its language, before anything on the Checkers list, and it needs no row there. It runs on the phone, so nothing leaves it. The panel’s title then reads Grammar · GiellaLT, and each card shows GiellaLT’s title for the error, its explanation (in your phone’s language where the checker has one, else English) and its suggestions. The first check after the keyboard starts loads the checker, which takes a few seconds; later checks do not. A loaded checker also takes memory, roughly half its download: North Sámi’s holds about 650 MB, so on a phone with 4 GB or less, Android may close the keyboard while it is loaded. The keyboard unloads it with its other models once it has been hidden for 2 minutes, and Resources lists it while it is loaded, with a button to unload it sooner.

The checkers run on GiellaLT’s divvun-runtime, which is a download of its own the first time you download a checker. Unlike the rest of the app it is GPL-3.0, because its VISL CG-3 rule engine is.

Grammar check in the toolbox, with the dialect chip and a live issue count.
Grammar check in the toolbox, with the dialect chip and a live issue count.
Grammar check's tile in the toolbox, ready to open.

Open Grammar check from the toolbox to check whatever’s in the field you’re writing in. It isn’t pinned to the toolbar by default, but two built-in keyboard modes (Chat and Writing) promote it to the front of the toolbox, since it’s one of the first things worth reaching for in either.

The panel sits in a fixed strip above the key rows rather than covering them, so you can fix an issue by editing the text directly as easily as by tapping a fix chip. Open the panel and it checks the field’s current text immediately, with no wait. After that, edits re-check after a short pause (see Fully offline below).

Each issue is a card: a colored dot and category label, the flagged text struck through, an arrow to up to five suggested fixes, and Harper’s explanation underneath. An issue Harper has no fix for shows the explanation in the chips’ place instead. From there:

  • Tap a fix chip to apply that one fix. The field re-checks straight after.
  • Tap the card itself, meaning neither a chip nor the dismiss icon, to jump the field’s cursor to the issue. A multi-word span parks the cursor at its start. A single word with a real replacement gets selected, so you can type over it. Anything else parks the cursor right after the issue.
  • Tap the copy icon to copy Harper’s explanation.
  • Tap the sparkle beside the dismiss icon, shown while the AI tool is on, to ask the AI about the issue. The AI panel opens a chat with the issue, Harper’s explanation, its fixes and the sentence around it attached, so you can ask why, or whether the fix is right.
  • Tap the dismiss icon (×) to hide a card. It stays hidden only until the text changes and re-lints. Dismissing isn’t remembered past that.
  • Tap “Fix all”, shown whenever at least one issue has a suggestion, to apply every issue’s top fix in one go. Fixes go in back to front, so earlier issues don’t shift the ones after them. An issue whose span overlaps one already fixed is skipped rather than risk mangling the text.
  • Tap the dialect chip in the header to switch American/British/Canadian/Australian on the fly. The same text re-checks immediately under the new dialect.
  • Tap the funnel beside the dialect chip to choose which kinds of issue are worth showing at all. See below.
Screenshot pending
A card mid-fix: struck-through original text, fix chips, and Harper's explanation.

If nothing’s flagged, the panel says so. If the field is empty, it invites you to type or to open a field with text in it. If issues were found but your filter hides every one of them, the panel says that instead, so a quiet panel is never ambiguous.

Harper is thorough, and thoroughness is not always what you want. If you don’t care to be told about regionalisms, or you want spelling but not style suggestions, the funnel in the panel header turns individual kinds off.

Screenshot pending
The filter open in the panel header, with a category expanded onto the kinds inside it.

The filter is two levels deep:

  • Tap a category row (Correctness, Clarity, Engagement, Delivery) to switch every kind inside it at once.
  • Tap the chevron on a category to expand it onto its own kinds, then tap one to switch only that kind.
  • A category shows a tick when all of its kinds are on, a dash when only some are, and nothing when they’re all off. Turning one kind of an off category back on leaves the category dashed rather than quietly re-enabling the rest.
  • Every row carries the number of issues of that kind in the current text, filter or no filter, so you can see what you’re hiding before you hide it.
  • Show every kind at the bottom clears the filter in one tap. It only appears while something is hidden.

The funnel turns accent-colored while any filter is active, so a panel that’s hiding things says so from the header.

Filtering is not the same as dismissing. A dismissed card comes back on the next re-check; a filtered kind stays gone until you turn it back on, and the choice is remembered across sessions and carried in your backup.

A hidden kind is hidden from everything the panel does, not only the cards: it’s left out of the issue count in the header, and “Fix all” will not touch it. Turning off a kind of suggestion you disagree with means “Fix all” stops applying it, which is usually what you want.

The same filter lives in settings, where the four categories are folds you can open onto their kinds. Changing it in either place changes it in both.

Harper is a Rust engine compiled into libharper_jni.so and loaded directly into the keyboard’s process. There’s no server call anywhere in this path, checked or unchecked. All linting runs on one dedicated background thread, since Harper’s internal linter state isn’t safe to share across threads.

Re-checks are debounced. After you stop typing, the panel waits before re-linting rather than running on every keystroke (see the Re-check delay setting below). Opening the panel or switching dialects skips the wait and checks right away.

With a DeepL API Pro key set up under Tools / Translate and DeepL Write turned on there, the panel gets one more button beside the filter. Tap it and DeepL rewrites the text in the field: grammar, wording, and the style you picked. Unlike Harper, DeepL Write reads German, French, Spanish, Italian, Portuguese, Japanese, Korean and Chinese as well as English.

The answer takes the place of the issue list until you tap Replace or close it with its X. Replace swaps the text for DeepL’s version. On a field longer than 2,500 characters, only the first 2,500 go to DeepL and only those are replaced. If you edit the text before the answer arrives, the answer is dropped, because it no longer matches what is in the field.

Apart from LanguageTool, when you switch it on, this is the one part of Grammar check that goes online. The text in the field and your DeepL key go to DeepL, and only when you tap the button. Without a DeepL key, or with DeepL Write off, the button is not there and the panel works as described above. It’s never there in a password field either, so a password can’t be sent to DeepL. The same rewrite is available on selected text as the DeepL Write selection action.

WM Keyboard  Tools  Grammar check
The Grammar check settings screen: dialect, re-check delay, and the system-wide group.
The Grammar check settings screen: dialect, re-check delay, and the system-wide group.
The Grammar check settings screen: dialect, re-check delay, and the system-wide group.
SettingDefaultRange/options
CheckersHarper on, LanguageTool offHarper, LanguageTool servers; see Checkers
English dialectAmericanAmerican, British, Canadian, Australian
Re-check delay350 ms100–1500 ms
Show Correctness issuesOnPer kind: spelling, typo, grammar, agreement, capitalization, punctuation, word boundaries, confused words, misheard phrases, usage
Show Clarity issuesOnPer kind: readability, redundancy, repetition, word choice
Show Engagement issuesOnPer kind: enhancement, style, miscellaneous
Show Delivery issuesOnPer kind: formatting, regionalism, nonstandard

Each category is a fold. The heading’s switch turns the whole category on or off; open it to reach the individual kinds, and the closed fold names the kinds still showing. These are the same switches as the panel’s funnel.

A System-wide group sits below these two, with a Use Harper everywhere button and (on Android 12 and up) an Underline only, no fix popup toggle. Those set up WM Keyboard as Android’s system spell checker, which adds Harper’s grammar marks in other apps on top of word-list spelling. See System spell checker for what that covers and how it differs from this in-keyboard tool.

  • Harper is English only. It doesn’t check any other language, and the dialect setting is global: it applies no matter which typing language or layout is currently active. For other languages switch on LanguageTool, or use the optional DeepL Write button.
  • The system spell checker is always Harper. The Checkers list only decides who checks in the Grammar panel. What Android’s spell checker marks in other apps comes from Harper on the phone, whatever the list says.
  • Every build. Grammar check and the spell checker’s grammar marks are in every build. F-Droid compiles Harper from source. A build made without Harper’s native library shows the tool as not in this build, and its spell checker checks spelling only.
  • A missing engine fails safely. If libharper_jni.so didn’t load, the panel opens normally and says the grammar engine isn’t in this build, rather than crashing or hanging on “checking.”
  • A fix only touches the words it flagged. Applying a chip (or “Fix all”) replaces the flagged spans and nothing else, so the rest of what you wrote isn’t re-typed into the field. That’s what keeps formatting alive in apps that have any: bold, colour, a checklist item in Google Keep. The one exception is an editor that won’t tell the keyboard where its text starts. There’s no way to address a span in one of those, so the fix falls back to rewriting the whole field, which flattens any styling it was carrying.
  • Dismissing an issue is temporary. A dismissed card comes back if you edit the text again and it re-lints to the same issue. There’s no permanent “ignore this” list.
  • “Fix all” only uses each issue’s first suggestion. For an issue with several possible fixes, use its individual fix chips instead if you want a different one applied.
  • A filter hides issues, it doesn’t stop the checking. Harper still lints the whole text; the filter decides what’s worth showing you. Turning kinds off doesn’t make checking faster.
  • An unfamiliar kind is always shown. If a future Harper adds a kind of issue this version of the app doesn’t know, it appears in the panel (under Engagement’s color) rather than being silently swallowed by a filter that can’t name it.
  • A LanguageTool error takes the issues’ place. No connection, a refused key or a busy server shows as a line in the panel, unless the next checker on the list could answer. The next edit checks again.
  • Re-check delay only paces this panel. It has no effect on the system spell checker, which always answers synchronously when another app’s text field asks it to.

For contact-aware, dictionary-based red squiggles in every other app on your phone, not only WM Keyboard’s own writing surface, see System spell checker. For the everyday typo-fixing that runs on every language and every build, see Autocorrect.