Proofing: one language table instead of nine lists

lib.rs named every dictionary language in nine places: reloading for a new
folder, the installed list, load errors, the missing-dictionary hint, the
loaded check, the spelling message, spelling, suggestions and grammar. The
lists had drifted. dictionary_load_error had no Russian or Czech entry, so a
Russian or Czech dictionary that failed to load showed no reason in File ›
Options › Proofing, though the status bar, which reads another list, said
why.

LANGUAGES holds one row per language: its dictionary slot, its spelling
message, and its spelling, suggestion and grammar functions. The nine
functions read the row, so a language now joins with its module, its
ProofLang and one row. The per-language reload_dictionary and
installed_path wrappers had no callers left, inside the crate or out, and
are gone.

Tests: every_dictionary_language_reports_its_load_error, which fails for
Russian on the old code, and the_language_table_covers_the_proofed_languages
(each ProofLang::PROOFED language but English has exactly one row). Proof
285, engine 927 and layout 501 (lib).
This commit is contained in:
VoiceLessQ
2026-10-24 19:55:43 -01:00
parent e7eda2aff0
commit 9a8f4669fa
11 changed files with 208 additions and 231 deletions
+1 -13
View File
@@ -18,7 +18,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["cs_CZ", "cs-CZ", "cs"];
/// The Czech dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Czech");
pub(crate) static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Czech");
/// The Czech dictionary: the installed Hunspell `cs_CZ` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available (Czech spelling is off
@@ -33,18 +33,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again (after
/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Czech dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is a Czech dictionary available (spelling is checked)?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()
+4 -19
View File
@@ -25,10 +25,10 @@ use crate::slot::DictSlot;
pub use suggest::suggest;
/// The dictionary of each variety, found on first use (see [`DictSlot`]).
static SPAIN_DICT: DictSlot<AffixDict> = DictSlot::new(SPAIN, "Spanish (Spain)");
static MEXICO_DICT: DictSlot<AffixDict> = DictSlot::new(MEXICO, "Spanish (Mexico)");
static LATIN_AMERICA_DICT: DictSlot<AffixDict> = DictSlot::new(LATIN_AMERICA, "Spanish (Latin America)");
static URUGUAY_DICT: DictSlot<AffixDict> = DictSlot::new(URUGUAY, "Spanish (Uruguay)");
pub(crate) static SPAIN_DICT: DictSlot<AffixDict> = DictSlot::new(SPAIN, "Spanish (Spain)");
pub(crate) static MEXICO_DICT: DictSlot<AffixDict> = DictSlot::new(MEXICO, "Spanish (Mexico)");
pub(crate) static LATIN_AMERICA_DICT: DictSlot<AffixDict> = DictSlot::new(LATIN_AMERICA, "Spanish (Latin America)");
pub(crate) static URUGUAY_DICT: DictSlot<AffixDict> = DictSlot::new(URUGUAY, "Spanish (Uruguay)");
/// File names of a Spanish Hunspell dictionary for Spain, most specific first (`es_ANY` is the
/// generic one RLA-ES publishes).
@@ -72,21 +72,6 @@ pub fn install_dictionary(lang: ProofLang, aff: &str, dic: &str) -> Result<(), S
slot(lang).install(aff, dic)
}
/// Forget every variety's loaded dictionary, so the next check looks for an installed one again
/// (after [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
for slot in [&SPAIN_DICT, &MEXICO_DICT, &LATIN_AMERICA_DICT, &URUGUAY_DICT] {
slot.reload();
}
}
/// The `.dic` file of the Hunspell dictionary `lang`'s variety finds in the dictionaries folder
/// or the system locations now (File › Options › Proofing shows it); `None` when there is none,
/// or when one given with [`install_dictionary`] is in use.
pub fn installed_path(lang: ProofLang) -> Option<std::path::PathBuf> {
slot(lang).installed_path()
}
/// Is a dictionary available for `lang`'s variety (spelling is checked)?
pub fn has_dictionary(lang: ProofLang) -> bool {
slot(lang).has_dictionary()
+1 -13
View File
@@ -20,7 +20,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["hr_HR", "hr-HR", "hr"];
/// The Croatian dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Croatian");
pub(crate) static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Croatian");
/// The Croatian dictionary: the installed Hunspell `hr_HR` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available (Croatian spelling is off
@@ -35,18 +35,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again (after
/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Croatian dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is a Croatian dictionary available (spelling is checked)?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()
+1 -13
View File
@@ -21,7 +21,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["id_ID", "id-ID", "id", "in_ID"];
/// The Indonesian dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Indonesian");
pub(crate) static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Indonesian");
/// The Indonesian dictionary: the installed Hunspell `id_ID` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available (Indonesian spelling is off
@@ -36,18 +36,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again (after
/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Indonesian dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is an Indonesian dictionary available (spelling is checked)?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()
+1 -13
View File
@@ -29,7 +29,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["it_IT", "it_CH", "it-IT", "it"];
/// The Italian dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Italian");
pub(crate) static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Italian");
/// The Italian dictionary: the installed Hunspell `it_IT` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available (Italian spelling is off
@@ -44,18 +44,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again (after
/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Italian dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is an Italian dictionary available (spelling is checked)?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()
+165 -109
View File
@@ -159,57 +159,150 @@ pub fn ignore_word(w: &str) {
}
}
/// A language proofed against an installed Hunspell dictionary: all the functions below need to
/// know about it, so a language joins with one row in [`LANGUAGES`].
struct Language {
lang: ProofLang,
slot: &'static dyn slot::Slot,
/// The spelling mark's message, in the language.
message: &'static str,
is_correct: fn(&str, ProofLang, &Options) -> bool,
suggest: fn(&str, usize, ProofLang) -> Vec<String>,
/// Grammar issues; the flag says whether the first word must have a capital (not in a list
/// item).
grammar: fn(&str, ProofLang, bool) -> Vec<Issue>,
}
/// Every language proofed against a Hunspell dictionary, in the order File › Options › Proofing
/// lists them. English has a built-in word list and is handled apart.
static LANGUAGES: [Language; 12] = [
Language {
lang: ProofLang::Pl,
slot: &pl::DICT,
message: "Możliwy błąd pisowni",
is_correct: |w, _, o| pl::is_correct_with_options(w, o),
suggest: |w, n, _| pl::suggest(w, n),
grammar: |t, _, c| pl::grammar::check_with(t, c),
},
Language {
lang: ProofLang::Pt,
slot: &pt::DICT,
message: "Possível erro de ortografia",
is_correct: |w, _, o| pt::is_correct_with_options(w, o),
suggest: |w, n, _| pt::suggest(w, n),
grammar: |t, _, c| pt::grammar::check_with(t, c),
},
Language {
lang: ProofLang::It,
slot: &it::DICT,
message: "Possibile errore di ortografia",
is_correct: |w, _, o| it::is_correct_with_options(w, o),
suggest: |w, n, _| it::suggest(w, n),
grammar: |t, _, c| it::grammar::check_with(t, c),
},
Language {
lang: ProofLang::Hr,
slot: &hr::DICT,
message: "Moguća pravopisna pogreška",
is_correct: |w, _, o| hr::is_correct_with_options(w, o),
suggest: |w, n, _| hr::suggest(w, n),
grammar: |t, _, c| hr::grammar::check_with(t, c),
},
Language {
lang: ProofLang::Tr,
slot: &tr::DICT,
message: "Olası yazım hatası",
is_correct: |w, _, o| tr::is_correct_with_options(w, o),
suggest: |w, n, _| tr::suggest(w, n),
grammar: |t, _, c| tr::grammar::check_with(t, c),
},
Language {
lang: ProofLang::Ru,
slot: &ru::DICT,
message: "Возможна орфографическая ошибка",
is_correct: |w, _, o| ru::is_correct_with_options(w, o),
suggest: |w, n, _| ru::suggest(w, n),
grammar: |t, _, c| ru::grammar::check_with(t, c),
},
// Czech, Indonesian and Spanish have no sentence-capital rule, so a list item needs no
// exception.
Language {
lang: ProofLang::Cs,
slot: &cs::DICT,
message: "Možná pravopisná chyba",
is_correct: |w, _, o| cs::is_correct_with_options(w, o),
suggest: |w, n, _| cs::suggest(w, n),
grammar: |t, _, _| cs::grammar::check(t),
},
Language {
lang: ProofLang::Id,
slot: &id::DICT,
message: "Kemungkinan salah eja",
is_correct: |w, _, o| id::is_correct_with_options(w, o),
suggest: |w, n, _| id::suggest(w, n),
grammar: |t, _, _| id::grammar::check(t),
},
Language {
lang: ProofLang::Es,
slot: &es::SPAIN_DICT,
message: "Posible error ortográfico",
is_correct: es::is_correct_with_options,
suggest: es::suggest,
grammar: |t, l, _| es::grammar::check(t, l),
},
Language {
lang: ProofLang::EsMx,
slot: &es::MEXICO_DICT,
message: "Posible error ortográfico",
is_correct: es::is_correct_with_options,
suggest: es::suggest,
grammar: |t, l, _| es::grammar::check(t, l),
},
Language {
lang: ProofLang::Es419,
slot: &es::LATIN_AMERICA_DICT,
message: "Posible error ortográfico",
is_correct: es::is_correct_with_options,
suggest: es::suggest,
grammar: |t, l, _| es::grammar::check(t, l),
},
Language {
lang: ProofLang::EsUy,
slot: &es::URUGUAY_DICT,
message: "Posible error ortográfico",
is_correct: es::is_correct_with_options,
suggest: es::suggest,
grammar: |t, l, _| es::grammar::check(t, l),
},
];
/// `lang`'s row in [`LANGUAGES`]; `None` for English and the languages WordCraft doesn't proof.
fn language(lang: ProofLang) -> Option<&'static Language> {
LANGUAGES.iter().find(|l| l.lang == lang)
}
/// Search `dir` for Hunspell dictionaries before the system locations (File › Options ›
/// Proofing › Dictionaries folder, #1063); `None` clears it. The dictionaries load again from
/// the new search path, and text is checked again.
pub fn set_dictionaries_folder(dir: Option<std::path::PathBuf>) {
hunspell::set_dictionary_dir(dir);
pl::reload_dictionary();
pt::reload_dictionary();
it::reload_dictionary();
hr::reload_dictionary();
tr::reload_dictionary();
ru::reload_dictionary();
cs::reload_dictionary();
id::reload_dictionary();
es::reload_dictionary();
for l in &LANGUAGES {
l.slot.reload();
}
DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed);
}
/// Each language that reads an installed Hunspell dictionary, with the `.dic` file found for it
/// now (`None`: not installed, so the language has no spelling marks).
pub fn installed_dictionaries() -> Vec<(ProofLang, Option<std::path::PathBuf>)> {
vec![
(ProofLang::Pl, pl::installed_path()),
(ProofLang::Pt, pt::installed_path()),
(ProofLang::It, it::installed_path()),
(ProofLang::Hr, hr::installed_path()),
(ProofLang::Tr, tr::installed_path()),
(ProofLang::Ru, ru::installed_path()),
(ProofLang::Cs, cs::installed_path()),
(ProofLang::Id, id::installed_path()),
(ProofLang::Es, es::installed_path(ProofLang::Es)),
(ProofLang::EsMx, es::installed_path(ProofLang::EsMx)),
(ProofLang::Es419, es::installed_path(ProofLang::Es419)),
(ProofLang::EsUy, es::installed_path(ProofLang::EsUy)),
]
LANGUAGES.iter().map(|l| (l.lang, l.slot.installed_path())).collect()
}
/// Why the dictionary found for `lang` failed to load (unsupported encoding, too large, a parse
/// error), if it was read and failed. Doesn't read anything itself: a dictionary nobody has
/// checked yet reports `None` (#2830).
pub fn dictionary_load_error(lang: ProofLang) -> Option<String> {
let key = match lang {
ProofLang::Pl => "pl_PL",
ProofLang::Pt => "pt_BR",
ProofLang::It => "it_IT",
ProofLang::Hr => "hr_HR",
ProofLang::Tr => "tr_TR",
ProofLang::Id => "id_ID",
ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => es::dictionary_name(lang),
_ => return None,
};
hunspell::load_error(key).map(|(_, reason)| reason)
hunspell::load_error(language(lang)?.slot.name()).map(|(_, reason)| reason)
}
/// A number that changes whenever a word is added to or removed from the user dictionary, or the
@@ -440,36 +533,13 @@ pub fn unreadable_dictionary(lang: Option<&str>) -> Option<(&'static str, std::p
/// 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,
})
language(lang).map(|l| l.slot.has_dictionary())
}
/// 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,
}
language(lang).filter(|_| !installed).map(|l| l.slot.name())
}
/// Is `word` spelled correctly in the run's proofing language? A language without a dictionary
@@ -739,18 +809,7 @@ fn spelling_issue(text: &str, a: usize, b: usize, lang: ProofLang, opts: &Option
if (opts.ignore_internet && internet) || (lang.is_english() && vi::is_vietnamese_word(w)) || is_correct_in_with(w, lang, opts) {
return None;
}
let message = match lang {
ProofLang::Pl => "Możliwy błąd pisowni",
ProofLang::Hr => "Moguća pravopisna pogreška",
ProofLang::Pt => "Possível erro de ortografia",
ProofLang::It => "Possibile errore di ortografia",
ProofLang::Tr => "Olası yazım hatası",
ProofLang::Ru => "Возможна орфографическая ошибка",
ProofLang::Cs => "Možná pravopisná chyba",
ProofLang::Id => "Kemungkinan salah eja",
ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => "Posible error ortográfico",
_ => "Possible spelling mistake",
};
let message = language(lang).map_or("Possible spelling mistake", |l| l.message);
Some(Issue { start: a, end: b, kind: IssueKind::Spelling, message: message.into(), suggestions: Vec::new(), lang })
}
@@ -774,40 +833,21 @@ fn composed(word: &str) -> std::borrow::Cow<'_, str> {
/// checked as its composed form ([`composed`]).
pub fn is_correct_in_with(word: &str, lang: ProofLang, opts: &Options) -> bool {
let word = &*composed(word);
match lang {
ProofLang::En | ProofLang::EnGb | ProofLang::EnCa => is_correct_english(word, lang, opts),
ProofLang::Pl => accepted_by_options(word, opts) || pl::is_correct_with_options(word, opts),
ProofLang::Hr => accepted_by_options(word, opts) || hr::is_correct_with_options(word, opts),
ProofLang::Pt => accepted_by_options(word, opts) || pt::is_correct_with_options(word, opts),
ProofLang::It => accepted_by_options(word, opts) || it::is_correct_with_options(word, opts),
ProofLang::Tr => accepted_by_options(word, opts) || tr::is_correct_with_options(word, opts),
ProofLang::Ru => accepted_by_options(word, opts) || ru::is_correct_with_options(word, opts),
ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => {
accepted_by_options(word, opts) || es::is_correct_with_options(word, lang, opts)
}
ProofLang::Cs => accepted_by_options(word, opts) || cs::is_correct_with_options(word, opts),
ProofLang::Id => accepted_by_options(word, opts) || id::is_correct_with_options(word, opts),
ProofLang::Bg | ProofLang::De | ProofLang::Other => true,
if lang.is_english() {
return is_correct_english(word, lang, opts);
}
// A language WordCraft doesn't proof (Bulgarian, German, any other) has no spelling marks.
language(lang).is_none_or(|l| accepted_by_options(word, opts) || (l.is_correct)(word, lang, opts))
}
/// Spelling suggestions for a word misspelled in `lang` (best first, at most `max`), composed
/// ([`composed`]) like the dictionaries' words.
pub fn suggest_in(word: &str, max: usize, lang: ProofLang) -> Vec<String> {
let word = &*composed(word);
match lang {
ProofLang::En | ProofLang::EnGb | ProofLang::EnCa => suggest_english(word, max, lang),
ProofLang::Pl => pl::suggest(word, max),
ProofLang::Hr => hr::suggest(word, max),
ProofLang::Pt => pt::suggest(word, max),
ProofLang::It => it::suggest(word, max),
ProofLang::Tr => tr::suggest(word, max),
ProofLang::Ru => ru::suggest(word, max),
ProofLang::Cs => cs::suggest(word, max),
ProofLang::Id => id::suggest(word, max),
ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => es::suggest(word, max, lang),
ProofLang::Bg | ProofLang::De | ProofLang::Other => Vec::new(),
if lang.is_english() {
return suggest_english(word, max, lang);
}
language(lang).map_or_else(Vec::new, |l| (l.suggest)(word, max, lang))
}
/// Grammar issues in a paragraph of text in `lang`: the English rules, the Polish ones
@@ -824,18 +864,7 @@ fn grammar_in(text: &str, lang: ProofLang, capital_first: bool) -> Vec<Issue> {
match lang {
ProofLang::En => grammar_en(text, capital_first),
ProofLang::EnGb | ProofLang::EnCa => grammar_en(text, capital_first).into_iter().map(|i| Issue { lang, ..i }).collect(),
ProofLang::Pl => pl::grammar::check_with(text, capital_first),
ProofLang::Hr => hr::grammar::check_with(text, capital_first),
ProofLang::Pt => pt::grammar::check_with(text, capital_first),
ProofLang::It => it::grammar::check_with(text, capital_first),
ProofLang::Tr => tr::grammar::check_with(text, capital_first),
ProofLang::Ru => ru::grammar::check_with(text, capital_first),
// Czech has no sentence-capital rule, so a list item needs no exception.
ProofLang::Cs => cs::grammar::check(text),
ProofLang::Id => id::grammar::check(text),
// Spanish has no sentence-capital rule either.
ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => es::grammar::check(text, lang),
ProofLang::Bg | ProofLang::De | ProofLang::Other => Vec::new(),
_ => language(lang).map_or_else(Vec::new, |l| (l.grammar)(text, lang, capital_first)),
}
}
@@ -1133,6 +1162,33 @@ mod tests {
assert!(is_correct_in("дом", ProofLang::Ru) && !is_correct_in("дум", ProofLang::Ru));
}
/// A dictionary that fails to load says why in File › Options › Proofing in every language
/// that reads one; Russian and Czech were missing from the list that named their dictionaries
/// there.
#[test]
fn every_dictionary_language_reports_its_load_error() {
let _guard = DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
for (lang, _) in installed_dictionaries() {
let name = dictionary_needed(lang, false).unwrap();
let reason = format!("zq unreadable {name}");
let _ = hunspell::note_load::<()>(name, std::path::Path::new("zq.dic"), Err(reason.clone()));
assert_eq!(dictionary_load_error(lang), Some(reason), "{lang:?}");
hunspell::clear_load_error(name);
}
}
/// Every proofed language other than English has a row in the language table, and only those.
#[test]
fn the_language_table_covers_the_proofed_languages() {
for l in ProofLang::PROOFED {
assert_eq!(language(l).is_some(), !l.is_english(), "{l:?}");
}
for (i, row) in LANGUAGES.iter().enumerate() {
assert!(row.lang.is_proofed(), "{:?}", row.lang);
assert!(LANGUAGES.iter().skip(i + 1).all(|other| other.lang != row.lang), "{:?} twice", row.lang);
}
}
/// #1741: a list item may be a fragment, so its first word needs no capital in any language
/// with the rule; a plain paragraph's does, and later sentences in the item are still checked.
#[test]
+1 -13
View File
@@ -28,7 +28,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["pl_PL", "pl-PL", "pl"];
/// The Polish dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Polish");
pub(crate) static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Polish");
/// The Polish dictionary: the installed Hunspell `pl_PL` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available (Polish spelling is off
@@ -43,18 +43,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again (after
/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Polish dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is a Polish dictionary available (spelling is checked)?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()
+1 -13
View File
@@ -27,7 +27,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["pt_BR", "pt-BR", "pt"];
/// The Portuguese dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Portuguese");
pub(crate) static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Portuguese");
/// The Portuguese dictionary: the installed Hunspell `pt_BR` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available (Portuguese spelling is off
@@ -42,18 +42,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again (after
/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Portuguese dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is a Portuguese dictionary available (spelling is checked)?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()
+1 -13
View File
@@ -39,7 +39,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["ru_RU", "ru-RU", "ru"];
/// The Russian dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Russian");
pub(crate) static DICT: DictSlot<AffixDict> = DictSlot::new(NAMES, "Russian");
/// The Russian dictionary: the installed Hunspell `ru_RU` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available (Russian spelling is off
@@ -54,18 +54,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again (after
/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed).
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Russian dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is a Russian dictionary available (spelling is checked)?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()
+31
View File
@@ -30,6 +30,37 @@ impl Dictionary for AffixDict {
}
}
/// What every language's slot answers, whatever its dictionary type (the language table in
/// lib.rs holds them side by side).
pub(crate) trait Slot: Sync {
/// See [`DictSlot::name`].
fn name(&self) -> &'static str;
/// See [`DictSlot::installed_path`].
fn installed_path(&self) -> Option<PathBuf>;
/// See [`DictSlot::has_dictionary`].
fn has_dictionary(&self) -> bool;
/// See [`DictSlot::reload`].
fn reload(&self);
}
impl<D: Dictionary> Slot for DictSlot<D> {
fn name(&self) -> &'static str {
DictSlot::name(self)
}
fn installed_path(&self) -> Option<PathBuf> {
DictSlot::installed_path(self)
}
fn has_dictionary(&self) -> bool {
DictSlot::has_dictionary(self)
}
fn reload(&self) {
DictSlot::reload(self);
}
}
/// The dictionary in use and whether it came from [`DictSlot::install`] rather than a file on
/// disk (it then has no file to report, even if a matching file also happens to exist): one lock,
/// so a reload and an install can't interleave their two fields.
+1 -12
View File
@@ -29,7 +29,7 @@ pub use suggest::suggest;
const NAMES: &[&str] = &["tr_TR", "tr-TR", "tr"];
/// The Turkish dictionary, found on first use (see [`DictSlot`]).
static DICT: DictSlot<TrDict> = DictSlot::new(NAMES, "Turkish");
pub(crate) static DICT: DictSlot<TrDict> = DictSlot::new(NAMES, "Turkish");
/// The Turkish dictionary: the installed Hunspell `tr_TR` (read on first use), one given with
/// [`install_dictionary`], or an empty one when none is available.
@@ -43,17 +43,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> {
DICT.install(aff, dic)
}
/// Forget the loaded dictionary, so the next check looks for an installed one again.
pub fn reload_dictionary() {
DICT.reload();
}
/// The `.dic` file of the installed Turkish dictionary (File › Options › Proofing shows it);
/// `None` when there is none, or when one given with [`install_dictionary`] is in use.
pub fn installed_path() -> Option<std::path::PathBuf> {
DICT.installed_path()
}
/// Is a Turkish dictionary available?
pub fn has_dictionary() -> bool {
DICT.has_dictionary()