use std::collections::HashMap;
pub type Locale = String;
#[derive(Debug, Clone)]
pub struct Translation {
pub key: String,
pub value: String,
}
pub struct I18nManager {
current_locale: Locale,
translations: HashMap<Locale, HashMap<String, String>>,
fallback_locale: Option<Locale>,
}
impl I18nManager {
pub fn new(current_locale: &str) -> Self {
Self {
current_locale: current_locale.to_string(),
translations: HashMap::new(),
fallback_locale: None,
}
}
pub fn with_fallback(mut self, fallback: &str) -> Self {
self.fallback_locale = Some(fallback.to_string());
self
}
pub fn load_translations(&mut self, locale: &str, json: &str) -> anyhow::Result<()> {
let data: HashMap<String, String> = serde_json::from_str(json)?;
self.translations.insert(locale.to_string(), data);
Ok(())
}
pub fn load_translations_map(&mut self, locale: &str, map: HashMap<String, String>) {
self.translations.insert(locale.to_string(), map);
}
pub fn t(&self, key: &str, args: &[(&str, &str)]) -> String {
let value = self.get_translated_text(key);
self.format_text(&value, args)
}
fn get_translated_text(&self, key: &str) -> String {
if let Some(lang_translations) = self.translations.get(&self.current_locale) {
if let Some(value) = lang_translations.get(key) {
return value.clone();
}
}
if let Some(ref fallback) = self.fallback_locale {
if let Some(lang_translations) = self.translations.get(fallback) {
if let Some(value) = lang_translations.get(key) {
return value.clone();
}
}
}
key.to_string()
}
fn format_text(&self, text: &str, args: &[(&str, &str)]) -> String {
let mut result = text.to_string();
for (key, value) in args {
result = result.replace(&format!("{{{}}}", key), value);
}
result
}
pub fn current_locale(&self) -> &str {
&self.current_locale
}
pub fn set_locale(&mut self, locale: &str) {
self.current_locale = locale.to_string();
}
pub fn available_locales(&self) -> Vec<&str> {
self.translations.keys().map(|s| s.as_str()).collect()
}
}
impl Default for I18nManager {
fn default() -> Self {
Self::new("en")
}
}
pub struct I18nText {
key: String,
args: Vec<(String, String)>,
}
impl I18nText {
pub fn new(key: &str) -> Self {
Self {
key: key.to_string(),
args: Vec::new(),
}
}
pub fn with_arg(mut self, key: &str, value: &str) -> Self {
self.args.push((key.to_string(), value.to_string()));
self
}
pub fn translate(&self, i18n: &I18nManager) -> String {
let args: Vec<(&str, &str)> = self
.args
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
i18n.t(&self.key, &args)
}
}
pub struct PluralRule {
pub count: i64,
pub singular: String,
pub plural: String,
}
impl PluralRule {
pub fn new(count: i64, singular: &str, plural: &str) -> Self {
Self {
count,
singular: singular.to_string(),
plural: plural.to_string(),
}
}
pub fn key(&self) -> &str {
if self.count == 1 {
&self.singular
} else {
&self.plural
}
}
pub fn translate(&self, i18n: &I18nManager) -> String {
let key = self.key();
i18n.t(key, &[("count", &self.count.to_string())])
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_i18n_manager_creation() {
let manager = I18nManager::new("en");
assert_eq!(manager.current_locale(), "en");
}
#[test]
fn test_load_translations() {
let mut manager = I18nManager::new("en");
let mut translations = HashMap::new();
translations.insert("hello".to_string(), "Hello".to_string());
translations.insert("world".to_string(), "World".to_string());
manager.load_translations_map("en", translations);
assert_eq!(manager.t("hello", &[]), "Hello");
assert_eq!(manager.t("world", &[]), "World");
}
#[test]
fn test_fallback_locale() {
let mut manager = I18nManager::new("zh-CN").with_fallback("en");
let mut en_translations = HashMap::new();
en_translations.insert("hello".to_string(), "Hello".to_string());
manager.load_translations_map("en", en_translations);
assert_eq!(manager.t("hello", &[]), "Hello");
}
#[test]
fn test_format_text() {
let mut manager = I18nManager::new("en");
let mut translations = HashMap::new();
translations.insert("greeting".to_string(), "Hello, {name}!".to_string());
manager.load_translations_map("en", translations);
assert_eq!(manager.t("greeting", &[("name", "World")]), "Hello, World!");
}
#[test]
fn test_set_locale() {
let mut manager = I18nManager::new("en");
manager.set_locale("zh-CN");
assert_eq!(manager.current_locale(), "zh-CN");
}
#[test]
fn test_plural_rule() {
let rule = PluralRule::new(1, "item", "items");
assert_eq!(rule.key(), "item");
let rule = PluralRule::new(5, "item", "items");
assert_eq!(rule.key(), "items");
}
}