From 409c8facfdf9c4c57e21106c37f34a968abbe802 Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Sun, 1 Nov 2026 18:45:19 +0000 Subject: [PATCH] =?UTF-8?q?CP4.2:=20review=20=E2=80=94=20ProofLang::tag()?= =?UTF-8?q?=20names=20each=20pack=20language=20(its=20locale)=20for=20keys?= =?UTF-8?q?,=20settings=20and=20reported=20values;=20code()=20stays=20'sta?= =?UTF-8?q?tic=20and=20empty=20for=20packs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 --- crates/proof/README.md | 2 +- crates/proof/src/lang.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/proof/README.md b/crates/proof/README.md index cd0e3f7d..bf48ded3 100644 --- a/crates/proof/README.md +++ b/crates/proof/README.md @@ -93,7 +93,7 @@ through the shared affix engine with default tuning, and hyphenated from its pac dictionary or pack it behaves exactly as `Other` did (no marks, English hyphenation fallback). `has_dictionary(tag)` / `ProofLang::is_proofed()` say whether a `Pack` language has a dictionary now (read on first use, cached). Loaded `Pack` dictionaries share a 256 MB budget (estimated from their files); the least recently used goes first, but never one used in the paragraph being checked, and languages with no dictionary take no room. Downstream `match`es on `ProofLang` need a `Pack(_)` arm; `code()` stays `&'static str` -and is `""` for it, as for `Other`: use `pack_locale()` for its locale. +and is `""` for it, as for `Other`: anything that keys, saves or reports a language (settings, caches, JSON/MCP output) uses `tag()`, which is `code()` for the built-ins and the locale (`nl`, `fr-fr`) for a pack language. **Which pack serves a language:** its locale, then shorter prefixes (`fr-ca` → `fr`), then another region's pack of the same language (`nl` → `nl-be`), never another script's (`sr-latn` never reads diff --git a/crates/proof/src/lang.rs b/crates/proof/src/lang.rs index f6d77443..d0a67625 100644 --- a/crates/proof/src/lang.rs +++ b/crates/proof/src/lang.rs @@ -176,7 +176,8 @@ impl ProofLang { /// The primary language subtag (`en`, `pl`, `bg`, `tr`, `ru`, `cs`, `id`, `de`, `es`); empty for /// [`ProofLang::Other`] and for a [`ProofLang::Pack`] language, whose locale isn't `'static`: - /// use [`ProofLang::pack_locale`] (`PackLocale::as_str`) for it. Kept `'static` so existing + /// use [`ProofLang::tag`] wherever the code names the language (keys, settings, saved or + /// reported values), or every pack language would read the same. Kept `'static` so existing /// callers (`PROOFED.map(ProofLang::code)`) don't break. pub fn code(self) -> &'static str { match self { @@ -210,6 +211,18 @@ impl ProofLang { } } + /// The code that names this language, for keys, settings and reported values: [`code`] for + /// the built-in languages (`en`, `es`, …), the locale for a [`ProofLang::Pack`] one (`nl`, + /// `sr-latn`), so no two pack languages share one; empty for [`ProofLang::Other`]. + /// + /// [`code`]: ProofLang::code + pub fn tag(self) -> std::borrow::Cow<'static, str> { + match self { + ProofLang::Pack(l) => std::borrow::Cow::Owned(l.as_str().to_string()), + other => std::borrow::Cow::Borrowed(other.code()), + } + } + /// The locale of a [`ProofLang::Pack`] language. pub fn pack_locale(self) -> Option { if let ProofLang::Pack(l) = self { Some(l) } else { None } @@ -478,6 +491,18 @@ mod tests { assert!(ProofLang::Pl.is_proofed() && !ProofLang::Other.is_proofed()); } + /// Two pack languages never share a key: `tag()` is the locale, while `code()` is empty. + #[test] + fn tag_names_each_pack_language() { + let (nl, fr) = (ProofLang::from_tag(Some("nl")), ProofLang::from_tag(Some("fr-FR"))); + assert_eq!((nl.code(), fr.code()), ("", "")); + assert_eq!((nl.tag().as_ref(), fr.tag().as_ref()), ("nl", "fr-fr")); + for l in ProofLang::PROOFED { + assert_eq!(l.tag(), l.code()); + } + assert_eq!(ProofLang::Other.tag(), ""); + } + #[test] fn tags_reject_unknown_or_partial_language_codes() { // A partial code is another language, never the one it starts like. @@ -485,6 +510,7 @@ mod tests { let lang = ProofLang::from_tag(Some(tag)); assert_eq!(lang.pack_locale().map(|l| l.to_string()), Some(tag.to_ascii_lowercase()), "{tag}"); assert_eq!(lang.code(), "", "{tag}: a pack language's locale is pack_locale()"); + assert_eq!(lang.tag(), lang.pack_locale().map(|l| l.as_str().to_string()).unwrap_or_default(), "{tag}"); } assert_tags(&["x-none", "e", "-", "und", "zxx", "i-klingon"], ProofLang::Other); }