es_fluent_shared/
language.rs1use icu_locale::{Locale, LocaleCanonicalizer};
4use thiserror::Error;
5pub use unic_langid::LanguageIdentifier;
6use unic_langid::LanguageIdentifierError;
7
8#[derive(Debug, Error)]
10pub enum CanonicalLanguageIdentifierError {
11 #[error("Invalid language identifier '{name}'")]
13 Invalid {
14 name: String,
16 #[source]
18 source: LanguageIdentifierError,
19 },
20 #[error("Language identifier '{name}' could not be parsed as an ICU locale: {details}")]
22 IcuInvalid {
23 name: String,
25 details: String,
27 },
28 #[error("Locale directory '{name}' must use canonical BCP-47 form '{canonical}'")]
30 NonCanonical {
31 name: String,
33 canonical: String,
35 },
36}
37
38pub fn parse_canonical_language_identifier(
40 name: &str,
41) -> Result<LanguageIdentifier, CanonicalLanguageIdentifierError> {
42 let lang = name.parse::<LanguageIdentifier>().map_err(|source| {
43 CanonicalLanguageIdentifierError::Invalid {
44 name: name.to_string(),
45 source,
46 }
47 })?;
48 let mut locale =
49 name.parse::<Locale>()
50 .map_err(|source| CanonicalLanguageIdentifierError::IcuInvalid {
51 name: name.to_string(),
52 details: source.to_string(),
53 })?;
54 LocaleCanonicalizer::new_extended().canonicalize(&mut locale);
55
56 let canonical = locale.to_string();
57 if canonical != name {
58 return Err(CanonicalLanguageIdentifierError::NonCanonical {
59 name: name.to_string(),
60 canonical,
61 });
62 }
63
64 Ok(lang)
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn parses_canonical_language_identifier() {
73 let lang = parse_canonical_language_identifier("de-DE-1901")
74 .expect("canonical locale should parse");
75 assert_eq!(lang.to_string(), "de-DE-1901");
76 }
77
78 #[test]
79 fn rejects_invalid_identifier() {
80 let err = parse_canonical_language_identifier("not-a-lang!")
81 .expect_err("invalid locale should fail");
82 assert!(matches!(
83 err,
84 CanonicalLanguageIdentifierError::Invalid { name, .. } if name == "not-a-lang!"
85 ));
86 }
87
88 #[test]
89 fn rejects_noncanonical_identifier() {
90 let err = parse_canonical_language_identifier("en-us")
91 .expect_err("noncanonical locale should fail");
92 assert!(matches!(
93 err,
94 CanonicalLanguageIdentifierError::NonCanonical { name, canonical }
95 if name == "en-us" && canonical == "en-US"
96 ));
97 }
98
99 #[test]
100 fn rejects_noncanonical_language_identifiers() {
101 let err = parse_canonical_language_identifier("iw")
102 .expect_err("noncanonical identifiers should fail");
103 assert!(matches!(
104 err,
105 CanonicalLanguageIdentifierError::NonCanonical { name, canonical }
106 if name == "iw" && canonical == "he"
107 ));
108 }
109}