diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index ecee45a6d..e78fe1ea3 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -11,7 +11,8 @@ permissions: concurrency: group: sonarcloud-${{ github.ref }} - cancel-in-progress: true + # A push to main is the run that publishes coverage; superseded runs there would never finish. + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: sonarcloud: diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs index 1d0ee5a7d..0cf413dd9 100644 --- a/crates/engine/src/cmd/format.rs +++ b/crates/engine/src/cmd/format.rs @@ -7,6 +7,7 @@ use wordcraft_doc::resolve::ResolvedChar; use wordcraft_doc::{InlineObject, PartKind, Pos, RevisionKind, StoryRef}; use super::sel_result; +use super::tools::{is_turkic, lower_char, upper_char}; use crate::{CmdError, CmdResult, CommandSpec, Session, p}; /// Word's font size list. @@ -453,7 +454,16 @@ fn change_case(s: &mut Session, v: &Value) -> CmdResult { let to = if path == b.path { b.off } else { p.len() }; let chars = live_chars(p, from, to); let src: String = chars.iter().map(|(_, c)| c).collect(); - case_groups(p, &chars, &convert_chars(&src, &mode, from == 0)) + // Turkish, Azerbaijani and Crimean Tatar text follows the same case mapping as + // AutoCorrect (#1923); each run's own language decides (#3479). + let turkic: Vec = chars + .iter() + .map(|(off, _)| { + let lang = s.doc.styles.resolve_char(p.props.style.as_deref(), p.props_of_char(*off)).lang; + is_turkic(lang.as_deref().or(s.editing_lang.as_deref())) + }) + .collect(); + case_groups(p, &chars, &convert_chars(&src, &mode, from == 0, &turkic)) }; if groups.is_empty() { continue; @@ -587,37 +597,47 @@ fn is_title(t: &str) -> bool { #[cfg(test)] fn convert(src: &str, mode: &str, para_start: bool) -> String { - convert_chars(src, mode, para_start).concat() + let turkic = vec![false; src.chars().count()]; + convert_chars(src, mode, para_start, &turkic).concat() } /// The case-converted form of each character of `src`, in order (one entry per char, so a -/// change of length such as ß → SS or İ → i̇ can be applied run by run). -fn convert_chars(src: &str, mode: &str, para_start: bool) -> Vec { +/// change of length such as ß → SS or İ → i̇ can be applied run by run). `turkic` is one flag +/// per char: true when that character's run is Turkish, Azerbaijani or Crimean Tatar. +fn convert_chars(src: &str, mode: &str, para_start: bool, turkic: &[bool]) -> Vec { let chars: Vec = src.chars().collect(); // Greek capital sigma lowers to the final form at the end of a word (as `str::to_lowercase`). let lower = |i: usize, c: char| -> String { let prev = i.checked_sub(1).and_then(|j| chars.get(j)).is_some_and(|p| p.is_alphabetic()); let next = chars.get(i + 1).is_some_and(|n| n.is_alphabetic()); - if c == 'Σ' && prev && !next { "ς".to_string() } else { c.to_lowercase().collect() } + if c == 'Σ' && prev && !next { + "ς".to_string() + } else if turkic.get(i).copied().unwrap_or(false) { + lower_char(c, true) + } else { + c.to_lowercase().collect() + } }; // Title case capitalises the first word of any selection; sentence case only at a paragraph start. let mut up = mode == "title" || para_start; let mut out = Vec::with_capacity(chars.len()); for (i, &c) in chars.iter().enumerate() { + let turkic = turkic.get(i).copied().unwrap_or(false); + let upper = |c: char| upper_char(c, turkic); let new = match mode { - "upper" => c.to_uppercase().collect(), + "upper" => upper(c), "lower" => lower(i, c), "toggle" => { if c.is_uppercase() { lower(i, c) } else { - c.to_uppercase().collect() + upper(c) } } "title" => { let n = if up && c.is_alphabetic() { up = false; - c.to_uppercase().collect() + upper(c) } else { lower(i, c) }; @@ -630,7 +650,7 @@ fn convert_chars(src: &str, mode: &str, para_start: bool) -> Vec { // Sentence case. let n = if up && c.is_alphabetic() { up = false; - c.to_uppercase().collect() + upper(c) } else { lower(i, c) }; diff --git a/crates/engine/src/cmd/tools.rs b/crates/engine/src/cmd/tools.rs index dde4631ad..3403cae1f 100644 --- a/crates/engine/src/cmd/tools.rs +++ b/crates/engine/src/cmd/tools.rs @@ -1028,7 +1028,7 @@ fn prev_token(before: &str) -> String { } /// Turkish, Azerbaijani (and Crimean Tatar) map `i`/`İ` and `ı`/`I` as pairs (#1923). -fn is_turkic(lang: Option<&str>) -> bool { +pub(crate) fn is_turkic(lang: Option<&str>) -> bool { if wordcraft_proof::ProofLang::from_tag(lang) == wordcraft_proof::ProofLang::Tr { return true; // tr, tur, any region, same normalisation as proofing } @@ -1038,7 +1038,7 @@ fn is_turkic(lang: Option<&str>) -> bool { } /// A letter in capitals: `i` → `İ` in Turkic languages, otherwise the Unicode default. -fn upper_char(c: char, turkic: bool) -> String { +pub(crate) fn upper_char(c: char, turkic: bool) -> String { if turkic && c == 'i' { return "\u{130}".to_string(); } @@ -1047,7 +1047,7 @@ fn upper_char(c: char, turkic: bool) -> String { /// A letter in lowercase: `I` → `ı` in Turkic languages. `İ` is always the single letter `i`, /// never `i` plus a combining dot. -fn lower_char(c: char, turkic: bool) -> String { +pub(crate) fn lower_char(c: char, turkic: bool) -> String { match c { 'I' if turkic => "\u{131}".to_string(), '\u{130}' => "i".to_string(), diff --git a/crates/engine/src/tests_typing.rs b/crates/engine/src/tests_typing.rs index 0f44e8a14..54715e015 100644 --- a/crates/engine/src/tests_typing.rs +++ b/crates/engine/src/tests_typing.rs @@ -2075,6 +2075,46 @@ fn autocorrect_capitalisation_follows_turkic_case_mapping() { assert_eq!(s.doc.plain_text(StoryRef::Body), "İngilizce ", "no combining dot in any language"); } +/// #3479: Format > Change Case follows the same Turkic case mapping as AutoCorrect, using +/// each run's own language; other languages keep the Unicode default. +#[test] +fn change_case_follows_turkic_case_mapping() { + for tag in ["tr-TR", "az-AZ"] { + for (mode, input, want) in [ + ("upper", "istanbul", "İSTANBUL"), + ("lower", "IŞIK", "ışık"), + ("title", "istanbul güzel", "İstanbul Güzel"), + ("sentence", "istanbul. izmir", "İstanbul. İzmir"), + ("toggle", "iI", "İı"), + ] { + let mut s = s(); + run(&mut s, "review.language", json!({"lang": tag, "default": true})); + run(&mut s, "text.insert", json!({"text": input, "raw": true})); + run(&mut s, "select.all", json!({})); + run(&mut s, "format.changeCase", json!({"mode": mode})); + assert_eq!(s.doc.plain_text(StoryRef::Body), want, "{tag} {mode}"); + } + } + // Other languages keep the Unicode default. + let mut s = self::s(); + run(&mut s, "text.insert", json!({"text": "istanbul", "raw": true})); + run(&mut s, "select.all", json!({})); + run(&mut s, "format.changeCase", json!({"mode": "title"})); + assert_eq!(s.doc.plain_text(StoryRef::Body), "Istanbul", "en-US"); + // Mixed runs: each run's own language decides. + let mut s = self::s(); + run(&mut s, "text.insert", json!({"text": "IŞIK IŞIK", "raw": true})); + run(&mut s, "caret.set", json!({"pos": Pos::body(0, 0)})); + run(&mut s, "caret.set", json!({"pos": Pos::body(0, 5), "extend": true})); + run(&mut s, "review.language", json!({"lang": "tr-TR"})); + run(&mut s, "caret.set", json!({"pos": Pos::body(0, 6)})); + run(&mut s, "caret.set", json!({"pos": Pos::body(0, 11), "extend": true})); + run(&mut s, "review.language", json!({"lang": "en-US"})); + run(&mut s, "select.all", json!({})); + run(&mut s, "format.changeCase", json!({"mode": "lower"})); + assert_eq!(s.doc.plain_text(StoryRef::Body), "ışık işik", "mixed runs"); +} + // Spelling as you type (#1552): the word being typed gets no red squiggle until the caret leaves // it; F7 and the Spelling pane still report it. See docs/typing-parity.md. Paragraphs start with // a correct word so AutoCorrect's sentence capital doesn't change the misspelled one. diff --git a/crates/proof/src/cs/mod.rs b/crates/proof/src/cs/mod.rs index af743406d..c2ec00c9a 100644 --- a/crates/proof/src/cs/mod.rs +++ b/crates/proof/src/cs/mod.rs @@ -42,10 +42,11 @@ pub fn dictionary() -> Arc { fn load_installed() -> AffixDict { let Some((aff, dic)) = crate::hunspell::find(NAMES) else { + crate::hunspell::clear_load_error(NAMES[0]); log::info!("Czech spelling: no Hunspell cs_CZ dictionary installed"); return AffixDict::default(); }; - crate::hunspell::load(&aff, &dic).unwrap_or_else(|e| { + crate::hunspell::note_load(NAMES[0], &dic, crate::hunspell::load(&aff, &dic)).unwrap_or_else(|e| { log::warn!("Czech spelling: {e}"); AffixDict::default() }) diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index be4a93173..9aba4adde 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -417,17 +417,13 @@ pub fn has_dictionary(lang: Option<&str>) -> bool { ProofLang::from_tag(lang).is_proofed() } -/// The Hunspell dictionary name (`pl_PL`, `pt_BR`) a proofing language needs but doesn't have +/// The Hunspell dictionary name (`pl_PL`, `it_IT`) a proofing language needs but doesn't have /// installed, for a status-bar hint (#1167). `None` when the language's dictionary is available, /// or when it needs none (English has a built-in word list; unproofed languages aren't offered /// one). Reads the dictionary once (it is cached afterwards). pub fn missing_dictionary(lang: Option<&str>) -> Option<&'static str> { - match ProofLang::from_tag(lang) { - ProofLang::Pl => dictionary_needed(ProofLang::Pl, pl::has_dictionary()), - ProofLang::Pt => dictionary_needed(ProofLang::Pt, pt::has_dictionary()), - l if l.is_spanish() => dictionary_needed(l, es::has_dictionary(l)), - _ => None, - } + let lang = ProofLang::from_tag(lang); + dictionary_needed(lang, dictionary_loaded(lang)?) } /// The Hunspell name, `.dic` file and reason of a dictionary that is installed for this language @@ -435,26 +431,40 @@ pub fn missing_dictionary(lang: Option<&str>) -> Option<&'static str> { /// loaded, or when no file exists (then [`missing_dictionary`] names it). Reads the dictionary /// once (it is cached afterwards). pub fn unreadable_dictionary(lang: Option<&str>) -> Option<(&'static str, std::path::PathBuf, String)> { - let (proof_lang, loaded) = match ProofLang::from_tag(lang) { - ProofLang::Pl => (ProofLang::Pl, pl::has_dictionary()), - ProofLang::Pt => (ProofLang::Pt, pt::has_dictionary()), - l if l.is_spanish() => (l, es::has_dictionary(l)), - _ => return None, - }; - let name = dictionary_needed(proof_lang, false)?; - if loaded { - return None; - } + let lang = ProofLang::from_tag(lang); + let name = dictionary_needed(lang, dictionary_loaded(lang)?)?; hunspell::load_error(name).map(|(file, reason)| (name, file, reason)) } -/// The Hunspell name `lang` needs when `installed` is false; Polish, Portuguese and Spanish (per -/// variety: `es_ES`, `es_MX`, `es_419`, `es_UY`) name one. +/// Did the Hunspell dictionary of `lang` load? `None` for languages that use none (English). +fn dictionary_loaded(lang: ProofLang) -> Option { + Some(match lang { + ProofLang::Pl => pl::has_dictionary(), + ProofLang::Pt => pt::has_dictionary(), + ProofLang::It => it::has_dictionary(), + ProofLang::Hr => hr::has_dictionary(), + ProofLang::Tr => tr::has_dictionary(), + ProofLang::Ru => ru::has_dictionary(), + ProofLang::Cs => cs::has_dictionary(), + ProofLang::Id => id::has_dictionary(), + l if l.is_spanish() => es::has_dictionary(l), + _ => return None, + }) +} + +/// The Hunspell name `lang` needs when `installed` is false (the first name its module looks for; +/// a Spanish variety's is its own: `es_ES`, `es_MX`, `es_419`, `es_UY`). fn dictionary_needed(lang: ProofLang, installed: bool) -> Option<&'static str> { match lang { _ if installed => None, ProofLang::Pl => Some("pl_PL"), ProofLang::Pt => Some("pt_BR"), + ProofLang::It => Some("it_IT"), + ProofLang::Hr => Some("hr_HR"), + ProofLang::Tr => Some("tr_TR"), + ProofLang::Ru => Some("ru_RU"), + ProofLang::Cs => Some("cs_CZ"), + ProofLang::Id => Some("id_ID"), l if l.is_spanish() => Some(es::dictionary_name(l)), _ => None, } @@ -1012,10 +1022,27 @@ mod tests { assert_eq!(dictionary_needed(ProofLang::Pt, false), Some("pt_BR")); assert_eq!(dictionary_needed(ProofLang::Pl, true), None); assert_eq!(dictionary_needed(ProofLang::Pt, true), None); - // #3360: each Spanish variety names its own. - for (l, name) in [(ProofLang::Es, "es_ES"), (ProofLang::EsMx, "es_MX"), (ProofLang::Es419, "es_419"), (ProofLang::EsUy, "es_UY")] { - assert_eq!(dictionary_needed(l, false), Some(name)); - assert_eq!(dictionary_needed(l, true), None); + // Every Hunspell language is named by the first name its module looks for (#3150). + for (l, name) in [ + (ProofLang::It, "it_IT"), + (ProofLang::Hr, "hr_HR"), + (ProofLang::Tr, "tr_TR"), + (ProofLang::Ru, "ru_RU"), + (ProofLang::Cs, "cs_CZ"), + (ProofLang::Id, "id_ID"), + // #3360: each Spanish variety names its own. + (ProofLang::Es, "es_ES"), + (ProofLang::EsMx, "es_MX"), + (ProofLang::Es419, "es_419"), + (ProofLang::EsUy, "es_UY"), + ] { + assert_eq!(dictionary_needed(l, false), Some(name), "{l:?}"); + assert_eq!(dictionary_needed(l, true), None, "{l:?}"); + } + // Every proofed language other than English has a Hunspell dictionary to report. + for l in ProofLang::PROOFED { + let english = matches!(l, ProofLang::En | ProofLang::EnGb | ProofLang::EnCa); + assert_eq!(dictionary_needed(l, false).is_some(), !english, "{l:?}"); } for l in [ProofLang::En, ProofLang::Bg, ProofLang::De, ProofLang::Other] { assert_eq!(dictionary_needed(l, false), None); diff --git a/crates/proof/src/ru/mod.rs b/crates/proof/src/ru/mod.rs index 4b1ffe093..c29bbc7c5 100644 --- a/crates/proof/src/ru/mod.rs +++ b/crates/proof/src/ru/mod.rs @@ -63,10 +63,11 @@ pub fn dictionary() -> Arc { fn load_installed() -> AffixDict { let Some((aff, dic)) = crate::hunspell::find(NAMES) else { + crate::hunspell::clear_load_error(NAMES[0]); log::info!("Russian spelling: no Hunspell ru_RU dictionary installed"); return AffixDict::default(); }; - crate::hunspell::load(&aff, &dic).unwrap_or_else(|e| { + crate::hunspell::note_load(NAMES[0], &dic, crate::hunspell::load(&aff, &dic)).unwrap_or_else(|e| { log::warn!("Russian spelling: {e}"); AffixDict::default() }) diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index 3caafc907..60053a871 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -7,6 +7,23 @@ use wordcraft_doc::{PartKind, StoryRef}; use crate::theme::{Tokens, TypeRung, fit_text, medium, paint_text, regular, semibold, text_width}; use crate::{WordApp, icons}; +/// Why the caret's language isn't spell-checked (dictionary unreadable or missing), if it isn't. +pub(crate) fn dictionary_problem(lang: &str, unreadable: &Option<(std::path::PathBuf, String)>, missing: Option<&str>) -> Option { + match (unreadable, missing) { + (Some((file, reason)), _) => Some(crate::i18n::fmt( + tl!("The spelling dictionary for {language} couldn't be read, so it isn't checked. File: {file}. Reason: {reason}"), + &[("language", lang), ("file", &file.to_string_lossy()), ("reason", reason)], + )), + (None, Some(name)) => Some(crate::i18n::fmt( + tl!( + "No spelling dictionary for {language} is installed, so it isn't checked. WordCraft looks for the Hunspell dictionary {name} in the WORDCRAFT_DICTIONARIES folder and in the usual system locations." + ), + &[("language", lang), ("name", name)], + )), + (None, None) => None, + } +} + /// The Quick Access Toolbar's button for command `id`. pub(crate) fn qat_button_id(id: &str) -> egui::Id { egui::Id::new(("qat_button", id)) @@ -590,17 +607,7 @@ pub fn status_bar(app: &mut WordApp, ui: &mut Ui) { } else { lang.clone() }; - let tip = match (&unreadable, missing) { - (Some((file, reason)), _) => crate::i18n::fmt( - tl!("The spelling dictionary for {language} couldn't be read, so it isn't checked. File: {file}. Reason: {reason}"), - &[("language", &lang), ("file", &file.to_string_lossy()), ("reason", reason)], - ), - (None, Some(name)) => crate::i18n::fmt( - tl!("No spelling dictionary for {language} is installed, so it isn't checked. WordCraft looks for the Hunspell dictionary {name} in the WORDCRAFT_DICTIONARIES folder and in the usual system locations."), - &[("language", &lang), ("name", name)], - ), - (None, None) => tl!("Language").to_string(), - }; + let tip = dictionary_problem(&lang, &unreadable, missing).unwrap_or_else(|| tl!("Language").to_string()); let resp = st(ui, &shown).on_hover_text(tip); egui::Popup::menu(&resp).show(|ui| { ui.set_min_width(200.0); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 5ace515e2..e94b1b23e 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2184,7 +2184,9 @@ Show the mini toolbar when text is selected Показване на мини лентата с инструменти при избран текст Spaces Интервали Spelling Правопис - Spelling and grammar are checked in English (United States). Правописът и граматиката се проверяват на английски (САЩ). + Spelling and grammar are checked in {language}. Език за проверка на правописа и граматиката: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Текстът при курсора е отбелязан с: Без проверка на правописа и граматиката. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft все още не проверява правописа и граматиката на този език. Tab characters Знаци за табулация These marks show even when Show/Hide ¶ is off. Тези знаци се показват дори когато „Показване/скриване на ¶“ е изключено. Typing replaces selected text Въвежданият текст замества избрания текст diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index fd2cc0b20..ea787b036 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2277,7 +2277,9 @@ preview Title Ukázka Show the mini toolbar when text is selected Při výběru textu zobrazit miniaturní panel nástrojů Spaces Mezery Spelling Pravopis - Spelling and grammar are checked in English (United States). Pravopis a mluvnice se kontrolují v angličtině (Spojené státy). + Spelling and grammar are checked in {language}. Jazyk kontroly pravopisu a mluvnice: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Text v místě vkládání je označen: Nekontrolovat pravopis ani mluvnici. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft v tomto jazyce zatím nekontroluje pravopis ani mluvnici. Tab characters Znaky tabulátoru These marks show even when Show/Hide ¶ is off. Tyto značky se zobrazují, i když je vypnuto Zobrazit/skrýt ¶. Typing replaces selected text Psaný text nahrazuje vybraný text diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index ad87c1ba4..a4d52a26e 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1990,7 +1990,9 @@ Spaces Mellemrum Special Særlige Spelling Stavning - Spelling and grammar are checked in English (United States). Stavning og grammatik kontrolleres på engelsk (USA). + Spelling and grammar are checked in {language}. Sprog til stave- og grammatikkontrol: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Teksten ved indsætningspunktet er markeret: Kontrollér ikke stavning eller grammatik. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft kontrollerer endnu ikke stavning eller grammatik på dette sprog. Stacked Bar Stablet søjlediagram Stacked Column Stablet kolonnediagram Standard Standard diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 671bed295..0d15aaff9 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1480,7 +1480,9 @@ Show the mini toolbar when text is selected Minisymbolleiste bei Textauswahl anzeigen Spaces Leerzeichen Spelling Rechtschreibung - Spelling and grammar are checked in English (United States). Rechtschreibung und Grammatik werden auf Englisch (USA) geprüft. + Spelling and grammar are checked in {language}. Sprache für Rechtschreibung und Grammatik: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Der Text an der Einfügemarke ist markiert: Rechtschreibung und Grammatik nicht prüfen. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft prüft Rechtschreibung und Grammatik in dieser Sprache noch nicht. Tab characters Tabstoppzeichen These marks show even when Show/Hide ¶ is off. Diese Zeichen werden auch angezeigt, wenn ¶ ein-/ausblenden deaktiviert ist. Typing replaces selected text Eingabe ersetzt markierten Text diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index b8777cb32..ee2f228a9 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -182,7 +182,9 @@ Show the mini toolbar when text is selected Mostrar la minibarra de herramientas al seleccionar texto Spaces Espacios Spelling Ortografía - Spelling and grammar are checked in English (United States). La ortografía y la gramática se revisan en inglés (Estados Unidos). + Spelling and grammar are checked in {language}. Idioma de revisión de ortografía y gramática: {language}. + The text at the insertion point is marked Do not check spelling or grammar. El texto en el punto de inserción está marcado como: No revisar la ortografía ni la gramática. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft todavía no revisa la ortografía ni la gramática en este idioma. Tab characters Caracteres de tabulación Tab stop position: Posición de la tabulación: Default tab stops: Tabulaciones predeterminadas: diff --git a/crates/ui-egui/src/i18n/et.tsv b/crates/ui-egui/src/i18n/et.tsv index dd2464842..72cc93e44 100644 --- a/crates/ui-egui/src/i18n/et.tsv +++ b/crates/ui-egui/src/i18n/et.tsv @@ -764,7 +764,9 @@ Spelling & Grammar Õigekiri ja grammatika Spelling &\nGrammar Õigekiri ja\ngrammatika Spelling Suggestions Õigekirjasoovitused - Spelling and grammar are checked in English (United States). Õigekirja ja grammatikat kontrollitakse inglise keeles (Ameerika Ühendriigid). + Spelling and grammar are checked in {language}. Õigekirja ja grammatika kontrolli keel: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Sisestuskohal olev tekst on märgitud: Ära kontrolli õigekirja ega grammatikat. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft ei kontrolli selles keeles veel õigekirja ega grammatikat. Split Tükelda Split Cells Tükelda lahtrid Split Table Tükelda tabel diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 58b61d339..fcf6282b8 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1941,7 +1941,9 @@ Show the mini toolbar when text is selected Afficher la mini barre d’outils lors de la sélection de texte Spaces Espaces Spelling Orthographe - Spelling and grammar are checked in English (United States). L’orthographe et la grammaire sont vérifiées en anglais (États-Unis). + Spelling and grammar are checked in {language}. Langue de vérification de l’orthographe et de la grammaire : {language}. + The text at the insertion point is marked Do not check spelling or grammar. Le texte au point d’insertion est marqué : Ne pas vérifier l’orthographe ni la grammaire. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language} : WordCraft ne vérifie pas encore l’orthographe ni la grammaire dans cette langue. Tab characters Caractères de tabulation These marks show even when Show/Hide ¶ is off. Ces marques s’affichent même lorsque Afficher/Masquer ¶ est désactivé. Typing replaces selected text La frappe remplace le texte sélectionné diff --git a/crates/ui-egui/src/i18n/hr.tsv b/crates/ui-egui/src/i18n/hr.tsv index 3cacead69..12e6eeb7d 100644 --- a/crates/ui-egui/src/i18n/hr.tsv +++ b/crates/ui-egui/src/i18n/hr.tsv @@ -2102,7 +2102,9 @@ template Letter Pismo Spelling & Grammar Pravopis i gramatika Spelling &\nGrammar Pravopis i\ngramatika Spelling Suggestions Pravopisni prijedlozi - Spelling and grammar are checked in English (United States). Pravopis i gramatika provjeravaju se na engleskom (Sjedinjene Američke Države). + Spelling and grammar are checked in {language}. Jezik provjere pravopisa i gramatike: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Tekst na točki umetanja označen je: Ne provjeravaj pravopis ni gramatiku. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft na ovom jeziku još ne provjerava pravopis ni gramatiku. Split Podijeli Split Cells Podijeli ćelije Split Table Podijeli tablicu diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 6884d09ed..d5d6f5e23 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1613,7 +1613,9 @@ Show the mini toolbar when text is selected Mini eszköztár megjelenítése szövegkijelöléskor Spaces Szóközök Spelling Helyesírás - Spelling and grammar are checked in English (United States). A helyesírás- és nyelvhelyesség-ellenőrzés nyelve: angol (Egyesült Államok). + Spelling and grammar are checked in {language}. A helyesírás- és nyelvhelyesség-ellenőrzés nyelve: {language}. + The text at the insertion point is marked Do not check spelling or grammar. A beszúrási pontnál lévő szöveg jelölése: Nincs helyesírás- és nyelvhelyesség-ellenőrzés. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: a WordCraft ezen a nyelven még nem ellenőrzi a helyesírást és a nyelvhelyességet. Tab characters Tabulátorok These marks show even when Show/Hide ¶ is off. Ezek a jelek akkor is látszanak, ha a Megjelenítés/elrejtés ¶ ki van kapcsolva. Typing replaces selected text A gépelés felülírja a kijelölt szöveget diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 298319be3..9e0e64f74 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1558,7 +1558,9 @@ Show the mini toolbar when text is selected Mostra la barra di formattazione rapida quando selezioni il testo Spaces Spazi Spelling Ortografia - Spelling and grammar are checked in English (United States). L'ortografia e la grammatica vengono controllate in inglese (Stati Uniti). + Spelling and grammar are checked in {language}. Lingua del controllo ortografico e grammaticale: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Il testo nel punto di inserimento è contrassegnato: Non controllare ortografia e grammatica. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft non controlla ancora ortografia e grammatica in questa lingua. Tab characters Caratteri di tabulazione These marks show even when Show/Hide ¶ is off. Questi segni vengono mostrati anche quando Mostra tutto è disattivato. Typing replaces selected text Il testo digitato sostituisce la selezione diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index df6706c36..6fa085385 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -197,7 +197,9 @@ Show the mini toolbar when text is selected テキストを選択したときにミニ ツール バーを表示する Spaces スペース Spelling スペル チェック - Spelling and grammar are checked in English (United States). スペルと文法は英語 (米国) でチェックされます。 + Spelling and grammar are checked in {language}. スペルと文法のチェック言語: {language} + The text at the insertion point is marked Do not check spelling or grammar. 挿入ポイントのテキストの設定: スペルチェックと文章校正を行わない + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft はこの言語のスペルと文法をまだチェックしません。 Tab characters タブ Tab stop position: タブ位置: Default tab stops: 既定のタブ間隔: diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 93352e76f..1e4d23d6a 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -161,7 +161,9 @@ Show the mini toolbar when text is selected 텍스트를 선택할 때 미니 도구 모음 표시 Spaces 공백 Spelling 맞춤법 - Spelling and grammar are checked in English (United States). 맞춤법과 문법은 영어(미국)로 검사합니다. + Spelling and grammar are checked in {language}. 맞춤법 및 문법 검사 언어: {language} + The text at the insertion point is marked Do not check spelling or grammar. 삽입 위치의 텍스트 설정: 맞춤법 및 문법 검사 안 함 + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft는 아직 이 언어의 맞춤법 및 문법을 검사하지 않습니다. Tab characters 탭 문자 Tab stop position: 탭 위치: Default tab stops: 기본 탭 위치: diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 6b9e0eff3..20fb89bd1 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1990,7 +1990,9 @@ Spaces Spaties Special Speciaal Spelling Spelling - Spelling and grammar are checked in English (United States). Spelling en grammatica worden gecontroleerd in het Engels (Verenigde Staten). + Spelling and grammar are checked in {language}. Controletaal voor spelling en grammatica: {language}. + The text at the insertion point is marked Do not check spelling or grammar. De tekst bij de invoegpositie is gemarkeerd: Spelling of grammatica niet controleren. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft controleert in deze taal nog geen spelling of grammatica. Stacked Bar Gestapeld staafdiagram Stacked Column Gestapeld kolomdiagram Standard Standaard diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 3eee4f2d0..f9d3a28ed 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2055,7 +2055,9 @@ Spaces Spacje Special Specjalne Spelling Pisownia - Spelling and grammar are checked in English (United States). Pisownia i gramatyka są sprawdzane w języku angielskim (Stany Zjednoczone). + Spelling and grammar are checked in {language}. Język sprawdzania pisowni i gramatyki: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Tekst w punkcie wstawiania jest oznaczony: Nie sprawdzaj pisowni ani gramatyki. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft nie sprawdza jeszcze pisowni ani gramatyki w tym języku. Stacked Bar Słupkowy skumulowany Stacked Column Kolumnowy skumulowany Standard Standardowe diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index f33cc082d..ad61ed32a 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -177,7 +177,9 @@ Show the mini toolbar when text is selected Mostrar a minibarra de ferramentas ao selecionar texto Spaces Espaços Spelling Ortografia - Spelling and grammar are checked in English (United States). A ortografia e a gramática são verificadas em inglês (Estados Unidos). + Spelling and grammar are checked in {language}. Idioma da verificação de ortografia e gramática: {language}. + The text at the insertion point is marked Do not check spelling or grammar. O texto no ponto de inserção está marcado como: Não verificar ortografia nem gramática. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: o WordCraft ainda não verifica ortografia nem gramática neste idioma. Tab characters Caracteres de tabulação Tab stop position: Posição da tabulação: Default tab stops: Tabulações padrão: diff --git a/crates/ui-egui/src/i18n/ro.tsv b/crates/ui-egui/src/i18n/ro.tsv index d2422c85b..272b17167 100644 --- a/crates/ui-egui/src/i18n/ro.tsv +++ b/crates/ui-egui/src/i18n/ro.tsv @@ -2222,7 +2222,9 @@ Spelling & Grammar Ortografie și gramatică Spelling &\nGrammar Ortografie și\ngramatică Spelling Suggestions Sugestii de ortografie - Spelling and grammar are checked in English (United States). Ortografia și gramatica se verifică în engleză (Statele Unite). + Spelling and grammar are checked in {language}. Limba de verificare a ortografiei și gramaticii: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Textul de la punctul de inserare este marcat: Nu verifica ortografia și gramatica. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft nu verifică încă ortografia și gramatica în această limbă. Spike Spike Split Împărțire Split Cells Împărțire celule diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 0c1ffe78b..5e1069948 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2483,7 +2483,9 @@ preview Title Пример Show the mini toolbar when text is selected Показывать мини-панель при выделении текста Spaces Пробелы Spelling Орфография - Spelling and grammar are checked in English (United States). Орфография и грамматика проверяются для английского языка (США). + Spelling and grammar are checked in {language}. Язык проверки орфографии и грамматики: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Текст в точке вставки помечен: Не проверять орфографию и грамматику. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft пока не проверяет орфографию и грамматику на этом языке. Tab characters Знаки табуляции These marks show even when Show/Hide ¶ is off. Эти знаки видны, даже когда «Показать все знаки» выключено. Typing replaces selected text Ввод заменяет выделенный текст diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index c55221a99..3a4cd73e8 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -2194,7 +2194,9 @@ Spelling Črkovanje Spelling & Grammar Črkovanje in slovnica Spelling &\nGrammar Črkovanje\nin slovnica - Spelling and grammar are checked in English (United States). Črkovanje in slovnica se preverjata v angleščini (Združene države). + Spelling and grammar are checked in {language}. Jezik preverjanja črkovanja in slovnice: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Besedilo na mestu vstavljanja je označeno: Ne preverjaj črkovanja ali slovnice. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft v tem jeziku še ne preverja črkovanja ali slovnice. Spelling Suggestions Predlogi za črkovanje Split Razdeli Split Cells Razdeli celice diff --git a/crates/ui-egui/src/i18n/sr-latn.tsv b/crates/ui-egui/src/i18n/sr-latn.tsv index 47080d476..8b6e068c9 100644 --- a/crates/ui-egui/src/i18n/sr-latn.tsv +++ b/crates/ui-egui/src/i18n/sr-latn.tsv @@ -254,7 +254,9 @@ Show the mini toolbar when text is selected Prikaži mini traku sa alatkama pri izboru teksta Spaces Razmaci Spelling Pravopis - Spelling and grammar are checked in English (United States). Pravopis i gramatika se proveravaju na engleskom (Sjedinjene Države). + Spelling and grammar are checked in {language}. Jezik provere pravopisa i gramatike: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Tekst na mestu umetanja je označen: Ne proveravaj pravopis ni gramatiku. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft još ne proverava pravopis ni gramatiku na ovom jeziku. Tab characters Znakovi tabulatora Tab stop position: Položaj tabulatora: Default tab stops: Podrazumevani tabulatori: diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index 176334d98..f0e4d893e 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -258,7 +258,9 @@ Show the mini toolbar when text is selected Прикажи мини траку са алаткама при избору текста Spaces Размаци Spelling Правопис - Spelling and grammar are checked in English (United States). Правопис и граматика се проверавају на енглеском (Сједињене Државе). + Spelling and grammar are checked in {language}. Језик провере правописа и граматике: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Текст на месту уметања је означен: Не проверавај правопис ни граматику. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft још не проверава правопис ни граматику на овом језику. Tab characters Знакови табулатора Tab stop position: Положај табулатора: Default tab stops: Подразумевани табулатори: diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 43b609c6b..a3727e570 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -176,7 +176,9 @@ Show the mini toolbar when text is selected Visa miniverktygsfältet när text är markerad Spaces Blanksteg Spelling Stavning - Spelling and grammar are checked in English (United States). Stavning och grammatik kontrolleras på engelska (USA). + Spelling and grammar are checked in {language}. Språk för stavnings- och grammatikkontroll: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Texten vid insättningspunkten är markerad: Kontrollera inte stavning eller grammatik. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft kontrollerar inte stavning eller grammatik på det här språket än. Tab characters Tabbtecken Tab stop position: Tabbstoppets position: Default tab stops: Standardtabbstopp: diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 65dca2a2e..e6fa2f96a 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2482,7 +2482,9 @@ preview Title Örnek Show the mini toolbar when text is selected Metin seçildiğinde mini araç çubuğunu göster Spaces Boşluklar Spelling Yazım - Spelling and grammar are checked in English (United States). Yazım ve dil bilgisi İngilizce (Amerika Birleşik Devletleri) için denetlenir. + Spelling and grammar are checked in {language}. Yazım ve dil bilgisi denetimi dili: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Ekleme noktasındaki metin şu şekilde işaretli: Yazım ve dil bilgisini denetleme. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft bu dilde henüz yazım ve dil bilgisi denetimi yapmıyor. Tab characters Sekme karakterleri These marks show even when Show/Hide ¶ is off. Bu işaretler, Göster/Gizle ¶ kapalıyken de görünür. Typing replaces selected text Yazmak seçili metnin yerine geçer diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index c7c233ab9..59caf783b 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -179,7 +179,9 @@ Show the mini toolbar when text is selected Показувати міні-панель інструментів під час виділення тексту Spaces Пробіли Spelling Правопис - Spelling and grammar are checked in English (United States). Правопис і граматика перевіряються англійською (США). + Spelling and grammar are checked in {language}. Мова перевірки правопису та граматики: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Текст у точці вставлення позначено: Не перевіряти правопис і граматику. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft поки що не перевіряє правопис і граматику цією мовою. Tab characters Символи табуляції Tab stop position: Позиція табуляції: Default tab stops: Типові позиції табуляції: diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 242396689..6b2497596 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2177,7 +2177,9 @@ Show the mini toolbar when text is selected Hiện thanh công cụ mini khi chọn chữ Spaces Khoảng trắng Spelling Chính tả - Spelling and grammar are checked in English (United States). Soát chính tả và ngữ pháp bằng tiếng Anh (Mỹ). + Spelling and grammar are checked in {language}. Ngôn ngữ soát chính tả và ngữ pháp: {language}. + The text at the insertion point is marked Do not check spelling or grammar. Văn bản tại điểm chèn được đánh dấu: Không kiểm tra chính tả hoặc ngữ pháp. + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}: WordCraft chưa soát chính tả và ngữ pháp cho ngôn ngữ này. Tab characters Ký tự tab These marks show even when Show/Hide ¶ is off. Các dấu này vẫn hiện cả khi đã tắt Hiện/Ẩn ¶. Typing replaces selected text Gõ sẽ thay thế văn bản đã chọn diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 7f12fb522..3c4dd5388 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -209,7 +209,9 @@ Show the mini toolbar when text is selected 选择文本时显示浮动工具栏 Spaces 空格 Spelling 拼写 - Spelling and grammar are checked in English (United States). 拼写和语法按英语(美国)检查。 + Spelling and grammar are checked in {language}. 拼写和语法检查语言:{language} + The text at the insertion point is marked Do not check spelling or grammar. 插入点处的文本已标记为:不检查拼写或语法 + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}:WordCraft 暂不检查此语言的拼写和语法 Tab characters 制表符 Tab stop position: 制表位位置: Default tab stops: 默认制表位: diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index fe94993b1..65eb21910 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -198,7 +198,9 @@ Show the mini toolbar when text is selected 選取文字時顯示迷你工具列 Spaces 空格 Spelling 拼字 - Spelling and grammar are checked in English (United States). 拼字和文法依英文(美國)檢查。 + Spelling and grammar are checked in {language}. 拼字和文法檢查語言:{language} + The text at the insertion point is marked Do not check spelling or grammar. 插入點處的文字已標示為:不要檢查拼字或文法 + {language}: WordCraft doesn't check spelling or grammar in this language yet. {language}:WordCraft 暫不檢查此語言的拼字和文法 Tab characters 定位字元 Tab stop position: 定位點位置: Default tab stops: 預設定位點間距: diff --git a/crates/ui-egui/src/options.rs b/crates/ui-egui/src/options.rs index 5b303f1be..5fa8fee1a 100644 --- a/crates/ui-egui/src/options.rs +++ b/crates/ui-egui/src/options.rs @@ -464,9 +464,17 @@ fn language(app: &mut WordApp, ui: &mut Ui) { // #414 (detect the document's language) goes here. ui.add_space(8.0); section(ui, "Proofing language"); - // The document's default language (Review › Language › Set As Default changes it). - let tag = app.session.doc.styles.default_chr.lang.clone(); - note(ui, &crate::ribbon::language_name(tag.as_deref())); + let lang = crate::ribbon::caret_language(app); + let text = if crate::ribbon::caret_no_proof(app) { + tl!("The text at the insertion point is marked Do not check spelling or grammar.").to_string() + } else if !crate::ribbon::caret_language_proofed(app) { + crate::i18n::fmt(tl!("{language}: WordCraft doesn't check spelling or grammar in this language yet."), &[("language", &lang)]) + } else { + let unreadable = crate::ribbon::caret_unreadable_dictionary(app); + crate::chrome::dictionary_problem(&lang, &unreadable, crate::ribbon::caret_missing_dictionary(app)) + .unwrap_or_else(|| crate::i18n::fmt(tl!("Spelling and grammar are checked in {language}."), &[("language", &lang)])) + }; + ui.label(RichText::new(text).small().weak()); } fn accessibility(app: &mut WordApp, ui: &mut Ui) { diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index e2ca5f491..a7446e875 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -913,11 +913,27 @@ pub(crate) fn caret_language(app: &WordApp) -> String { tl!(&language_name(caret_lang_tag(app).as_deref())).to_string() } -/// The language tag at the caret, resolved through the styles and the document default. -fn caret_lang_tag(app: &WordApp) -> Option { +/// The formatting at the caret, resolved through the styles and the document default. +fn caret_char(app: &WordApp) -> wordcraft_doc::resolve::ResolvedChar { let s = &app.session; let style = s.doc.para_at(&s.sel.focus).and_then(|p| p.props.style.clone()); - s.doc.styles.resolve_char(style.as_deref(), &s.typing_props()).lang + s.doc.styles.resolve_char(style.as_deref(), &s.typing_props()) +} + +/// The language tag at the caret, resolved through the styles and the document default. +fn caret_lang_tag(app: &WordApp) -> Option { + caret_char(app).lang +} + +/// Is the text at the caret itself marked Do not check spelling or grammar? (`format.state` +/// looks at the whole word around a caret, so a partly marked word would read as checked.) +pub(crate) fn caret_no_proof(app: &WordApp) -> bool { + caret_char(app).no_proof +} + +/// Does WordCraft check spelling and grammar in the caret's language at all (Bulgarian: no)? +pub(crate) fn caret_language_proofed(app: &WordApp) -> bool { + wordcraft_engine::proof::has_dictionary(caret_lang_tag(app).as_deref()) } /// The Hunspell dictionary the caret's language needs but doesn't have (`pl_PL`), if any (#1167). diff --git a/docs/spelling-dictionaries.md b/docs/spelling-dictionaries.md index c6db3264a..276f1463f 100644 --- a/docs/spelling-dictionaries.md +++ b/docs/spelling-dictionaries.md @@ -1,6 +1,6 @@ # Installing spelling dictionaries -> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-22 · **Change:** minor (Spanish: `es_ES`, `es_MX`, `es_419` and `es_UY` with their fallbacks, #3360, #3553; previously trivial: ISO 8859-1 dictionaries are read, #2824; previously major: first version) +> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-23 · **Change:** minor (Spanish: `es_ES`, `es_MX`, `es_419` and `es_UY` with their fallbacks, #3360, #3553; previously trivial: every Hunspell language shows (no dictionary), not only Polish and Portuguese, and Indonesian is listed, #3150; previously trivial: ISO 8859-1 dictionaries are read, #2824) English spelling works out of the box. Polish, Russian, Portuguese, Czech, Italian, Croatian, Turkish, Indonesian and Spanish spelling use a standard **Hunspell** dictionary (an `.aff` and a `.dic` file) that @@ -29,6 +29,7 @@ WordCraft looks for the first of these file names (each name means a `.aff | Italian | `it_IT`, `it_CH`, `it-IT`, `it` | LibreOffice `dict-it` | | Croatian | `hr_HR`, `hr-HR`, `hr` | LibreOffice `dict-hr` | | Turkish | `tr_TR`, `tr-TR`, `tr` | LibreOffice `dict-tr` | +| Indonesian | `id_ID`, `id-ID`, `id`, `in_ID` | LibreOffice `dict-id` | | Spanish (Spain) | `es_ES`, `es-ES`, `es_ANY`, `es` | LibreOffice `dict-es` (RLA-ES) | | Spanish (Mexico) | `es_MX`, `es-MX`, `es_419`, `es-419`, `es_ANY`, `es` | LibreOffice `dict-es` (RLA-ES) | | Spanish (Latin America, and the other Spanish regions of the Americas) | `es_419`, `es-419`, `es_ANY`, `es`, `es_US`, `es_MX`, `es_AR`, `es-AR` | LibreOffice `dict-es` (RLA-ES) | @@ -139,6 +140,7 @@ Never commit dictionary files to this repository: they stay on the user's system | Date | Change | Summary | |---|---|---| | 2026-10-22 | minor | Spanish (#3360, #3553): the file names for Spain, Mexico, Latin America and Uruguay and their fallbacks | +| 2026-10-22 | trivial | Italian, Croatian, Turkish, Russian, Czech and Indonesian without a dictionary now show **(no dictionary)** (or **(dictionary unreadable)**) too, as this page already said; before, only Polish and Portuguese did. Indonesian is listed with its file names (#3150) | | 2026-10-18 | trivial | ISO 8859-1 (`SET ISO8859-1`) dictionaries are read, so the original VERO `pt_BR` works without converting it to UTF-8 (#2824) | | 2026-10-18 | minor | An installed dictionary that can't be read shows **(dictionary unreadable)** in the status bar, with the file and reason in its tooltip and in File › Options › Proofing (#2830) | | 2026-10-16 | major | First version: the file names per language, the search order and folders on Windows, macOS and Linux, installing `pt_BR` on each, and how to check it works | diff --git a/docs/typing-parity.md b/docs/typing-parity.md index df0fb9a22..5f8d61472 100644 --- a/docs/typing-parity.md +++ b/docs/typing-parity.md @@ -187,8 +187,9 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin `İngilizce`, `ılık` → `Ilık`; observed in Word for Mac with Turkish proofing). Lowercasing `İ` gives the single letter `i` in every language, never `i` + combining dot. Other languages keep the Unicode default (`istanbul` → `Istanbul` in English) - (`autocorrect_capitalisation_follows_turkic_case_mapping`). Format > Change Case is not yet - Turkish-aware. + (`autocorrect_capitalisation_follows_turkic_case_mapping`); Format > Change Case follows the + same Turkic mapping from each run's language, while other languages keep the Unicode default + (`change_case_follows_turkic_case_mapping`, #3479). - Names of days are capitalised (`monday` → `Monday`). AutoCorrect Options (#412, `tools.autocorrect`, saved per user) switch each of these off: replacements, sentence and table-cell capitals, days, smart quotes, fractions, ordinals, dashes, links, bulleted and diff --git a/packaging/linux/package.sh b/packaging/linux/package.sh index 4b9923bac..25c1b7e57 100755 --- a/packaging/linux/package.sh +++ b/packaging/linux/package.sh @@ -99,6 +99,34 @@ fi # ---- AppImage ----------------------------------------------------------------------------------- if has appimage; then + APPIMAGETOOL_VERSION=1.9.1 + # appimagetool would otherwise embed type2-runtime's `continuous` build, unchecked. + RUNTIME_VERSION=20251108 + case "$ARCH" in + x86_64) + APPIMAGETOOL_SHA256=ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 + RUNTIME_SHA256=2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d + ;; + aarch64) + APPIMAGETOOL_SHA256=f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158 + RUNTIME_SHA256=00cbdfcf917cc6c0ff6d3347d59e0ca1f7f45a6df1a428a0d6d8a78664d87444 + ;; + *) APPIMAGETOOL_SHA256='' RUNTIME_SHA256='' ;; + esac + if [[ -z "$RUNTIME_SHA256" ]]; then + echo "no pinned AppImage runtime for $ARCH" >&2 + exit 2 + fi + # Downloads $1 to $3 once and checks it against SHA-256 $2, the cached copy again on every run. + fetch_pinned() { + if [[ ! -x "$3" ]]; then + curl --proto '=https' -fsSL -o "$3.download" "$1" + printf '%s %s\n' "$2" "$3.download" | sha256sum -c - + chmod 755 "$3.download" + mv "$3.download" "$3" + fi + printf '%s %s\n' "$2" "$3" | sha256sum -c - + } APPDIR="$WORK/WordCraft.AppDir" cp -R "$STAGE" "$APPDIR" mv "$APPDIR/usr/share/doc" "$WORK/doc-unused" @@ -109,17 +137,23 @@ if has appimage; then TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}" if [[ -z "$TOOL" ]]; then - TOOL="$CARGO_TARGET_DIR/appimagetool-$ARCH.AppImage" - if [[ ! -x "$TOOL" ]]; then - curl --proto '=https' -fsSL -o "$TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage" - chmod +x "$TOOL" + if [[ -z "$APPIMAGETOOL_SHA256" ]]; then + echo "no pinned appimagetool for $ARCH; set APPIMAGETOOL or put appimagetool on PATH" >&2 + exit 2 fi + TOOL="$CARGO_TARGET_DIR/appimagetool-$APPIMAGETOOL_VERSION-$ARCH.AppImage" + fetch_pinned "https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-$ARCH.AppImage" \ + "$APPIMAGETOOL_SHA256" "$TOOL" fi + RUNTIME="$CARGO_TARGET_DIR/appimage-runtime-$RUNTIME_VERSION-$ARCH" + fetch_pinned "https://github.com/AppImage/type2-runtime/releases/download/$RUNTIME_VERSION/runtime-$ARCH" \ + "$RUNTIME_SHA256" "$RUNTIME" # Absolute, because appimagetool runs in $DIST below (CARGO_TARGET_DIR or APPIMAGETOOL may be # relative, e.g. target/agent-). OUT="$(cd "$DIST" && pwd)/$BASENAME.AppImage" APPDIR="$(cd "$APPDIR" && pwd)" TOOL="$(cd "$(dirname "$TOOL")" && pwd)/$(basename "$TOOL")" + RUNTIME="$(cd "$(dirname "$RUNTIME")" && pwd)/$(basename "$RUNTIME")" # A .zsync left by an earlier run would hide a missing zsyncmake and describe another file. rm -f "$OUT.zsync" # Update information: AppImageUpdate, AppImageLauncher and the like read it from the file and @@ -128,10 +162,11 @@ if has appimage; then # pre-release. A fork's builds point at its own releases through GITHUB_REPOSITORY. REPO="${GITHUB_REPOSITORY:-storytold/wordcraft}" UPDATE_INFO="gh-releases-zsync|${REPO%%/*}|${REPO#*/}|latest|wordcraft-*-linux-$ARCH.AppImage.zsync" - # Extract-and-run: works without FUSE (containers, CI). The output embeds the static runtime, - # so users don't need libfuse2 either. With zsyncmake on the host (CI installs the zsync + # Extract-and-run: works without FUSE (containers, CI). The output embeds the pinned static + # runtime, so users don't need libfuse2 either. With zsyncmake on the host (CI installs the zsync # package) appimagetool also writes the .zsync, into its working directory, hence the cd. - (cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream -u "$UPDATE_INFO" "$APPDIR" "$OUT") + (cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream \ + --runtime-file "$RUNTIME" -u "$UPDATE_INFO" "$APPDIR" "$OUT") echo "wrote $OUT" if [[ -s "$OUT.zsync" ]]; then echo "wrote $OUT.zsync" diff --git a/packaging/windows/sign.ps1 b/packaging/windows/sign.ps1 index 697c59c68..df3b65332 100644 --- a/packaging/windows/sign.ps1 +++ b/packaging/windows/sign.ps1 @@ -56,8 +56,8 @@ if (-not $haveCert -and -not $haveAzure) { $script:SignTool = Find-SignTool $common = @('sign', '/v', '/fd', 'SHA256', '/td', 'SHA256', '/d', 'WordCraft', '/du', 'https://github.com/storytold/wordcraft') -$tmp = Join-Path ([IO.Path]::GetTempPath()) "wordcraft-sign-$PID" -New-Item -ItemType Directory -Force -Path $tmp | Out-Null +$tmp = Join-Path ([IO.Path]::GetTempPath()) "wordcraft-sign-$([IO.Path]::GetRandomFileName())" +New-Item -ItemType Directory -Path $tmp | Out-Null try { if ($haveCert) { @@ -73,8 +73,15 @@ try { Write-Output "Signing with Azure Trusted Signing ($env:AZURE_SIGNING_ACCOUNT / $env:AZURE_CERT_PROFILE): $($Files -join ', ')" # The dlib authenticates with DefaultAzureCredential, which reads AZURE_TENANT_ID, # AZURE_CLIENT_ID and AZURE_CLIENT_SECRET from the environment. + $trustedSigningVersion = '1.0.95' + $trustedSigningSha256 = '3bfcf1e0a3cb42af1692f0a8ed45c15de070c2de86f28a59b2795d904d8a920f' $pkg = Join-Path $tmp 'trusted-signing.zip' - Invoke-WebRequest -Uri 'https://www.nuget.org/api/v2/package/Microsoft.Trusted.Signing.Client' -OutFile $pkg + $uri = "https://api.nuget.org/v3-flatcontainer/microsoft.trusted.signing.client/$trustedSigningVersion/microsoft.trusted.signing.client.$trustedSigningVersion.nupkg" + Invoke-WebRequest -Uri $uri -OutFile $pkg + $actualHash = (Get-FileHash -Path $pkg -Algorithm SHA256).Hash.ToLowerInvariant() + if ($actualHash -ne $trustedSigningSha256) { + throw "Microsoft.Trusted.Signing.Client $trustedSigningVersion checksum mismatch" + } Expand-Archive -Path $pkg -DestinationPath (Join-Path $tmp 'client') -Force $dlib = Get-ChildItem -Path (Join-Path $tmp 'client') -Recurse -Filter Azure.CodeSigning.Dlib.dll | Where-Object { $_.FullName -match '\\x64\\' } | Select-Object -First 1