use ortho_config::LanguageIdentifier;
pub const SOURCE_LOCALE: &str = "en-US";
#[derive(Debug, Clone, Copy)]
pub struct LocaleCatalogue {
tag: &'static str,
resource: &'static str,
}
impl LocaleCatalogue {
#[must_use]
pub const fn tag(&self) -> &'static str {
self.tag
}
#[must_use]
pub const fn resource(&self) -> &'static str {
self.resource
}
}
macro_rules! define_locales {
($($tag:literal),+ $(,)?) => {
pub const SUPPORTED_LOCALES: &[LocaleCatalogue] = &[
$(LocaleCatalogue {
tag: $tag,
resource: include_str!(concat!("../locales/", $tag, "/messages.ftl")),
}),+
];
};
}
define_locales![
"ar", "cs", "cy", "da", "de", "el", "en-GB", "en-US", "es-419", "es-ES", "fa", "fi", "fr",
"gd", "he", "hi", "hu", "id", "it", "ja", "ko", "nb", "nl", "pl", "pt-BR", "pt-PT", "ro", "ru",
"sv", "th", "tr", "uk", "vi", "zh-Hans", "zh-Hant",
];
struct LanguageFallback {
language: &'static str,
subtags: &'static [(&'static str, &'static str)],
other_region: &'static str,
bare: &'static str,
}
const LANGUAGE_FALLBACKS: &[LanguageFallback] = &[
LanguageFallback {
language: "en",
subtags: &[("US", "en-US"), ("GB", "en-GB")],
other_region: "en-GB",
bare: "en-US",
},
LanguageFallback {
language: "es",
subtags: &[("ES", "es-ES"), ("419", "es-419")],
other_region: "es-419",
bare: "es-ES",
},
LanguageFallback {
language: "pt",
subtags: &[("BR", "pt-BR"), ("PT", "pt-PT")],
other_region: "pt-PT",
bare: "pt-PT",
},
LanguageFallback {
language: "zh",
subtags: &[
("Hans", "zh-Hans"),
("Hant", "zh-Hant"),
("CN", "zh-Hans"),
("SG", "zh-Hans"),
("MY", "zh-Hans"),
("TW", "zh-Hant"),
("HK", "zh-Hant"),
("MO", "zh-Hant"),
],
other_region: "zh-Hans",
bare: "zh-Hans",
},
LanguageFallback {
language: "no",
subtags: &[],
other_region: "nb",
bare: "nb",
},
];
#[must_use]
pub fn catalogue(tag: &str) -> Option<&'static LocaleCatalogue> {
SUPPORTED_LOCALES.iter().find(|entry| entry.tag == tag)
}
const EMPTY_SOURCE: LocaleCatalogue = LocaleCatalogue {
tag: SOURCE_LOCALE,
resource: "",
};
#[must_use]
pub fn source_catalogue() -> &'static LocaleCatalogue {
catalogue(SOURCE_LOCALE).unwrap_or(&EMPTY_SOURCE)
}
fn fallback_for(language: &str) -> Option<&'static LanguageFallback> {
LANGUAGE_FALLBACKS
.iter()
.find(|entry| entry.language == language)
}
fn unique_language_catalogue(language: &str) -> Option<&'static LocaleCatalogue> {
let mut matches = SUPPORTED_LOCALES
.iter()
.filter(|entry| tag_language(entry.tag) == language);
let first = matches.next()?;
matches.next().is_none().then_some(first)
}
pub(crate) fn tag_language(tag: &str) -> &str {
tag.split('-').next().unwrap_or(tag)
}
fn subtag_catalogue(
fallback: &LanguageFallback,
subtag: Option<&str>,
) -> Option<&'static LocaleCatalogue> {
let requested = subtag?;
fallback
.subtags
.iter()
.find(|(key, _)| *key == requested)
.and_then(|(_, tag)| catalogue(tag))
}
#[must_use]
pub fn resolve_catalogue(locale: &LanguageIdentifier) -> &'static LocaleCatalogue {
let language = locale.language.as_str();
let script = locale.script.map(|script| script.to_string());
let region = locale.region.map(|region| region.to_string());
if let Some(exact) = catalogue(&locale.to_string()) {
return exact;
}
if let Some(fallback) = fallback_for(language) {
return resolve_via_fallback(fallback, script.as_deref(), region.as_deref());
}
unique_language_catalogue(language).unwrap_or_else(source_catalogue)
}
fn resolve_via_fallback(
fallback: &LanguageFallback,
script: Option<&str>,
region: Option<&str>,
) -> &'static LocaleCatalogue {
subtag_catalogue(fallback, script)
.or_else(|| subtag_catalogue(fallback, region))
.or_else(|| {
let tag = if region.is_some() {
fallback.other_region
} else {
fallback.bare
};
catalogue(tag)
})
.unwrap_or_else(source_catalogue)
}