use std::{env, ffi::OsStr};
const LANGUAGE: &str = "LANGUAGE";
const LC_ALL: &str = "LC_ALL";
const LC_MESSAGES: &str = "LC_MESSAGES";
const LANG: &str = "LANG";
trait EnvAccess {
fn get(&self, key: impl AsRef<OsStr>) -> Option<String>;
}
struct StdEnv;
impl EnvAccess for StdEnv {
fn get(&self, key: impl AsRef<OsStr>) -> Option<String> {
env::var(key).ok()
}
}
pub(crate) fn get() -> impl Iterator<Item = String> {
_get(&StdEnv)
}
fn _get(env: &impl EnvAccess) -> impl Iterator<Item = String> {
let mut locales = Vec::new();
if let Some(val) = env.get(LANGUAGE).filter(|val| !val.is_empty()) {
for part in val.split(':') {
let locale = posix_to_bcp47(part);
if !locales.contains(&locale) {
locales.push(locale);
}
}
}
for variable in [LC_ALL, LC_MESSAGES, LANG] {
if let Some(val) = env.get(variable).filter(|val| !val.is_empty()) {
let locale = posix_to_bcp47(&val);
if !locales.contains(&locale) {
locales.push(locale);
}
}
}
locales.into_iter()
}
fn posix_to_bcp47(locale: &str) -> String {
locale
.chars()
.take_while(|&c| c != '.' && c != '@')
.map(|c| if c == '_' { '-' } else { c })
.collect()
}
#[cfg(test)]
mod tests {
use super::{EnvAccess, _get, posix_to_bcp47, LANG, LANGUAGE, LC_ALL, LC_MESSAGES};
use std::{
collections::HashMap,
ffi::{OsStr, OsString},
};
type MockEnv = HashMap<OsString, String>;
impl EnvAccess for MockEnv {
fn get(&self, key: impl AsRef<OsStr>) -> Option<String> {
self.get(key.as_ref()).cloned()
}
}
const BCP_47: &str = "fr-FR";
const POSIX: &str = "fr_FR";
const POSIX_ENC: &str = "fr_FR.UTF-8";
const POSIX_MOD: &str = "fr_FR@euro";
const POSIX_ENC_MOD: &str = "fr_FR.UTF-8@euro";
#[test]
fn parse_identifier() {
assert_eq!(posix_to_bcp47(BCP_47), BCP_47);
assert_eq!(posix_to_bcp47(POSIX), BCP_47);
assert_eq!(posix_to_bcp47(POSIX_ENC), BCP_47);
assert_eq!(posix_to_bcp47(POSIX_MOD), BCP_47);
assert_eq!(posix_to_bcp47(POSIX_ENC_MOD), BCP_47);
}
#[test]
fn env_get() {
fn case(
env: &mut MockEnv,
language: impl Into<String>,
lc_all: impl Into<String>,
lc_messages: impl Into<String>,
lang: impl Into<String>,
expected: impl IntoIterator<Item = impl Into<String>>,
) {
env.insert(LANGUAGE.into(), language.into());
env.insert(LC_ALL.into(), lc_all.into());
env.insert(LC_MESSAGES.into(), lc_messages.into());
env.insert(LANG.into(), lang.into());
assert!(_get(env).eq(expected.into_iter().map(|s| s.into())));
}
let mut env = MockEnv::new();
assert_eq!(_get(&env).next(), None);
case(&mut env, "", "", "", "", &[] as &[String]);
case(
&mut env,
POSIX_ENC_MOD,
POSIX_ENC,
POSIX_MOD,
POSIX,
[BCP_47],
);
case(&mut env, "en_US", "", "", "", ["en-US"]);
case(&mut env, "", "en_US", "", "", ["en-US"]);
case(&mut env, "", "", "en_US", "", ["en-US"]);
case(&mut env, "", "", "", "en_US", ["en-US"]);
case(&mut env, "en_US", "en_US", "en_US", "en_US", ["en-US"]);
case(
&mut env,
"en_US",
"en_US",
"ru_RU",
"en_US",
["en-US", "ru-RU"],
);
case(
&mut env,
"en_US",
"ru_RU",
"ru_RU",
"en_US",
["en-US", "ru-RU"],
);
case(
&mut env,
"en_US",
"es_ES",
"ru_RU",
"en_US",
["en-US", "es-ES", "ru-RU"],
);
case(
&mut env,
"en_US:ru_RU:es_ES:en_US",
"es_ES",
"ru_RU",
"en_US",
["en-US", "ru-RU", "es-ES"],
);
case(
&mut env,
"en_US:fr_fr",
"EN_US",
"fR_Fr",
"En_US",
["en-US", "fr-fr", "EN-US", "fR-Fr", "En-US"],
);
case(
&mut env,
"ru_RU:ru:en_US:en",
"ru_RU.UTF-8",
"ru_RU.UTF-8",
"ru_RU.UTF-8",
["ru-RU", "ru", "en-US", "en"],
);
case(
&mut env,
"fr_FR.UTF-8@euro:fr_FR.UTF-8:fr_FR:fr:en_US.UTF-8:en_US:en",
"es_ES.UTF-8@euro",
"fr_FR.UTF-8@euro",
"fr_FR.UTF-8@euro",
["fr-FR", "fr", "en-US", "en", "es-ES"],
);
case(
&mut env,
"",
"es_ES.UTF-8@euro",
"fr_FR.UTF-8@euro",
"fr_FR.UTF-8@euro",
["es-ES", "fr-FR"],
);
case(
&mut env,
"fr_FR@euro",
"fr_FR.UTF-8",
"en_US.UTF-8",
"en_US.UTF-8@dict",
["fr-FR", "en-US"],
);
case(&mut env, BCP_47, BCP_47, BCP_47, POSIX, [BCP_47]);
case(
&mut env,
"fr-FR",
"es-ES",
"de-DE",
"en-US",
["fr-FR", "es-ES", "de-DE", "en-US"],
);
}
}