AutoCorrect: the English replacement list applies only to English text (#835) (#846)

* #835: AutoCorrect's English replacement list applies only to English text

Built-in word entries (teh, i, dont…) are replaced only when the word's
language (run w:lang, style, document default) is English or unset;
symbols, fractions and user entries apply in every language. Polish,
Croatian, Bosnian and Serbian "i" ("and") is no longer turned into "I".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #835 review: untagged text follows the editing language; English as proofing decides it

- Text with no language at all (a .docx without w:lang) is in the editing language, so with a
  Polish interface `Ala i kot ` keeps its `i` there too.
- `wordcraft_proof::ProofLang::from_tag` decides what is English (it also takes `eng`), so
  AutoCorrect and proofing agree; the local `is_english` is gone.
- Tests through `set_editing_language` (the document-default path) and with no language at all.
- docs/typing-parity.md: the Croatian/Bosnian/Serbian case needs the text tagged, as those
  interfaces set no editing language yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Dmitrii Khlapov 2026-10-12 15:56:29 +02:00 committed by GitHub
parent 7d37b14679
commit c9079196db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 82 additions and 5 deletions

View File

@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~15 points (60% → 75% ready for real work) and ~150–230 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** trivial (Turkish interface catalog completed, #774; previously trivial: Turkish interface catalog completed, #774; previously trivial: Hungarian interface landed; previously trivial: independent complex-script formatting preserved across DOCX open/save; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface and Polish proofing; previously trivial: Turkish interface landed; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** trivial (Turkish interface catalog completed, #774; previously trivial: Turkish interface catalog completed, #774; previously trivial: Hungarian interface landed; previously trivial: Quick Access Toolbar KeyTips and a saved enable/disable option, #620; previously trivial: new documents follow the system region, #838; previously minor: Polish interface and Polish proofing; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
WordCraft aims for complete parity with Microsoft Word, then goes further on speed, openness and
agent control. This page is the summary; the assessment is
@ -132,6 +132,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
## Progress log
| Date | What landed |
| 2026-10-12 | AutoCorrect keys its built-in English replacement list by language (#835): Polish, Croatian, Bosnian and Serbian `i` ("and") is no longer turned into `I`; English and untagged text unchanged |
|---|---|
| 2026-10-12 | Ribbon groups fit above their labels: View › Show splits its four check boxes into two columns (Hide White Space, #645, had pushed the fourth onto the "Show" label), Developer › Controls loses the gaps that ran into its label, and Layout › Paragraph (#654) no longer trips a debug assertion and sits 2.5 pt higher; a test draws every tab in every language and fails on any group taller than the ribbon |
| 2026-10-12 | Window caption: a non-Latin file name is drawn with a document fallback face (or the CJK UI font), instead of `?` |
@ -250,6 +251,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
| 2026-10-12 | trivial | Progress log: new documents follow the system region (#838) |
| 2026-10-11 | trivial | DOCX complex-script formatting inheritance fix and round-trip coverage |
| 2026-10-11 | minor | Czech (`cs`) interface catalog added |
| 2026-10-12 | trivial | Progress log: AutoCorrect replacement list follows the text's language (#835) |
| 2026-10-11 | trivial | Progress log: German interface (#574) |
| 2026-10-12 | trivial | Progress log: language-aware sorting and table numbers (#836, #837) |
| 2026-10-12 | trivial | Progress log: Word 365 cover pages (#743) |

View File

@ -60,6 +60,11 @@ pub const AUTOCORRECT: &[(&str, &str)] = &[
("3/4", "¾"),
];
/// A built-in entry that is an English word (letters and apostrophes only), not a symbol.
fn is_english_entry(from: &str) -> bool {
from.chars().any(char::is_alphabetic) && from.chars().all(|c| c.is_alphabetic() || c == '\'')
}
/// Compare and Combine take the same settings.
const COMPARE_PARAMS: &str = r#"{"path"?: string (revised document), "text"?: string (revised text), "original"?: string (original document; default: this one), "author"?: string (label changes with), "caseChanges"?: bool, "whiteSpace"?: bool, "formatting"?: bool (default true: differences count), "level"?: "word|character", "showIn"?: "original|revised|new" (revised/new: an untitled document), "moves"|"comments"|"tables"|"headersFooters"|"footnotes"|"textBoxes"|"fields"?: bool (accepted; not honoured yet except tables, always compared)}"#;
@ -162,14 +167,23 @@ impl AutoCorrectPrefs {
self
}
/// The replacement for `word`, if any: the user's entry, else a built-in one not deleted.
/// Built-in lowercase entries also match a capitalised word ("Teh" → "The").
/// The replacement for `word` in English text, if any: the user's entry, else a built-in
/// one not deleted. Built-in lowercase entries also match a capitalised word ("Teh" → "The").
pub fn replacement(&self, word: &str) -> Option<String> {
self.replacement_in(word, true)
}
/// The replacement for `word` in text that is (`english`) or isn't English: the built-in
/// English words ("teh", "i", "dont") apply only to English text, as Word keeps a list per
/// language (#835: Polish "i" means "and"); symbols, fractions and the user's entries apply
/// whatever the language.
pub fn replacement_in(&self, word: &str, english: bool) -> Option<String> {
if let Some((_, b)) = self.entries.iter().find(|(a, _)| a == word) {
return Some(b.clone());
}
AUTOCORRECT
.iter()
.filter(|(a, _)| english || !is_english_entry(a))
.filter(|(a, _)| !self.removed.iter().any(|r| r == a) && !self.entries.iter().any(|(e, _)| e == a))
.filter(|(a, _)| self.fractions || !FRACTIONS.contains(a))
.find(|(a, _)| {
@ -779,7 +793,15 @@ pub fn autocorrect_word(s: &mut Session, on_enter: bool) -> Result<(), CmdError>
word = format!("{}{fixed}{}", word.get(..at).unwrap_or(""), word.get(at + letters.len()..).unwrap_or(""));
}
// A whole-word replacement, else "wait..." → "wait…".
let found = if ac.replace_text { ac.replacement(&word) } else { None };
// The word's language (run, paragraph style, document default) picks the list (#835).
// Text with no language at all is in the editing language (a Polish interface writes Polish),
// else English. Proofing decides what is English the same way.
let english = s.doc.para_at(&f).is_none_or(|p| {
let rc = s.doc.styles.resolve_char(p.props.style.as_deref(), p.props_of_char(start));
let lang = rc.lang.as_deref().or(s.editing_lang.as_deref());
wordcraft_proof::ProofLang::from_tag(lang) == wordcraft_proof::ProofLang::En
});
let found = if ac.replace_text { ac.replacement_in(&word, english) } else { None };
let (from, rep) = match found {
Some(r) => (start, Some(r)),
None if ac.replace_text && word.len() > 3 && word.ends_with("...") => (start + word.len() - 3, Some("…".to_string())),

View File

@ -188,6 +188,48 @@ fn autocorrect_as_you_type() {
assert_eq!(s.doc.plain_text(StoryRef::Body), "½ cup. iPhone. See readme.txt ");
}
/// #835: the English replacement list applies only to English text. Polish "i" ("and") stays
/// lowercase; symbols still change; English and untagged text still get "I".
#[test]
fn autocorrect_replacement_list_follows_the_texts_language() {
// Polish as the document's default language.
let mut s = s();
run(&mut s, "review.language", json!({"lang": "pl-PL", "default": true}));
typ(&mut s, "Ala i kot. kawa i herbata teh (c) ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "Ala i kot. Kawa i herbata teh © ");
// Polish on the run only: the word's own language counts.
let mut s = self::s();
typ(&mut s, "Ala");
run(&mut s, "select.all", json!({}));
run(&mut s, "review.language", json!({"lang": "pl-PL"}));
run(&mut s, "caret.docEnd", json!({}));
typ(&mut s, " i kot ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "Ala i kot ");
// English (any region) and untagged text (the default proofing language is English).
let mut s = self::s();
run(&mut s, "review.language", json!({"lang": "en-GB", "default": true}));
typ(&mut s, "so i think teh end ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "So I think the end ");
let s = typed("so i think teh end ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "So I think the end ");
// A Polish interface: a new document is Polish by its default language, and text with no
// language at all (a .docx without `w:lang`) follows the editing language too.
let mut s = self::s();
s.set_editing_language(Some("pl-PL"));
typ(&mut s, "Ala i kot ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "Ala i kot ", "document default from the editing language");
let mut s = self::s();
s.set_editing_language(Some("pl-PL"));
s.doc.styles.default_chr.lang = None;
typ(&mut s, "Ala i kot ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "Ala i kot ", "untagged text in the editing language");
// `eng` is English too, as for proofing.
let mut s = self::s();
run(&mut s, "review.language", json!({"lang": "eng", "default": true}));
typ(&mut s, "so i think ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "So I think ");
}
#[test]
fn autocorrect_options_change_what_typing_does() {
let mut s = s();

View File

@ -1,6 +1,6 @@
# Typing parity with Word
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously minor: right-to-left letters typed into an empty paragraph make it right-to-left, #713; previously trivial: grammar proofing: ellipses are not sentence boundaries; previously minor: AutoCorrect's English replacement list applies only to English text, #835; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343; previously trivial: outline keys in Outline view, #493) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
What Word does when you type, observed black-box (same keystrokes typed into Word and
WordCraft, results read back paragraph by paragraph) and pinned by tests in
@ -99,6 +99,15 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin
numbered lists, border lines; user entries are added, replaced or deleted (built-in ones too),
and "don't capitalize after" exceptions extend the abbreviation list. A replacement of several
words takes a sentence capital on its first (`autocorrect_options_change_what_typing_does`).
- The built-in replacement list is English: its words (`teh`, `i`, `dont`…) are replaced only
in English text (the run's `w:lang`, then its style, then the document default; text with no
language at all is in the editing language, so with a Polish interface a `.docx` without
`w:lang` is Polish). English is decided as proofing decides it (`ProofLang`: `en`, `eng`).
Polish `Ala i kot` keeps its `i` ("and"); symbols (`(c)`, `->`, `1/2`) and the user's own
entries apply in every language (#835; `autocorrect_replacement_list_follows_the_texts_language`).
Croatian, Bosnian and Serbian `i` is kept only when the text is tagged with that language: those
interfaces don't set an editing language yet. Per-language lists for other languages aren't
there yet (#735).
¹ From Word's documented AutoFormat and AutoCorrect options; not yet keystroke-compared with
Word.
@ -160,7 +169,9 @@ Word.
|---|---|---|
| 2026-10-12 | minor | AutoCorrect: Correct TWo INitial CApitals, accidental cAPS LOCK, capitalize first letter of table cells (#544) |
| 2026-10-11 | minor | Text direction: right-to-left letters typed into an empty paragraph make it right-to-left; existing paragraphs keep theirs (#713) |
| 2026-10-12 | trivial | AutoCorrect: untagged text follows the editing language; English decided as proofing does (#835 review) |
| 2026-10-11 | trivial | Grammar proofing (not AutoCorrect) no longer suggests capitalising a sentence after `...` or `…` (`proof::tests::ellipses_do_not_start_sentences`) |
| 2026-10-12 | minor | AutoCorrect: the English replacement list applies only to English (or untagged) text; Polish `i` stays (#835) |
| 2026-10-11 | minor | AutoFormat tables from `+---+`, arrow bullets, smileys inside words, undo after a border line (#546) |
| 2026-10-11 | trivial | Outline view keys: expand/collapse, Show Level, moving and promoting with collapsed content (#493) |
| 2026-10-11 | minor | Editing options: typing replaces selection, overtype and the Insert key (#485) |