diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 09b6dbf..f1f2348 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -125,6 +125,27 @@ fn reset_shortcuts_remove_direct_formatting_but_keep_styles() { assert_eq!(text(&s), "Hello world—"); } +#[test] +fn non_english_runs_are_not_proofed() { + // #100: runs tagged with a proofing language that has no dictionary are skipped + // by both proofing paths; English still is proofed. + let mut s = s(); + run(&mut s, "text.insert", json!({"text": "ąžuolas labas"})); + let before = run(&mut s, "review.issues", json!({})); + assert!(!before.as_array().map(|a| a.is_empty()).unwrap_or(true), "default language flags the words: {before}"); + + run(&mut s, "select.all", json!({})); + run(&mut s, "review.language", json!({"lang": "lt-LT"})); + let after = run(&mut s, "review.issues", json!({})); + assert_eq!(after, json!([]), "Lithuanian runs are skipped: {after}"); + + // English typos are still caught. + let mut e = Session::new(wordcraft_doc::Document::new()); + run(&mut e, "text.insert", json!({"text": "Hello helo world"})); + let en = run(&mut e, "review.issues", json!({})); + assert_eq!(en.as_array().map(|a| a.len()).unwrap_or(0), 1, "English typo still flagged: {en}"); +} + #[test] fn typing_enter_undo() { let mut s = s(); diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 71a0e4c..34693d7 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -186,6 +186,19 @@ fn known_core(w: &str) -> bool { 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) != ProofLang::Other +} + +/// 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 { @@ -688,6 +701,35 @@ mod tests { assert!(is_correct("zxqwordcrafty")); } + /// #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("es"), 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}"); + } + } + + /// #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() { @@ -757,6 +799,8 @@ mod tests { 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::Pl, ProofLang::Other] { let _ = check_spelling_in(&s, lang); diff --git a/docs/gaps.md b/docs/gaps.md index 578532f..a0d7636 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -69,6 +69,7 @@ The same gaps grouped the way the parity documents are, for agents working in on | Date | Change | Summary | |---|---|---| +| 2026-10-11 | trivial | #8 narrowed: runs in a proofing language without a dictionary are skipped instead of flagged against the English dictionary (#100); dictionaries still English-only | | 2026-10-11 | trivial | #16: DOCX open/save preserves independent complex-script formatting inheritance; RTL sections, tables and typography remain open | | 2026-10-11 | trivial | `.doc` pictures, shapes and text boxes read (#551); gap #14 narrowed to the writer and Word 6/95 files | | 2026-10-11 | trivial | Table column and row borders and the resize handle drag on the page (#217); UI/UX gap narrowed to the ruler's column markers | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 79c94d6..63265d5 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -251,6 +251,7 @@ can be used without committing Word output. | Date | Change | Summary | |---|---|---| +| 2026-10-11 | trivial | Proofing: runs in a language without a dictionary are skipped instead of flagged against the English dictionary (#100); no percentage change | | 2026-10-11 | minor | Polish interface (13 UI languages) and Polish proofing with per-run languages: Proofing 35% → 40% (25–45 h → 20–40 h), Localization 25% → 27%; feature depth and readiness unchanged at this precision | | 2026-10-11 | trivial | Tables: column and row borders and the resize handle drag on the page (#217); Tables row and interaction-fidelity evidence updated, no percentage change | | 2026-10-11 | trivial | Review: Show revisions in balloons takes tracked deletions in the body out of the text into "Deleted: …" balloons (it drew like the comments-and-formatting mode before); Review row updated, no percentage change |