use std::collections::HashMap;
use std::fmt;
use gpui::SharedString;
use super::locale::Locale;
use super::runtime::TranslationMap;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PluralCategory {
Zero,
One,
Two,
Few,
Many,
Other,
}
impl PluralCategory {
pub fn for_number(n: u64, locale: &Locale) -> Self {
let lang = locale.language();
match lang {
"ar" => {
if n == 0 {
PluralCategory::Zero
} else if n == 1 {
PluralCategory::One
} else if n == 2 {
PluralCategory::Two
} else if (3..=10).contains(&n) {
PluralCategory::Few
} else if (11..=99).contains(&n) {
PluralCategory::Many
} else {
PluralCategory::Other
}
}
"zh" | "ja" | "ko" | "vi" | "th" => PluralCategory::Other,
"fr" | "pt" | "it" | "es" | "ca" | "gl" => {
if n == 0 || n == 1 {
PluralCategory::One
} else {
PluralCategory::Other
}
}
"de" | "nl" | "sv" | "da" | "no" | "fi" | "et" | "el" => {
if n == 1 {
PluralCategory::One
} else {
PluralCategory::Other
}
}
"pl" | "cs" | "sk" | "sl" | "uk" | "ru" | "bg" | "sr" | "hr" => {
if n == 1 {
PluralCategory::One
} else if (2..=4).contains(&n) {
PluralCategory::Few
} else {
PluralCategory::Other
}
}
"ro" | "hu" => {
if n == 1 {
PluralCategory::One
} else if (2..=19).contains(&n) {
PluralCategory::Few
} else {
PluralCategory::Other
}
}
"en" | "en-US" | "en-GB" => {
if n == 1 {
PluralCategory::One
} else {
PluralCategory::Other
}
}
_ => {
if n == 1 {
PluralCategory::One
} else {
PluralCategory::Other
}
}
}
}
}
impl fmt::Display for PluralCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PluralCategory::Zero => write!(f, "zero"),
PluralCategory::One => write!(f, "one"),
PluralCategory::Two => write!(f, "two"),
PluralCategory::Few => write!(f, "few"),
PluralCategory::Many => write!(f, "many"),
PluralCategory::Other => write!(f, "other"),
}
}
}
pub struct TranslatedString {
value: String,
}
impl TranslatedString {
pub fn new(value: impl Into<String>) -> Self {
Self {
value: value.into(),
}
}
pub fn with_args(mut self, args: &HashMap<&str, impl fmt::Display>) -> Self {
for (key, value) in args {
let placeholder = format!("{{{}}}", key);
self.value = self.value.replace(&placeholder, &value.to_string());
}
self
}
pub fn into_shared(self) -> SharedString {
self.value.into()
}
}
impl From<String> for TranslatedString {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl<'a> From<&'a str> for TranslatedString {
fn from(s: &'a str) -> Self {
Self::new(s)
}
}
pub trait Translator {
fn t(&self, key: &str) -> Option<&str>;
fn tn(&self, key: &str, n: usize) -> Option<&str>;
fn tf(&self, key: &str, args: &HashMap<&str, impl fmt::Display>) -> String;
}
impl Translator for TranslationMap {
fn t(&self, key: &str) -> Option<&str> {
self.get(key)
}
fn tn(&self, key: &str, n: usize) -> Option<&str> {
let category = PluralCategory::for_number(n as u64, &Locale::default());
let plural_key = format!("{}.{}", key, category);
if let Some(value) = self.get(&plural_key) {
return Some(value);
}
let other_key = format!("{}.other", key);
self.get(&other_key)
}
fn tf(&self, key: &str, args: &HashMap<&str, impl fmt::Display>) -> String {
let base = self.t(key).unwrap_or(key);
let mut result = base.to_string();
for (key, value) in args {
let placeholder = format!("{{{}}}", key);
result = result.replace(&placeholder, &value.to_string());
}
result
}
}
#[macro_export]
macro_rules! t {
($key:expr) => {{
$key
}};
}
#[macro_export]
macro_rules! tf {
($key:expr, $args:expr) => {{ $key }};
}
#[macro_export]
macro_rules! tn {
($key:expr, n = $n:expr) => {{ $key }};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plural_english() {
let locale = Locale::new("en").unwrap();
assert_eq!(
PluralCategory::for_number(0, &locale),
PluralCategory::Other
);
assert_eq!(PluralCategory::for_number(1, &locale), PluralCategory::One);
assert_eq!(
PluralCategory::for_number(2, &locale),
PluralCategory::Other
);
assert_eq!(
PluralCategory::for_number(5, &locale),
PluralCategory::Other
);
}
#[test]
fn test_plural_arabic() {
let locale = Locale::new("ar").unwrap();
assert_eq!(PluralCategory::for_number(0, &locale), PluralCategory::Zero);
assert_eq!(PluralCategory::for_number(1, &locale), PluralCategory::One);
assert_eq!(PluralCategory::for_number(2, &locale), PluralCategory::Two);
assert_eq!(PluralCategory::for_number(5, &locale), PluralCategory::Few);
}
#[test]
fn test_translated_string_args() {
let mut args: HashMap<&str, Box<dyn fmt::Display>> = HashMap::new();
args.insert("name", Box::new("World"));
args.insert("count", Box::new(5));
let s = TranslatedString::new("Hello {name}, you have {count} items").with_args(&args);
assert_eq!(s.into_shared().to_string(), "Hello World, you have 5 items");
}
}