From e23d383b27c879a308f5acad0adf64f5914d5068 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 23 Oct 2026 17:01:48 -0400 Subject: [PATCH 1/4] ci(sonar): don't cancel superseded runs on main, so coverage is published (#4070) --- .github/workflows/sonarcloud.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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: From 7086f8a425862c46ff186f131eea60a3be9eeecf Mon Sep 17 00:00:00 2001 From: aiongg <67543387+aiongg@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:03:53 -0400 Subject: [PATCH 2/4] Packaging: pin the signing client and appimagetool, and check their hashes (#3326) * Packaging: pin the Windows signing client and check its hash * Packaging: pin appimagetool and check its hash * Packaging: pin the AppImage runtime and stop clearly when an architecture has no pinned tool * Packaging: empty hashes are quoted --- packaging/linux/package.sh | 49 ++++++++++++++++++++++++++++++++------ packaging/windows/sign.ps1 | 13 +++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) 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 From 3ed7fe977c1e624c08e2c3bf91c79ea357024fb8 Mon Sep 17 00:00:00 2001 From: SlavenDj <53652906+SlavenDj@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:14:34 +0200 Subject: [PATCH 3/4] Change Case follows each run's language for Turkic casing (#3479) (#3480) Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> --- crates/engine/src/cmd/format.rs | 38 ++++++++++++++++++++++------- crates/engine/src/cmd/tools.rs | 6 ++--- crates/engine/src/tests_typing.rs | 40 +++++++++++++++++++++++++++++++ docs/typing-parity.md | 5 ++-- 4 files changed, 75 insertions(+), 14 deletions(-) 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/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 From 67a0e7231f2654a25cff23a8b43bd78a41b6e80d Mon Sep 17 00:00:00 2001 From: naxvog Date: Fri, 23 Oct 2026 23:24:46 +0200 Subject: [PATCH 4/4] =?UTF-8?q?Options=20=E2=80=BA=20Language=20names=20th?= =?UTF-8?q?e=20proofing=20language=20at=20the=20caret=20(#3150)=20(#3177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The note under "Proofing language" always said English (United States). It now names the language at the caret, as the status bar does, and the 22 catalogs carry the new sentence. Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> --- crates/proof/src/cs/mod.rs | 3 +- crates/proof/src/lib.rs | 60 +++++++++++++++++++++-------- crates/proof/src/ru/mod.rs | 3 +- crates/ui-egui/src/chrome.rs | 29 ++++++++------ crates/ui-egui/src/i18n/bg.tsv | 4 +- crates/ui-egui/src/i18n/cs.tsv | 4 +- crates/ui-egui/src/i18n/da.tsv | 4 +- crates/ui-egui/src/i18n/de.tsv | 4 +- crates/ui-egui/src/i18n/es.tsv | 4 +- crates/ui-egui/src/i18n/et.tsv | 4 +- crates/ui-egui/src/i18n/fr.tsv | 4 +- crates/ui-egui/src/i18n/hr.tsv | 4 +- crates/ui-egui/src/i18n/hu.tsv | 4 +- crates/ui-egui/src/i18n/it.tsv | 4 +- crates/ui-egui/src/i18n/ja.tsv | 4 +- crates/ui-egui/src/i18n/ko.tsv | 4 +- crates/ui-egui/src/i18n/nl.tsv | 4 +- crates/ui-egui/src/i18n/pl.tsv | 4 +- crates/ui-egui/src/i18n/pt-br.tsv | 4 +- crates/ui-egui/src/i18n/ro.tsv | 4 +- crates/ui-egui/src/i18n/ru.tsv | 4 +- crates/ui-egui/src/i18n/sl.tsv | 4 +- crates/ui-egui/src/i18n/sr-latn.tsv | 4 +- crates/ui-egui/src/i18n/sr.tsv | 4 +- crates/ui-egui/src/i18n/sv.tsv | 4 +- crates/ui-egui/src/i18n/tr.tsv | 4 +- crates/ui-egui/src/i18n/uk.tsv | 4 +- crates/ui-egui/src/i18n/vi.tsv | 4 +- crates/ui-egui/src/i18n/zh-hans.tsv | 4 +- crates/ui-egui/src/i18n/zh-hant.tsv | 4 +- crates/ui-egui/src/options.rs | 14 +++++-- crates/ui-egui/src/ribbon.rs | 22 +++++++++-- docs/spelling-dictionaries.md | 8 ++-- 33 files changed, 179 insertions(+), 64 deletions(-) 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 6e17a6921..71f96827b 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -395,16 +395,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()), - _ => 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 @@ -412,24 +409,38 @@ 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()), - _ => 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; only Polish and Portuguese use 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(), + _ => return None, + }) +} + +/// The Hunspell name `lang` needs when `installed` is false (the first name its module looks for). 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"), _ => None, } } @@ -957,6 +968,23 @@ 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); + // 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"), + ] { + 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 05cce09db..b54861289 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2182,7 +2182,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 abf56a128..efdb43fbf 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2275,7 +2275,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 282cd76eb..3c19ed79a 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1988,7 +1988,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 af9a50d5f..7d981fe84 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 7b60c0b59..791ce145b 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 c6687355d..49473d746 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 3d451f0e6..f67633ff8 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1939,7 +1939,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 e4ed78889..26befcc57 100644 --- a/crates/ui-egui/src/i18n/hr.tsv +++ b/crates/ui-egui/src/i18n/hr.tsv @@ -2100,7 +2100,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 1605bc293..fb93810d8 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 5f02a42ae..e3c22564e 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 73658303d..5e93670f1 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 9cbce2bd4..2da03818f 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 566948975..0b58facd4 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1988,7 +1988,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 0235c84ae..453948dad 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2053,7 +2053,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 5d1d4a285..b7762e21f 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 20b9ba982..284a7838f 100644 --- a/crates/ui-egui/src/i18n/ro.tsv +++ b/crates/ui-egui/src/i18n/ro.tsv @@ -2220,7 +2220,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 bfd4765dc..573ebd48e 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2481,7 +2481,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 c540fd514..a4605b730 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -2192,7 +2192,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 9cdd5c4e3..5967a6c80 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 c7134d6d5..156a6015d 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 f936d47c0..0dd9fa570 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 41212c0d4..bbd16c831 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2480,7 +2480,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 a5c6bc702..6f2899432 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 8fe80c88a..e988b4c59 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2175,7 +2175,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 3230fa001..ab73b632c 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 4970f8c3e..a78f4d069 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 a1f7f727b..00a053468 100644 --- a/crates/ui-egui/src/options.rs +++ b/crates/ui-egui/src/options.rs @@ -460,9 +460,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 fc43cfbcd..17eab9d2f 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -908,11 +908,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 9e90e91e0..2ca90618a 100644 --- a/docs/spelling-dictionaries.md +++ b/docs/spelling-dictionaries.md @@ -1,9 +1,9 @@ # Installing spelling dictionaries -> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-18 · **Change:** trivial (ISO 8859-1 dictionaries are read, #2824; previously major: first version) +> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-22 · **Change:** 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 and -Turkish spelling use a standard **Hunspell** dictionary (an `.aff` and a `.dic` file) that +English spelling works out of the box. Polish, Russian, Portuguese, Czech, Italian, Croatian, +Turkish and Indonesian spelling use a standard **Hunspell** dictionary (an `.aff` and a `.dic` file) that WordCraft reads at runtime. These dictionaries are not bundled: they are large, and several are GPL or LGPL licensed. Without one, the language isn't checked. The status bar then shows the language with **(no dictionary)**, for example *Português (Brasil) (no dictionary)*. @@ -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` | Every Portuguese region uses the same `pt_BR` dictionary, so text marked *Português (Portugal)* is checked with it too. @@ -127,6 +128,7 @@ Never commit dictionary files to this repository: they stay on the user's system | Date | Change | Summary | |---|---|---| +| 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 |