CP4.2: review — English never takes another dialect's dictionary or pack: each English reads its own names in the dictionaries folder, an exactly named pack and the system, else the built-in Moby list; the British-spelling test runs unconditionally again

hunspell::candidates_exact (folder → exact-locale pack → system, own names only) for English without SCOWL; packs::exact_spelling_dirs. Other languages keep the region-ordered cross-region tiers. Drops has_british_source().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2
This commit is contained in:
Martin BallandClaude Opus 5.5 committed 2026-11-01 22:34:33 +00:00
1 parent d905406fe3
commit ba11ccd855
4 files changed
+53 -34

No files matched your search

+8 -4
View File
@@ -191,13 +191,17 @@ fn key(lang: ProofLang) -> &'static str {
names(lang).first().copied().unwrap_or("en_US")
}
/// Read the first Hunspell pair of `lang`'s own that loads, trying each name in each folder
/// ([`crate::hunspell::candidates`]; with SCOWL embedded, the dictionaries folder only:
/// Read the first Hunspell pair of `lang`'s own that loads, trying each name in each folder and
/// an installed pack named exactly for it ([`crate::hunspell::candidates_exact`]: never another
/// dialect's file or pack; with SCOWL embedded, the dictionaries folder only:
/// [`crate::hunspell::user_candidates`]), with the first failure before it (the file `lang`
/// would rather use), for [`note_error`].
fn load(lang: ProofLang) -> (Loaded, Option<(PathBuf, String)>) {
let candidates =
if crate::scowl::dict(lang).is_some() { crate::hunspell::user_candidates(names(lang)) } else { crate::hunspell::candidates(names(lang)) };
let candidates = if crate::scowl::dict(lang).is_some() {
crate::hunspell::user_candidates(names(lang))
} else {
crate::hunspell::candidates_exact(names(lang))
};
if candidates.is_empty() {
log::info!("English spelling: no Hunspell {} dictionary to read first; using SCOWL or the built-in word list", key(lang));
return (None, None);
+9
View File
@@ -199,6 +199,15 @@ fn ordered(names: &[&str], dirs: &[PathBuf], region_packs: &[PathBuf], system: &
out
}
/// [`candidates`] without another region's or dialect's dictionary: the dictionaries folders,
/// then an installed pack named exactly by one of `names` (`en_GB` → `en-gb`), then the system's
/// folders, `names` only. English uses it: en-GB never falls back to an `en_US` file or pack.
pub fn candidates_exact(names: &[&str]) -> Vec<(PathBuf, PathBuf)> {
let lo = names.first().copied().map(lo_ext_dirs);
let (dirs, system) = search_tiers(lo.as_deref());
ordered(names, &dirs, &crate::packs::exact_spelling_dirs(names), &system, &[])
}
/// [`candidates`] in the folders the user keeps dictionaries in for WordCraft ([`user_dirs`])
/// only, not the system locations.
pub fn user_candidates(names: &[&str]) -> Vec<(PathBuf, PathBuf)> {
+17 -30
View File
@@ -1321,6 +1321,10 @@ mod tests {
/// installed English Language pack, then the system's Hunspell, then the built-in list.
#[test]
fn an_english_pack_comes_after_the_dictionaries_folder() {
// With SCOWL embedded, English reads only the dictionaries folder before it.
if scowl_embedded() {
return;
}
let _guard = DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
set_dictionaries_folder(Some(shared_test_folder()));
let root = std::env::temp_dir().join(format!("wc-proof-en-pack-{}", std::process::id()));
@@ -1328,24 +1332,24 @@ mod tests {
let (dir, packs) = (root.join("dictionaries"), root.join("language-packs"));
let aff: &[u8] = b"SET UTF-8\nSFX S Y 1\nSFX S 0 s .\n";
let pack = write_pack(&packs, "en-us", &[("spelling.aff", aff), ("spelling.dic", b"2\nzzpackword\nhouse/S\n")]);
let gb_pack = write_pack(&packs, "en-gb", &[("spelling.aff", aff), ("spelling.dic", b"2\nzzgbword\nhouse/S\n")]);
packs::set_packs_dir(Some(packs.clone()));
let english: &[(&str, &[u8], &[u8])] = &[("en_US", aff, b"2\nzzfolderword\nhouse/S\n")];
folder_with_english(&dir, english);
if english_data() {
assert_eq!(english_source(ProofLang::En), Some(EnglishSource::Embedded));
} else {
assert_eq!(english_source(ProofLang::En), Some(EnglishSource::Hunspell(dir.join("en_US.dic"))), "the folder first");
assert!(is_correct_in("zzfolderword", ProofLang::En) && !is_correct_in("zzpackword", ProofLang::En));
}
assert_eq!(english_source(ProofLang::En), Some(EnglishSource::Hunspell(dir.join("en_US.dic"))), "the folder first");
assert!(is_correct_in("zzfolderword", ProofLang::En) && !is_correct_in("zzpackword", ProofLang::En));
// Each English its own: the folder's en_US never stands in for en-GB, which takes its pack.
assert_eq!(english_source(ProofLang::EnGb), Some(EnglishSource::Hunspell(gb_pack.join("spelling.dic"))));
drop_english_files(&dir, english);
if !english_data() {
assert_eq!(english_source(ProofLang::En), Some(EnglishSource::Hunspell(pack.join("spelling.dic"))), "then the pack");
assert!(is_correct_in("zzpackword", ProofLang::En) && is_correct_in("houses", ProofLang::En));
}
assert_eq!(english_source(ProofLang::En), Some(EnglishSource::Hunspell(pack.join("spelling.dic"))), "then the pack");
assert!(is_correct_in("zzpackword", ProofLang::En) && is_correct_in("houses", ProofLang::En));
// Neither English takes the other dialect's pack: without its own, en-CA has none.
std::fs::remove_dir_all(&gb_pack).unwrap();
packs::reload();
assert_ne!(english_source(ProofLang::EnGb), Some(EnglishSource::Hunspell(pack.join("spelling.dic"))), "never en-us for en-GB");
assert_ne!(english_source(ProofLang::EnCa), Some(EnglishSource::Hunspell(pack.join("spelling.dic"))), "never en-us for en-CA");
packs::set_packs_dir(None);
if !english_data() {
assert_ne!(english_source(ProofLang::En), Some(EnglishSource::Hunspell(pack.join("spelling.dic"))), "a removed pack is not used");
}
assert_ne!(english_source(ProofLang::En), Some(EnglishSource::Hunspell(pack.join("spelling.dic"))), "a removed pack is not used");
set_dictionaries_folder(Some(shared_test_folder()));
let _ = std::fs::remove_dir_all(&root);
}
@@ -2253,20 +2257,6 @@ mod tests {
assert_eq!(suggest_in("programmes", 5, us).first().map(String::as_str), Some("programs"));
}
/// Does en-GB read a list with the British spellings: the Moby list (embedded or built in) or a
/// British Hunspell dictionary? Without the Language data, a machine with only a US Hunspell
/// dictionary (GitHub's Ubuntu runners) spells en-GB with it, by design, and marks `goitre`.
fn has_british_source() -> bool {
match english_source(ProofLang::EnGb) {
Some(EnglishSource::Embedded | EnglishSource::CoreList) => true,
Some(EnglishSource::Hunspell(dic)) => {
let stem = dic.file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default();
!["en_US", "en-US", "en_CA", "en-CA"].contains(&stem.as_str())
}
None => false,
}
}
/// More British spellings that must stay right in en-GB and en-AU (#1281): -re, single l, ae,
/// with their inflections.
#[test]
@@ -2275,9 +2265,6 @@ mod tests {
let _guard = crate::DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let gb = ProofLang::EnGb;
assert_eq!(ProofLang::from_tag(Some("en-AU")), gb);
if !has_british_source() {
return;
}
for w in [
"goitre",
"goitres",
+19
View File
@@ -468,6 +468,25 @@ pub(crate) fn spelling_dirs(names: &[&str]) -> (Vec<PathBuf>, Vec<PathBuf>) {
all.into_iter().partition(in_region)
}
/// The folders of the installed packs with a spelling dictionary whose locale is exactly one of
/// `names` (`en_GB` → `en-gb`), in that order: no other region, no language alone.
pub(crate) fn exact_spelling_dirs(names: &[&str]) -> Vec<PathBuf> {
if read().dir.is_none() {
return Vec::new();
}
let scan = scan();
let mut out: Vec<PathBuf> = Vec::new();
for n in names {
let l = n.to_ascii_lowercase().replace('_', "-");
if let Some((p, _)) = scan.packs.iter().find(|(p, h)| h.spelling && p.locale == l)
&& !out.contains(&p.path)
{
out.push(p.path.clone());
}
}
out
}
/// The pack locales of `names`' own region: the first name's (`pt_BR` → `pt-br`) and the language
/// alone (`pt`) when it is written in the same script.
pub(crate) fn region_locales(names: &[&str]) -> Vec<String> {