feat(learn): spelling and grammar checked as people write (ADR 0024)

Where the instance's LanguageTool checks the language, the editors
underline what it finds after a pause, a paragraph at a time, a tap
offering its suggestions (GrammarHost, GrammarCheckController,
GrammarOverlay; nothing written into the document). A card's front is
checked in the studyset's front language, its back and answers in the
back's, everything else in the language recognised.

In a private studyset nothing is sent until the person agrees for it,
asked once in a banner above the editor and kept in their settings
(an agreement under privateAgreements, a refusal in grammarDeclined,
settings schema 14), taken back in the studyset's privacy dialog.
Settings > Writing turns it off on the device.

The field's Check asks LanguageTool first where it checks the
studyset's language, in a sheet offering to ask the assistant why;
elsewhere it asks the assistant as before (amends ADR 0021, D54).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 23:51:45 +01:00
commit d2eaef255e
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 2986 additions and 32 deletions

View file

@ -587,6 +587,10 @@
"settingsStudy": "Lernen",
"settingsStudyPomodoroTitle": "Pomodoro-Timer beim Lernen anzeigen",
"settingsStudyPomodoroSubtitle": "Zeige den Fokus-/Pausen-Countdown in der Lernsitzung, damit du die verbleibende Zeit siehst, ohne die Benachrichtigung zu öffnen.",
"settingsWriting": "Schreiben",
"settingsGrammarTitle": "Grammatik beim Schreiben prüfen",
"settingsGrammarSubtitle": "Unterstreiche mögliche Rechtschreib- und Grammatikfehler in Karteikarten und Notizen, mit Korrekturen per Fingertipp. Die Prüfung läuft, wenn du pausierst, in den Sprachen des Lernsets.",
"settingsGrammarUnavailable": "Diese Instanz hat noch keine Rechtschreib- und Grammatikprüfung eingerichtet, daher wird nichts unterstrichen. Bleibt es an, beginnt die Prüfung, sobald sie eine hat.",
"settingsVersion": "Version {version} (Build {build})",
"syncTitle": "Synchronisierung",
"syncStateSyncing": "Wird synchronisiert…",
@ -1880,12 +1884,27 @@
"assistantPrivateMark": "{name} darf dieses private Lernset bekommen",
"studysetPrivacyAssistants": "Assistenten, an die es gehen darf",
"studysetPrivacyTakeBack": "Zurücknehmen",
"studysetPrivacyGrammar": "Rechtschreibung und Grammatik",
"studysetPrivacyGrammarChecked": "Beim Schreiben von {instance} geprüft",
"studysetPrivacyGrammarNotChecked": "Beim Schreiben nicht geprüft",
"studyMissedExplain": "Nicht gewusst? {name} kann erklären, warum.",
"assistantExplain": "Erklären",
"assistantExplainQuestion": "Warum ist das die Antwort?",
"studyTypedWhy": "Warum?",
"assistantWhyTyped": "Ich habe „{typed}“ getippt. Warum stimmt das nicht?",
"assistantCheck": "Auf Fehler prüfen",
"grammarNoSuggestions": "Keine Vorschläge",
"grammarNothingFound": "Keine Rechtschreib- oder Grammatikfehler gefunden",
"grammarPrivateAsk": "Rechtschreibung und Grammatik in diesem privaten Lernset prüfen? Während geprüft wird, geht, was du hier schreibst, lesbar an {instance}, ein Absatz nach dem anderen; weder die Instanz noch ihr LanguageTool behält etwas davon. Gefragt wird einmal für dieses Lernset und seine Themen.",
"grammarPrivateNotHere": "Hier nicht",
"grammarPrivateCheckHere": "Hier prüfen",
"grammarCheckTitle": "Rechtschreibung und Grammatik",
"grammarCheckTooOften": "Gerade zu oft geprüft. Versuche es in einer Minute erneut.",
"grammarCheckTooLong": "Zu lang, um es auf einmal zu prüfen.",
"grammarCheckUnknownLanguage": "Diese Sprache prüft die Instanz nicht.",
"grammarCheckFailed": "Die Prüfung der Instanz ist nicht erreichbar. Versuche es später erneut.",
"grammarCheckAskWhy": "{name} fragen, warum",
"grammarCheckAskInstead": "{name} prüfen lassen",
"proposalsTitle": "Karteikarten von {name}",
"proposalsWriting": "{name} schreibt Karteikarten …",
"proposalsNone": "{name} hat keine Karteikarten vorgeschlagen.",

View file

@ -666,6 +666,12 @@
"settingsStudy": "Study",
"settingsStudyPomodoroTitle": "Show the Pomodoro timer while studying",
"settingsStudyPomodoroSubtitle": "Show the focus / break countdown inside the study session, so you can see the time left without opening the notification.",
"settingsWriting": "Writing",
"@settingsWriting": {"description": "Settings' card on checking what is written (ADR 0024)."},
"settingsGrammarTitle": "Check grammar while writing",
"settingsGrammarSubtitle": "Underline possible spelling and grammar mistakes in flashcards and notes, with one-tap fixes. Checks run when you pause, in the studyset's languages.",
"settingsGrammarUnavailable": "This instance has no spelling and grammar check set up yet, so nothing is underlined. Left on, checking starts once it has one.",
"@settingsGrammarUnavailable": {"description": "Below the switch where the instance runs no LanguageTool."},
"settingsVersion": "Version {version} (build {build})",
"@settingsVersion": {"placeholders": {"version": {"type": "String"}, "build": {"type": "String"}}},
"syncTitle": "Sync",
@ -2217,6 +2223,11 @@
"@studysetPrivacyAssistants": {"description": "The privacy dialog's list of the assistants agreed to (D17)."},
"studysetPrivacyTakeBack": "Take back",
"@studysetPrivacyTakeBack": {"description": "Takes an agreement back."},
"studysetPrivacyGrammar": "Spelling and grammar",
"@studysetPrivacyGrammar": {"description": "The privacy dialog's answer on checking what is written (ADR 0024)."},
"studysetPrivacyGrammarChecked": "Checked by {instance} as you write",
"@studysetPrivacyGrammarChecked": {"placeholders": {"instance": {"type": "String"}}},
"studysetPrivacyGrammarNotChecked": "Not checked as you write",
"studyMissedExplain": "Missed it? {name} can explain why.",
"@studyMissedExplain": {"description": "After a card was rated Bad (ADR 0021, D53); name is the assistant's.", "placeholders": {"name": {"type": "String"}}},
"assistantExplain": "Explain",
@ -2229,6 +2240,23 @@
"@assistantWhyTyped": {"description": "The question an assistant is asked about a reading typed wrong.", "placeholders": {"typed": {"type": "String"}}},
"assistantCheck": "Check for mistakes",
"@assistantCheck": {"description": "Has an assistant check what was written in the field (D54)."},
"grammarNoSuggestions": "No suggestions",
"@grammarNoSuggestions": {"description": "In place of suggestions for a mistake that has none."},
"grammarNothingFound": "No spelling or grammar mistakes found",
"grammarPrivateAsk": "Check spelling and grammar in this private studyset? While it is checked, what you write here goes to {instance} readable, a paragraph at a time; neither the instance nor its LanguageTool keeps any of it. You're asked once for this studyset and its topics.",
"@grammarPrivateAsk": {"placeholders": {"instance": {"type": "String"}}, "description": "The banner above an editor in a private studyset not asked yet (ADR 0024)."},
"grammarPrivateNotHere": "Not here",
"grammarPrivateCheckHere": "Check here",
"grammarCheckTitle": "Spelling and grammar",
"@grammarCheckTitle": {"description": "The field's Check sheet, what the instance's LanguageTool found."},
"grammarCheckTooOften": "Checked too often for now. Try again in a minute.",
"grammarCheckTooLong": "Too long to check at once.",
"grammarCheckUnknownLanguage": "The instance doesn't check this language.",
"grammarCheckFailed": "The instance's check couldn't be reached. Try again later.",
"grammarCheckAskWhy": "Ask {name} why",
"@grammarCheckAskWhy": {"placeholders": {"name": {"type": "String"}}},
"grammarCheckAskInstead": "Ask {name} to check",
"@grammarCheckAskInstead": {"placeholders": {"name": {"type": "String"}}},
"proposalsTitle": "Flashcards by {name}",
"@proposalsTitle": {"description": "The sheet of cards an assistant proposes (ADR 0021, D55).", "placeholders": {"name": {"type": "String"}}},
"proposalsWriting": "{name} is writing flashcards…",

View file

@ -3508,6 +3508,30 @@ abstract class AppLocalizations {
/// **'Show the focus / break countdown inside the study session, so you can see the time left without opening the notification.'**
String get settingsStudyPomodoroSubtitle;
/// Settings' card on checking what is written (ADR 0024).
///
/// In en, this message translates to:
/// **'Writing'**
String get settingsWriting;
/// No description provided for @settingsGrammarTitle.
///
/// In en, this message translates to:
/// **'Check grammar while writing'**
String get settingsGrammarTitle;
/// No description provided for @settingsGrammarSubtitle.
///
/// In en, this message translates to:
/// **'Underline possible spelling and grammar mistakes in flashcards and notes, with one-tap fixes. Checks run when you pause, in the studyset\'s languages.'**
String get settingsGrammarSubtitle;
/// Below the switch where the instance runs no LanguageTool.
///
/// In en, this message translates to:
/// **'This instance has no spelling and grammar check set up yet, so nothing is underlined. Left on, checking starts once it has one.'**
String get settingsGrammarUnavailable;
/// No description provided for @settingsVersion.
///
/// In en, this message translates to:
@ -11102,6 +11126,24 @@ abstract class AppLocalizations {
/// **'Take back'**
String get studysetPrivacyTakeBack;
/// The privacy dialog's answer on checking what is written (ADR 0024).
///
/// In en, this message translates to:
/// **'Spelling and grammar'**
String get studysetPrivacyGrammar;
/// No description provided for @studysetPrivacyGrammarChecked.
///
/// In en, this message translates to:
/// **'Checked by {instance} as you write'**
String studysetPrivacyGrammarChecked(String instance);
/// No description provided for @studysetPrivacyGrammarNotChecked.
///
/// In en, this message translates to:
/// **'Not checked as you write'**
String get studysetPrivacyGrammarNotChecked;
/// After a card was rated Bad (ADR 0021, D53); name is the assistant's.
///
/// In en, this message translates to:
@ -11138,6 +11180,78 @@ abstract class AppLocalizations {
/// **'Check for mistakes'**
String get assistantCheck;
/// In place of suggestions for a mistake that has none.
///
/// In en, this message translates to:
/// **'No suggestions'**
String get grammarNoSuggestions;
/// No description provided for @grammarNothingFound.
///
/// In en, this message translates to:
/// **'No spelling or grammar mistakes found'**
String get grammarNothingFound;
/// The banner above an editor in a private studyset not asked yet (ADR 0024).
///
/// In en, this message translates to:
/// **'Check spelling and grammar in this private studyset? While it is checked, what you write here goes to {instance} readable, a paragraph at a time; neither the instance nor its LanguageTool keeps any of it. You\'re asked once for this studyset and its topics.'**
String grammarPrivateAsk(String instance);
/// No description provided for @grammarPrivateNotHere.
///
/// In en, this message translates to:
/// **'Not here'**
String get grammarPrivateNotHere;
/// No description provided for @grammarPrivateCheckHere.
///
/// In en, this message translates to:
/// **'Check here'**
String get grammarPrivateCheckHere;
/// The field's Check sheet, what the instance's LanguageTool found.
///
/// In en, this message translates to:
/// **'Spelling and grammar'**
String get grammarCheckTitle;
/// No description provided for @grammarCheckTooOften.
///
/// In en, this message translates to:
/// **'Checked too often for now. Try again in a minute.'**
String get grammarCheckTooOften;
/// No description provided for @grammarCheckTooLong.
///
/// In en, this message translates to:
/// **'Too long to check at once.'**
String get grammarCheckTooLong;
/// No description provided for @grammarCheckUnknownLanguage.
///
/// In en, this message translates to:
/// **'The instance doesn\'t check this language.'**
String get grammarCheckUnknownLanguage;
/// No description provided for @grammarCheckFailed.
///
/// In en, this message translates to:
/// **'The instance\'s check couldn\'t be reached. Try again later.'**
String get grammarCheckFailed;
/// No description provided for @grammarCheckAskWhy.
///
/// In en, this message translates to:
/// **'Ask {name} why'**
String grammarCheckAskWhy(String name);
/// No description provided for @grammarCheckAskInstead.
///
/// In en, this message translates to:
/// **'Ask {name} to check'**
String grammarCheckAskInstead(String name);
/// The sheet of cards an assistant proposes (ADR 0021, D55).
///
/// In en, this message translates to:

View file

@ -2118,6 +2118,20 @@ class AppLocalizationsDe extends AppLocalizations {
String get settingsStudyPomodoroSubtitle =>
'Zeige den Fokus-/Pausen-Countdown in der Lernsitzung, damit du die verbleibende Zeit siehst, ohne die Benachrichtigung zu öffnen.';
@override
String get settingsWriting => 'Schreiben';
@override
String get settingsGrammarTitle => 'Grammatik beim Schreiben prüfen';
@override
String get settingsGrammarSubtitle =>
'Unterstreiche mögliche Rechtschreib- und Grammatikfehler in Karteikarten und Notizen, mit Korrekturen per Fingertipp. Die Prüfung läuft, wenn du pausierst, in den Sprachen des Lernsets.';
@override
String get settingsGrammarUnavailable =>
'Diese Instanz hat noch keine Rechtschreib- und Grammatikprüfung eingerichtet, daher wird nichts unterstrichen. Bleibt es an, beginnt die Prüfung, sobald sie eine hat.';
@override
String settingsVersion(String version, String build) {
return 'Version $version (Build $build)';
@ -7005,6 +7019,17 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get studysetPrivacyTakeBack => 'Zurücknehmen';
@override
String get studysetPrivacyGrammar => 'Rechtschreibung und Grammatik';
@override
String studysetPrivacyGrammarChecked(String instance) {
return 'Beim Schreiben von $instance geprüft';
}
@override
String get studysetPrivacyGrammarNotChecked => 'Beim Schreiben nicht geprüft';
@override
String studyMissedExplain(String name) {
return 'Nicht gewusst? $name kann erklären, warum.';
@ -7027,6 +7052,52 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get assistantCheck => 'Auf Fehler prüfen';
@override
String get grammarNoSuggestions => 'Keine Vorschläge';
@override
String get grammarNothingFound =>
'Keine Rechtschreib- oder Grammatikfehler gefunden';
@override
String grammarPrivateAsk(String instance) {
return 'Rechtschreibung und Grammatik in diesem privaten Lernset prüfen? Während geprüft wird, geht, was du hier schreibst, lesbar an $instance, ein Absatz nach dem anderen; weder die Instanz noch ihr LanguageTool behält etwas davon. Gefragt wird einmal für dieses Lernset und seine Themen.';
}
@override
String get grammarPrivateNotHere => 'Hier nicht';
@override
String get grammarPrivateCheckHere => 'Hier prüfen';
@override
String get grammarCheckTitle => 'Rechtschreibung und Grammatik';
@override
String get grammarCheckTooOften =>
'Gerade zu oft geprüft. Versuche es in einer Minute erneut.';
@override
String get grammarCheckTooLong => 'Zu lang, um es auf einmal zu prüfen.';
@override
String get grammarCheckUnknownLanguage =>
'Diese Sprache prüft die Instanz nicht.';
@override
String get grammarCheckFailed =>
'Die Prüfung der Instanz ist nicht erreichbar. Versuche es später erneut.';
@override
String grammarCheckAskWhy(String name) {
return '$name fragen, warum';
}
@override
String grammarCheckAskInstead(String name) {
return '$name prüfen lassen';
}
@override
String proposalsTitle(String name) {
return 'Karteikarten von $name';

View file

@ -2098,6 +2098,20 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsStudyPomodoroSubtitle =>
'Show the focus / break countdown inside the study session, so you can see the time left without opening the notification.';
@override
String get settingsWriting => 'Writing';
@override
String get settingsGrammarTitle => 'Check grammar while writing';
@override
String get settingsGrammarSubtitle =>
'Underline possible spelling and grammar mistakes in flashcards and notes, with one-tap fixes. Checks run when you pause, in the studyset\'s languages.';
@override
String get settingsGrammarUnavailable =>
'This instance has no spelling and grammar check set up yet, so nothing is underlined. Left on, checking starts once it has one.';
@override
String settingsVersion(String version, String build) {
return 'Version $version (build $build)';
@ -6939,6 +6953,17 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get studysetPrivacyTakeBack => 'Take back';
@override
String get studysetPrivacyGrammar => 'Spelling and grammar';
@override
String studysetPrivacyGrammarChecked(String instance) {
return 'Checked by $instance as you write';
}
@override
String get studysetPrivacyGrammarNotChecked => 'Not checked as you write';
@override
String studyMissedExplain(String name) {
return 'Missed it? $name can explain why.';
@ -6961,6 +6986,51 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get assistantCheck => 'Check for mistakes';
@override
String get grammarNoSuggestions => 'No suggestions';
@override
String get grammarNothingFound => 'No spelling or grammar mistakes found';
@override
String grammarPrivateAsk(String instance) {
return 'Check spelling and grammar in this private studyset? While it is checked, what you write here goes to $instance readable, a paragraph at a time; neither the instance nor its LanguageTool keeps any of it. You\'re asked once for this studyset and its topics.';
}
@override
String get grammarPrivateNotHere => 'Not here';
@override
String get grammarPrivateCheckHere => 'Check here';
@override
String get grammarCheckTitle => 'Spelling and grammar';
@override
String get grammarCheckTooOften =>
'Checked too often for now. Try again in a minute.';
@override
String get grammarCheckTooLong => 'Too long to check at once.';
@override
String get grammarCheckUnknownLanguage =>
'The instance doesn\'t check this language.';
@override
String get grammarCheckFailed =>
'The instance\'s check couldn\'t be reached. Try again later.';
@override
String grammarCheckAskWhy(String name) {
return 'Ask $name why';
}
@override
String grammarCheckAskInstead(String name) {
return 'Ask $name to check';
}
@override
String proposalsTitle(String name) {
return 'Flashcards by $name';