Merge origin/main into spanish-proofing

Upstream merged #3177 (dictionary hints for every Hunspell language) and #3480.
Resolve the conflicts in crates/proof/src/lib.rs by taking #3177's
dictionary_loaded/dictionary_needed structure and adding the four Spanish
varieties to it, and in docs/spelling-dictionaries.md keeping both sides.
This commit is contained in:
Federico Olivares
2026-10-23 19:06:06 -03:00
40 changed files with 312 additions and 94 deletions
+2 -1
View File
@@ -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:
+29 -9
View File
@@ -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<bool> = 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<String> {
/// 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<String> {
let chars: Vec<char> = 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<String> {
// Sentence case.
let n = if up && c.is_alphabetic() {
up = false;
c.to_uppercase().collect()
upper(c)
} else {
lower(i, c)
};
+3 -3
View File
@@ -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(),
+40
View File
@@ -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.
+2 -1
View File
@@ -42,10 +42,11 @@ pub fn dictionary() -> Arc<AffixDict> {
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()
})
+50 -23
View File
@@ -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<bool> {
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);
+2 -1
View File
@@ -63,10 +63,11 @@ pub fn dictionary() -> Arc<AffixDict> {
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()
})
+18 -11
View File
@@ -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<String> {
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);
+3 -1
View File
@@ -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 Въвежданият текст замества избрания текст
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 756 and column 23.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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
1 # Estonian (et) interface catalog. Columns separated by a TAB:
764
765
766
767
768
769
770
771
772
+3 -1
View File
@@ -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é
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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: 既定のタブ間隔:
1 # Japanese (ja) interface catalog. Same format and clean-room rules as zh-hans.tsv.
197
198
199
200
201
202
203
204
205
+3 -1
View File
@@ -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: 기본 탭 위치:
1 # Korean (ko) interface catalog. Same format and clean-room rules as zh-hans.tsv.
161
162
163
164
165
166
167
168
169
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 756 and column 23.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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 Ввод заменяет выделенный текст
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 6 and column 25.
+3 -1
View File
@@ -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: Подразумевани табулатори:
Can't render this file because it contains an unexpected character in line 11 and column 25.
+3 -1
View File
@@ -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:
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 808 and column 23.
+3 -1
View File
@@ -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: Типові позиції табуляції:
Can't render this file because it contains an unexpected character in line 6 and column 31.
+3 -1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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: 默认制表位:
Can't render this file because it contains an unexpected character in line 7 and column 25.
+3 -1
View File
@@ -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: 預設定位點間距:
1 # Traditional Chinese (zh-hant, Taiwan vocabulary) interface catalog. Same format and clean-room
198
199
200
201
202
203
204
205
206
+11 -3
View File
@@ -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) {
+19 -3
View File
@@ -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<String> {
/// 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<String> {
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).
+3 -1
View File
@@ -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 `<name>.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 |
+3 -2
View File
@@ -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
+42 -7
View File
@@ -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-<name>).
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"
+10 -3
View File
@@ -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