use std::fmt::Display;
use rust_i18n::t;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
En,
ZhCn,
}
impl Language {
pub const DEFAULT: Self = Self::En;
pub fn as_locale(self) -> &'static str {
match self {
Self::En => "en",
Self::ZhCn => "zh-CN",
}
}
pub fn parse(raw: &str) -> Option<Self> {
for candidate in raw.split(':') {
if let Some(language) = Self::parse_one(candidate) {
return Some(language);
}
}
None
}
pub fn from_env() -> Option<Self> {
language_from_env_var("FASTSYNC_LANG").or_else(Self::from_system_env)
}
pub fn from_system_env() -> Option<Self> {
["LC_ALL", "LC_MESSAGES", "LANGUAGE", "LANG"]
.into_iter()
.find_map(language_from_env_var)
}
fn parse_one(raw: &str) -> Option<Self> {
let normalized = normalize_locale(raw)?;
if matches!(normalized.as_str(), "c" | "posix") || normalized.starts_with("en") {
return Some(Self::En);
}
if normalized == "cn"
|| normalized == "chinese"
|| normalized == "中文"
|| normalized.starts_with("zh")
{
return Some(Self::ZhCn);
}
None
}
}
fn language_from_env_var(name: &str) -> Option<Language> {
std::env::var(name)
.ok()
.and_then(|value| Language::parse(&value))
}
fn normalize_locale(raw: &str) -> Option<String> {
let value = raw.trim();
if value.is_empty() {
return None;
}
let without_encoding = value.split('.').next().unwrap_or(value);
let without_modifier = without_encoding
.split('@')
.next()
.unwrap_or(without_encoding);
let normalized = without_modifier
.trim()
.replace('_', "-")
.to_ascii_lowercase();
(!normalized.is_empty()).then_some(normalized)
}
pub fn set_language(language: Language) {
rust_i18n::set_locale(language.as_locale());
}
pub fn current_language() -> Language {
Language::parse(&rust_i18n::locale()).unwrap_or(Language::DEFAULT)
}
pub fn tr(language: Language, key: &str) -> String {
t!(key, locale = language.as_locale()).to_string()
}
pub fn tr_current(key: &str) -> String {
tr(current_language(), key)
}
pub fn tr_path(key: &str, path: impl Display) -> String {
t!(
key,
locale = current_language().as_locale(),
path = path.to_string()
)
.to_string()
}
pub fn tr_source_target(key: &str, source: impl Display, target: impl Display) -> String {
t!(
key,
locale = current_language().as_locale(),
source = source.to_string(),
target = target.to_string()
)
.to_string()
}
pub fn tr_path_source_target(
key: &str,
path: impl Display,
source: impl Display,
target: impl Display,
) -> String {
t!(
key,
locale = current_language().as_locale(),
path = path.to_string(),
source = source.to_string(),
target = target.to_string()
)
.to_string()
}
pub fn tr_value(key: &str, value: impl Display) -> String {
t!(
key,
locale = current_language().as_locale(),
value = value.to_string()
)
.to_string()
}
pub fn tr_many_errors(count: usize, first: &str) -> String {
t!(
"error.many",
locale = current_language().as_locale(),
count = count,
first = first
)
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_common_linux_locale_names() {
assert_eq!(Language::parse("zh_CN.UTF-8"), Some(Language::ZhCn));
assert_eq!(Language::parse("zh-CN.UTF-8"), Some(Language::ZhCn));
assert_eq!(Language::parse("zh_Hans_CN.UTF-8"), Some(Language::ZhCn));
assert_eq!(Language::parse("en_US.UTF-8"), Some(Language::En));
assert_eq!(Language::parse("C.UTF-8"), Some(Language::En));
}
#[test]
fn parses_language_priority_list() {
assert_eq!(Language::parse("fr_FR:zh_CN:en_US"), Some(Language::ZhCn));
assert_eq!(Language::parse("fr_FR:en_US"), Some(Language::En));
}
}