AutoCorrect: TWo INitial CApitals, cAPS LOCK and table-cell capitals (#544) (#545)

- Correct TWo INitial CApitals: a finished word of 3+ letters with two
  leading capitals and the rest lowercase loses its second capital
  ("WOrd" -> "Word"), before the replacement list ("TEh" -> "The").
  Built-in exceptions (IDs, CDs, PCs, TVs, ...) plus user exceptions.
- Correct accidental cAPS LOCK: "wORD" -> "Word" (not iOS, mRNA, ...).
- Capitalize first letter of table cells, under its own option.
- Session::autocorrect_caps (AutoCorrectCaps, all on by default);
  tools.autocorrect takes twoInitialCaps / capsLock / capCells /
  addException / removeException.
- Each fix is one undo step right after it (the existing AutoFormat
  snapshot); case changes replace letter by letter, keeping formatting.
- Tests in tests_typing.rs; docs/typing-parity.md and ROADMAP updated.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Valentin Klein 2026-10-12 13:35:11 +02:00 committed by GitHub
parent 417896301b
commit 2f9bf4e800
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 207 additions and 6 deletions

View File

@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
| 2026-10-11 | Hide White Space (#645): `view.hideWhiteSpace` (View › Show checkbox, or double-click the gap between pages) collapses margins, headers and footers between pages to a thin line in Print Layout; screen only, page count unchanged |
| 2026-10-11 | Fonts: Palatino Linotype and Book Antiqua fall back to Palatino (macOS) or TeX Gyre Pagella / P052 / URW Palladio (Linux) before generic serif (#670) |
|---|---|
| 2026-10-12 | AutoCorrect capitals (#544): Correct TWo INitial CApitals (with exceptions), accidental cAPS LOCK, first letter of table cells; each undoable and switchable |
| 2026-10-11 | Turkish (`tr`) interface completed (#774): 2,325 entries, every key of the Russian catalog (equation gallery, built-in style names, watermarks, paper sizes, preview tiles); the interface audit checks Turkish too |
| 2026-10-12 | Navigation headings truncate within the resizable pane, with an ellipsis for long titles and full text on hover (#759) |
| 2026-10-11 | Reading Mode: existing View ribbon button now opens a read-only, viewport-column layout with animated horizontal paging, transient display controls, heading navigation (Navigation pane) and resize anchoring; document layout and export unchanged |

View File

@ -71,12 +71,16 @@ pub struct AutoCorrectPrefs {
pub enabled: bool,
/// Replace text as you type (the replacement list).
pub replace_text: bool,
/// Correct TWo INitial CApitals: "WOrd" → "Word" (not the exceptions, e.g. "IDs").
pub two_initial_caps: bool,
/// Capitalize the first letter of sentences.
pub cap_sentences: bool,
/// Capitalize the first letter of table cells.
pub cap_cells: bool,
/// Capitalize names of days.
pub cap_days: bool,
/// Correct accidental usage of cAPS LOCK: "wORD" → "Word".
pub caps_lock: bool,
/// AutoFormat As You Type: "straight quotes" with “smart quotes”.
pub smart_quotes: bool,
/// Fractions (1/2) with fraction characters (½).
@ -101,6 +105,8 @@ pub struct AutoCorrectPrefs {
pub exceptions: Vec<String>,
/// Built-in abbreviations the user deleted from the exceptions.
pub exceptions_removed: Vec<String>,
/// The user's words that keep two initial capitals (besides the built-in ones).
pub initial_caps_exceptions: Vec<String>,
}
impl Default for AutoCorrectPrefs {
@ -108,9 +114,11 @@ impl Default for AutoCorrectPrefs {
AutoCorrectPrefs {
enabled: true,
replace_text: true,
two_initial_caps: true,
cap_sentences: true,
cap_cells: true,
cap_days: true,
caps_lock: true,
smart_quotes: true,
fractions: true,
ordinals: true,
@ -123,6 +131,7 @@ impl Default for AutoCorrectPrefs {
removed: Vec::new(),
exceptions: Vec::new(),
exceptions_removed: Vec::new(),
initial_caps_exceptions: Vec::new(),
}
}
}
@ -145,7 +154,7 @@ impl AutoCorrectPrefs {
let mut seen = std::collections::HashSet::new();
self.entries.retain(|(a, b)| ok(a, MAX_FROM) && b.chars().count() <= MAX_TO && !b.contains(['\n', '\r']) && seen.insert(a.clone()));
self.entries.truncate(MAX_ENTRIES);
for list in [&mut self.removed, &mut self.exceptions, &mut self.exceptions_removed] {
for list in [&mut self.removed, &mut self.exceptions, &mut self.exceptions_removed, &mut self.initial_caps_exceptions] {
let mut seen = std::collections::HashSet::new();
list.retain(|a| ok(a, MAX_FROM) && seen.insert(a.clone()));
list.truncate(MAX_ENTRIES);
@ -216,9 +225,11 @@ fn autocorrect_options(s: &mut Session, v: &Value) -> CmdResult {
for (k, f) in [
("enabled", &mut ac.enabled as &mut bool),
("replaceText", &mut ac.replace_text),
("twoInitialCaps", &mut ac.two_initial_caps),
("capSentences", &mut ac.cap_sentences),
("capCells", &mut ac.cap_cells),
("capDays", &mut ac.cap_days),
("capsLock", &mut ac.caps_lock),
("smartQuotes", &mut ac.smart_quotes),
("fractions", &mut ac.fractions),
("ordinals", &mut ac.ordinals),
@ -273,13 +284,31 @@ fn autocorrect_options(s: &mut Session, v: &Value) -> CmdResult {
ac.exceptions_removed.push(e);
}
}
if let Some(w) = p::str(v, "addInitialCapsException") {
let w = w.trim();
if bad(w, MAX_FROM) || w.contains(char::is_whitespace) {
return Err(CmdError::Params("an INitial CAps exception is one word, like \"IDs\"".into()));
}
if !INITIAL_CAPS_EXCEPTIONS.contains(&w)
&& !ac.initial_caps_exceptions.iter().any(|e| e == w)
&& ac.initial_caps_exceptions.len() < MAX_ENTRIES
{
ac.initial_caps_exceptions.push(w.to_string());
}
}
if let Some(w) = p::str(v, "deleteInitialCapsException") {
let w = w.trim();
ac.initial_caps_exceptions.retain(|e| e != w);
}
let ac = &s.prefs.autocorrect;
Ok(json!({
"enabled": ac.enabled,
"replaceText": ac.replace_text,
"twoInitialCaps": ac.two_initial_caps,
"capSentences": ac.cap_sentences,
"capCells": ac.cap_cells,
"capDays": ac.cap_days,
"capsLock": ac.caps_lock,
"smartQuotes": ac.smart_quotes,
"fractions": ac.fractions,
"ordinals": ac.ordinals,
@ -291,6 +320,7 @@ fn autocorrect_options(s: &mut Session, v: &Value) -> CmdResult {
"entries": ac.list().into_iter().map(|(a, b, builtin)| json!({"from": a, "to": b, "builtIn": builtin})).collect::<Vec<_>>(),
"user": ac.entries,
"exceptions": ac.exception_list(),
"initialCapsExceptions": INITIAL_CAPS_EXCEPTIONS.iter().map(|e| e.to_string()).chain(ac.initial_caps_exceptions.iter().cloned()).collect::<Vec<_>>(),
}))
}
@ -321,7 +351,7 @@ pub fn specs() -> Vec<CommandSpec> {
.pure(),
CommandSpec::new("tools.autocorrect", "AutoCorrect Options", "File › Options › Proofing", autocorrect_options)
.params(
r#"{"enabled"?: bool (all of AutoCorrect), "replaceText"|"capSentences"|"capCells"|"capDays"|"smartQuotes"|"fractions"|"ordinals"|"dashes"|"links"|"bullets"|"numbering"|"borderLines"?: bool, "add"?: {"from": string, "to": string} (adds or replaces an entry), "delete"?: string (an entry's "from", built in or added), "addException"?: string, "deleteException"?: string} → options, entries and exceptions"#,
r#"{"enabled"?: bool (all of AutoCorrect), "replaceText"|"twoInitialCaps"|"capSentences"|"capCells"|"capDays"|"capsLock"|"smartQuotes"|"fractions"|"ordinals"|"dashes"|"links"|"bullets"|"numbering"|"borderLines"?: bool, "add"?: {"from": string, "to": string} (adds or replaces an entry), "delete"?: string (an entry's "from", built in or added), "addException"?: string, "deleteException"?: string, "addInitialCapsException"?: string (keeps TWo INitial CApitals, like "IDs"), "deleteInitialCapsException"?: string} → options, entries and exceptions"#,
)
.pure(),
CommandSpec::new("tools.customizeKeyboard", "Customize Keyboard", "File › Options", customize_keyboard)
@ -714,7 +744,7 @@ pub fn autocorrect_word(s: &mut Session, on_enter: bool) -> Result<(), CmdError>
return Ok(());
}
let ac = s.prefs.autocorrect.clone();
let f = s.sel.focus.clone();
let mut f = s.sel.focus.clone();
let Some(text) = s.doc.para_at(&f).map(|p| p.text.clone()) else { return Ok(()) };
let Some(before) = text.get(..f.off) else { return Ok(()) };
let Some(last) = before.chars().next_back() else { return Ok(()) };
@ -724,10 +754,29 @@ pub fn autocorrect_word(s: &mut Session, on_enter: bool) -> Result<(), CmdError>
let trigger = if on_enter { 0 } else { last.len_utf8() };
let Some(body) = before.get(..before.len() - trigger) else { return Ok(()) };
let start = body.rfind(char::is_whitespace).map(|i| i + 1).unwrap_or(0);
let Some(word) = body.get(start..).map(str::to_string) else { return Ok(()) };
let Some(mut word) = body.get(start..).map(str::to_string) else { return Ok(()) };
if word.is_empty() {
return Ok(());
}
// TWo INitial CApitals and cAPS LOCK come first, so "TEh" is still a replacement's typo.
if let Some((at, reps)) = case_fix(&ac, &word) {
let para = s.doc.para_mut(f.story, &f.path)?;
let letters = word.get(at..).unwrap_or("").chars().take(reps.len()).collect::<String>();
let mut end = start + at + letters.len();
for (c, rep) in letters.chars().rev().zip(reps.iter().rev()) {
let from = end.saturating_sub(c.len_utf8());
if rep.chars().ne(std::iter::once(c)) {
let props = para.props_of_char(from).clone();
para.delete(from, end)?;
para.insert_text(from, rep, &props)?;
}
end = from;
}
let fixed: String = reps.concat();
f.off = (f.off + fixed.len()).saturating_sub(letters.len());
s.sel = Selection::caret(f.clone());
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 };
let (from, rep) = match found {
@ -804,6 +853,49 @@ pub fn autocorrect_word(s: &mut Session, on_enter: bool) -> Result<(), CmdError>
Ok(())
}
/// Built-in words that keep their two initial capitals (plurals of two-letter abbreviations).
pub const INITIAL_CAPS_EXCEPTIONS: &[&str] = &["CDs", "IDs", "PCs", "TVs", "DJs", "GBs", "KBs", "MBs", "TBs", "VMs"];
/// Words with a lowercase first letter and the rest in capitals that aren't cAPS LOCK slips.
const CAPS_LOCK_EXCEPTIONS: &[&str] = &["iOS", "mRNA", "tRNA", "rRNA", "cDNA", "eSIM", "eSATA", "iSCSI", "vCPU"];
/// The capitalisation fix for a finished word: TWo INitial CApitals ("WOrd" → "Word") or cAPS
/// LOCK ("wORD" → "Word"). Returns the byte offset of the word's letters and each letter's
/// replacement. Only words of three or more letters, optionally in quotes or brackets.
fn case_fix(caps: &AutoCorrectPrefs, word: &str) -> Option<(usize, Vec<String>)> {
if !caps.two_initial_caps && !caps.caps_lock {
return None;
}
let at = word.find(char::is_alphabetic)?;
let rest = word.get(at..)?;
let letters = rest.get(..rest.find(|c: char| !c.is_alphabetic()).unwrap_or(rest.len()))?;
let tail = rest.get(letters.len()..)?;
if !word.get(..at)?.chars().all(|c| "\"'“‘([".contains(c)) || !tail.chars().all(|c| "\"'”’)]".contains(c)) {
return None;
}
let cs: Vec<char> = letters.chars().collect();
let (&a, &b, more) = match cs.as_slice() {
[a, b, more @ ..] if !more.is_empty() => (a, b, more),
_ => return None,
};
let one = |c: char| c.to_string();
let lower = |c: char| c.to_lowercase().collect::<String>();
if caps.two_initial_caps
&& a.is_uppercase()
&& b.is_uppercase()
&& more.iter().all(|c| c.is_lowercase())
&& !INITIAL_CAPS_EXCEPTIONS.contains(&letters)
&& !caps.initial_caps_exceptions.iter().any(|e| e == letters)
{
return Some((at, std::iter::once(one(a)).chain(std::iter::once(lower(b))).chain(more.iter().map(|&c| one(c))).collect()));
}
if caps.caps_lock && a.is_lowercase() && b.is_uppercase() && more.iter().all(|c| c.is_uppercase()) && !CAPS_LOCK_EXCEPTIONS.contains(&letters) {
let up = a.to_uppercase().collect::<String>();
return Some((at, std::iter::once(up).chain(std::iter::once(lower(b))).chain(more.iter().map(|&c| lower(c))).collect()));
}
None
}
/// Byte length of an ordinal suffix to superscript ("1st" → 2), if `word` is a correct ordinal.
fn ordinal_suffix(word: &str) -> Option<usize> {
let digits = word.find(|c: char| !c.is_ascii_digit())?;

View File

@ -634,6 +634,61 @@ fn shift_f5_goes_back_through_the_last_edits() {
assert!(Session::new(wordcraft_doc::Document::new()).run("edit.goBack", &json!({})).is_err(), "nothing edited yet");
}
#[test]
fn two_initial_capitals_are_corrected() {
let s = typed("the WOrd and THe end, not IDs or CDs. TV is fine ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "The Word and The end, not IDs or CDs. TV is fine ");
// "TEh" loses its second capital, then the replacement list fixes the typo.
assert_eq!(typed("TEh ").doc.plain_text(StoryRef::Body), "The ");
// A user exception.
let mut s = typed("");
run(&mut s, "tools.autocorrect", json!({"addInitialCapsException": "WCraft"}));
typ(&mut s, "WCraft ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "WCraft ");
}
#[test]
fn accidental_caps_lock_is_corrected() {
let s = typed("a wORD and hELLO there. iOS stays ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "A Word and Hello there. iOS stays ");
}
#[test]
fn capitalisation_fixes_undo_and_turn_off() {
let mut s = typed("WOrd");
typ(&mut s, " ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "Word ");
run(&mut s, "edit.undo", json!({}));
assert_eq!(s.doc.plain_text(StoryRef::Body), "WOrd ");
let mut s = typed("x wORD");
typ(&mut s, " ");
run(&mut s, "edit.undo", json!({}));
assert_eq!(s.doc.plain_text(StoryRef::Body), "X wORD ");
let mut s = typed("");
run(&mut s, "tools.autocorrect", json!({"twoInitialCaps": false, "capsLock": false}));
typ(&mut s, "x WOrd wORD ");
assert_eq!(s.doc.plain_text(StoryRef::Body), "X WOrd wORD ");
}
#[test]
fn first_letter_of_a_table_cell_is_capitalised() {
let cell_text = |s: &Session, c: usize| {
s.doc.body.iter().find_map(|b| b.as_table()).and_then(|t| t.rows.first()?.cells.get(c)?.blocks.first()?.as_para().map(|p| p.text.clone()))
};
let mut s = typed("");
run(&mut s, "insert.table", json!({"rows": 1, "cols": 2}));
typ(&mut s, "apples and pears\tplums ");
assert_eq!(cell_text(&s, 0).as_deref(), Some("Apples and pears"));
assert_eq!(cell_text(&s, 1).as_deref(), Some("Plums "));
run(&mut s, "edit.undo", json!({}));
assert_eq!(cell_text(&s, 1).as_deref(), Some("plums "));
let mut s = typed("");
run(&mut s, "tools.autocorrect", json!({"capCells": false}));
run(&mut s, "insert.table", json!({"rows": 1, "cols": 1}));
typ(&mut s, "plums ");
assert_eq!(cell_text(&s, 0).as_deref(), Some("plums "));
}
/// File › Options › Advanced, "Typing replaces selected text" (#485): on (Word's default) the
/// typed text replaces the selection; off, it goes in front of the selection and keeps it.
#[test]

View File

@ -336,9 +336,11 @@ impl AutoCorrectForm {
let o = &self.options;
json!({
"replaceText": o.replace_text,
"twoInitialCaps": o.two_initial_caps,
"capSentences": o.cap_sentences,
"capCells": o.cap_cells,
"capDays": o.cap_days,
"capsLock": o.caps_lock,
"smartQuotes": o.smart_quotes,
"fractions": o.fractions,
"ordinals": o.ordinals,
@ -410,6 +412,7 @@ fn autocorrect_ui(app: &mut WordApp, ui: &mut Ui, f: &mut AutoCorrectForm) -> bo
fn autocorrect_tab(app: &mut WordApp, ui: &mut Ui, f: &mut AutoCorrectForm) {
let o = &mut f.options;
ui.checkbox(&mut o.two_initial_caps, tl!("Correct TWo INitial CApitals"));
ui.checkbox(&mut o.cap_sentences, tl!("Capitalize first letter of sentences"));
ui.horizontal(|ui| {
ui.add_space(18.0);
@ -423,6 +426,7 @@ fn autocorrect_tab(app: &mut WordApp, ui: &mut Ui, f: &mut AutoCorrectForm) {
let o = &mut f.options;
ui.checkbox(&mut o.cap_cells, tl!("Capitalize first letter of table cells"));
ui.checkbox(&mut o.cap_days, tl!("Capitalize names of days"));
ui.checkbox(&mut o.caps_lock, tl!("Correct accidental usage of cAPS LOCK key"));
ui.checkbox(&mut o.replace_text, tl!("Replace text as you type"));
egui::Grid::new("ac_boxes").num_columns(2).spacing(vec2(8.0, 2.0)).show(ui, |ui| {
ui.label(tl!("Replace:"));

View File

@ -2058,3 +2058,5 @@
Reading Column Width Šířka sloupce pro čtení
Reading Line Spacing Řádkování pro čtení
Reading Text Size Velikost textu pro čtení
Correct TWo INitial CApitals Opravovat DVě POčáteční VElká písmena
Correct accidental usage of cAPS LOCK key Opravovat náhodné použití klávesy cAPS LOCK

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -2052,3 +2052,5 @@
Reading Column Width Spaltenbreite beim Lesen
Reading Line Spacing Zeilenabstand beim Lesen
Reading Text Size Textgröße beim Lesen
Correct TWo INitial CApitals ZWei GRoßbuchstaben am WOrtanfang korrigieren
Correct accidental usage of cAPS LOCK key Unbeabsichtigtes Verwenden der fESTSTELLTASTE korrigieren

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -1468,6 +1468,8 @@
Border lines Líneas de borde
Capitalize first letter of sentences Poner en mayúscula la primera letra de las oraciones
Capitalize first letter of table cells Poner en mayúscula la primera letra de las celdas
Correct TWo INitial CApitals Corregir DOs MAyúsculas SEguidas
Correct accidental usage of cAPS LOCK key Corregir el uso accidental de bLOQ mAYÚS
Capitalize names of days Poner en mayúscula los nombres de los días
Case changes Cambios de mayúsculas
Character level Nivel de carácter

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -1447,6 +1447,8 @@
Border lines Äärisjooned
Capitalize first letter of sentences Lausete esitäht suureks
Capitalize first letter of table cells Tabelilahtrite esitäht suureks
Correct TWo INitial CApitals Paranda KAks SUurt algustähte
Correct accidental usage of cAPS LOCK key Paranda kogemata sisse jäänud cAPS LOCK
Capitalize names of days Nädalapäevade nimed suure algustähega
Case changes Tähesuuruse muutused
Character level Märgi tasandil

Can't render this file because it has a wrong number of fields in line 12.

View File

@ -2061,3 +2061,5 @@
Reading Column Width Largeur de colonne de lecture
Reading Line Spacing Interligne de lecture
Reading Text Size Taille du texte de lecture
Correct TWo INitial CApitals Corriger DEux MAjuscules EN début de mot
Correct accidental usage of cAPS LOCK key Corriger l’utilisation accidentelle de la touche vERR. mAJ

Can't render this file because it contains an unexpected character in line 7 and column 25.

View File

@ -2451,3 +2451,5 @@
Reading Column Width Olvasási oszlopszélesség
Reading Line Spacing Olvasási sorköz
Reading Text Size Olvasási szövegméret
Correct TWo INitial CApitals KÉt KEzdő NAgybetű javítása
Correct accidental usage of cAPS LOCK key A cAPS LOCK billentyű véletlen használatának javítása

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -2437,3 +2437,5 @@ preview Title Esempio
Reading Column Width Larghezza colonna di lettura
Reading Line Spacing Interlinea di lettura
Reading Text Size Dimensione testo di lettura
Correct TWo INitial CApitals Correggi DUe MAiuscole INiziali
Correct accidental usage of cAPS LOCK key Correggi l'uso accidentale del tasto bLOC mAIUSC

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -1428,6 +1428,8 @@
Border lines 罫線
Capitalize first letter of sentences 文の先頭文字を大文字にする
Capitalize first letter of table cells 表のセルの先頭文字を大文字にする
Correct TWo INitial CApitals 先頭 2 文字の大文字を修正する
Correct accidental usage of cAPS LOCK key Caps Lock キーの押し間違いを修正する
Capitalize names of days 曜日の先頭文字を大文字にする
Case changes 大文字/小文字の変更
Character level 文字単位

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -2094,3 +2094,5 @@
Reading Column Width Szerokość kolumny do czytania
Reading Line Spacing Interlinia do czytania
Reading Text Size Rozmiar tekstu do czytania
Correct TWo INitial CApitals Poprawiaj DWie POczątkowe WIelkie litery
Correct accidental usage of cAPS LOCK key Poprawiaj przypadkowe użycie klawisza cAPS LOCK

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -1440,6 +1440,8 @@
Border lines Linhas de borda
Capitalize first letter of sentences Primeira letra das frases em maiúscula
Capitalize first letter of table cells Primeira letra das células em maiúscula
Correct TWo INitial CApitals Corrigir DUas INiciais MAiúsculas
Correct accidental usage of cAPS LOCK key Corrigir o uso acidental da tecla cAPS LOCK
Capitalize names of days Nomes dos dias em maiúscula
Case changes Alterações de maiúsculas
Character level Nível de caractere

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -2037,6 +2037,8 @@ preview Title Пример
Border lines Линии границ
Capitalize first letter of sentences Делать первые буквы предложений прописными
Capitalize first letter of table cells Делать первые буквы ячеек таблиц прописными
Correct TWo INitial CApitals Исправлять ДВе ПРописные буквы в начале слова
Correct accidental usage of cAPS LOCK key Устранять последствия случайного нажатия cAPS LOCK
Capitalize names of days Названия дней с прописной буквы
Case changes Изменения регистра
Character level На уровне символов

Can't render this file because it contains an unexpected character in line 7 and column 25.

View File

@ -1438,6 +1438,8 @@
Border lines Linije ivica
Capitalize first letter of sentences Veliko prvo slovo rečenice
Capitalize first letter of table cells Veliko prvo slovo u ćelijama tabele
Correct TWo INitial CApitals Ispravi DVa POčetna velika slova
Correct accidental usage of cAPS LOCK key Ispravi slučajno uključen cAPS LOCK
Capitalize names of days Veliko slovo u nazivima dana
Case changes Promene veličine slova
Character level Nivo znaka

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -1442,6 +1442,8 @@
Border lines Линије ивица
Capitalize first letter of sentences Велико прво слово реченице
Capitalize first letter of table cells Велико прво слово у ћелијама табеле
Correct TWo INitial CApitals Исправи ДВа ПОчетна велика слова
Correct accidental usage of cAPS LOCK key Исправи случајно укључен cAPS LOCK
Capitalize names of days Велико слово у називима дана
Case changes Промене величине слова
Character level Ниво знака

Can't render this file because it contains an unexpected character in line 11 and column 25.

View File

@ -2039,6 +2039,8 @@ preview Title Örnek
Border lines Kenarlık çizgileri
Capitalize first letter of sentences Cümlelerin ilk harfini büyük yap
Capitalize first letter of table cells Tablo hücrelerinin ilk harfini büyük yap
Correct TWo INitial CApitals İKi BAşharfi büyük olan sözcükleri düzelt
Correct accidental usage of cAPS LOCK key cAPS LOCK tuşunun yanlışlıkla kullanımını düzelt
Capitalize names of days Gün adlarını büyük harfle başlat
Case changes Büyük/küçük harf değişiklikleri
Character level Karakter düzeyi

Can't render this file because it has a wrong number of fields in line 15.

View File

@ -1440,6 +1440,8 @@
Border lines Лінії меж
Capitalize first letter of sentences Перша літера речення велика
Capitalize first letter of table cells Перша літера в клітинках таблиці велика
Correct TWo INitial CApitals Виправляти ДВі ВЕликі літери на початку слова
Correct accidental usage of cAPS LOCK key Виправляти випадкове натискання cAPS LOCK
Capitalize names of days Назви днів із великої літери
Case changes Зміни регістру
Character level Рівень символів

Can't render this file because it contains an unexpected character in line 6 and column 31.

View File

@ -1440,6 +1440,8 @@
Border lines 边框线
Capitalize first letter of sentences 句首字母大写
Capitalize first letter of table cells 表格单元格首字母大写
Correct TWo INitial CApitals 更正前两个字母连续大写
Correct accidental usage of cAPS LOCK key 更正误按 Caps Lock 键造成的大小写错误
Capitalize names of days 星期名称首字母大写
Case changes 大小写更改
Character level 字符级

Can't render this file because it contains an unexpected character in line 7 and column 25.

View File

@ -1429,6 +1429,8 @@
Border lines 框線
Capitalize first letter of sentences 句首字母大寫
Capitalize first letter of table cells 表格儲存格首字母大寫
Correct TWo INitial CApitals 更正前兩個字母連續大寫
Correct accidental usage of cAPS LOCK key 更正意外使用 Caps Lock 鍵造成的大小寫錯誤
Capitalize names of days 星期名稱首字母大寫
Case changes 大小寫變更
Character level 字元層級

Can't render this file because it has a wrong number of fields in line 4.

View File

@ -1,6 +1,6 @@
# Typing parity with Word
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (right-to-left letters typed into an empty paragraph make it right-to-left, #713; previously minor: editing options: typing replaces selection, overtype, #485; previously minor: 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-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
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
@ -63,6 +63,18 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin
after abbreviations (`e.g.`, `Dr.`, …) or initials (`J.`), and not for mixed-case words
(`iPhone`) or single-letter labels (`a.`). `teh`→`the`, `i`→`I`, `(c)`→`©`, `(tm)`→`™`,
`wait...`→`wait…`, smart quotes (`autocorrect_as_you_type`).
- Correct TWo INitial CApitals: a finished word of three or more letters with two capitals and
the rest lowercase loses its second capital (`WOrd`→`Word`, `TEh`→`The`); not the exceptions
(`IDs`, `CDs`, `PCs`, `TVs`, … plus the user's, `tools.autocorrect {"addInitialCapsException"}`)
(`two_initial_capitals_are_corrected`).
- Correct accidental usage of cAPS LOCK: `wORD`→`Word` (three or more letters; not `iOS`,
`mRNA`, …). Word also switches Caps Lock off; we only correct the word
(`accidental_caps_lock_is_corrected`).
- Capitalize first letter of table cells: the first word typed in a cell is capitalised like a
sentence start, under its own option (`first_letter_of_a_table_cell_is_capitalised`).
- Each fix is one undo step right after it, and each has its own switch (`twoInitialCaps`,
`capsLock`, `capCells` on `tools.autocorrect`; on by default)
(`capitalisation_fixes_undo_and_turn_off`).
- `1/2 1/4 3/4` → `½ ¼ ¾`; `1st 22nd 13th` get superscript suffixes (`ordinals_become_superscript`).
- `word -- word` → en dash, `word - word` → en dash, `word--word` → em dash (`dashes`).
- Web addresses become links; following text isn't linked (`web_addresses_become_links`).
@ -128,7 +140,9 @@ Word.
- Moving paragraphs with Track Changes on isn't recorded as a tracked move; whole table rows
don't move yet (a selection across cells is refused). Go Back's places aren't mapped through
later edits (they are clamped to the document).
- Not yet compared: Correct TWo INitial CApitals; the rows marked ¹ above.
- Not yet compared: keystroke-by-keystroke comparison of Correct TWo INitial CApitals, cAPS LOCK
and table-cell capitals (implemented from Word's AutoCorrect options; Word's exception list
not compared); the rows marked ¹ above.
## How to compare
@ -144,6 +158,7 @@ Word.
| Date | Change | Summary |
|---|---|---|
| 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-11 | trivial | Grammar proofing (not AutoCorrect) no longer suggests capitalising a sentence after `...` or `…` (`proof::tests::ellipses_do_not_start_sentences`) |
| 2026-10-11 | minor | AutoFormat tables from `+---+`, arrow bullets, smileys inside words, undo after a border line (#546) |