use std::collections::HashMap;
use tairitsu_hooks::{Context, consume_context, provide_context, use_context};
use crate::i18n::language::Language;
#[derive(Clone, Debug)]
pub struct I18nState {
pub current: Language,
pub translations: HashMap<Language, HashMap<String, String>>,
pub fallback: Language,
}
impl I18nState {
pub fn new(
translations: HashMap<Language, HashMap<String, String>>,
default_locale: Language,
) -> Self {
let fallback = default_locale;
Self {
current: default_locale,
translations,
fallback,
}
}
pub fn with_fallback(mut self, fallback: Language) -> Self {
self.fallback = fallback;
self
}
pub fn t(&self, key: &str) -> Option<&str> {
if let Some(map) = self.translations.get(&self.current)
&& let Some(value) = map.get(key)
{
return Some(value.as_str());
}
if self.current != self.fallback
&& let Some(map) = self.translations.get(&self.fallback)
&& let Some(value) = map.get(key)
{
return Some(value.as_str());
}
None
}
pub fn t_or_key<'a>(&'a self, key: &'a str) -> &'a str {
self.t(key).unwrap_or(key)
}
}
pub struct I18nProvider {
context: Context<I18nState>,
}
impl I18nProvider {
pub fn new(
translations: HashMap<Language, HashMap<String, String>>,
default_locale: Language,
) -> Self {
let state = I18nState::new(translations, default_locale);
Self {
context: Context::new(state),
}
}
pub fn provide(&self) {
provide_context(self.context.get().clone());
}
pub fn set_locale(&self, locale: Language) {
let mut state = self.context.get_mut();
state.current = locale;
}
pub fn locale(&self) -> Language {
self.context.get().current
}
pub fn t(&self, key: &str) -> Option<String> {
self.context.get().t(key).map(|s| s.to_string())
}
}
pub fn provide_i18n(language: Language, translations: HashMap<Language, HashMap<String, String>>) {
let provider = I18nProvider::new(translations, language);
provider.provide();
}
pub fn use_locale() -> Language {
consume_context::<I18nState>().current
}
pub fn set_locale(locale: Language) {
let ctx =
use_context::<I18nState>().expect("I18n context not found. Call provide_i18n() first.");
let mut state = ctx.get_mut();
state.current = locale;
}
pub fn translate(key: &str) -> Option<String> {
let state = consume_context::<I18nState>();
state.t(key).map(|s| s.to_string())
}
pub fn translate_or_key(key: &str) -> String {
let state = consume_context::<I18nState>();
state.t_or_key(key).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_translations() -> HashMap<Language, HashMap<String, String>> {
let mut en: HashMap<String, String> = HashMap::new();
en.insert("common.button.submit".to_string(), "Submit".to_string());
en.insert("common.button.cancel".to_string(), "Cancel".to_string());
en.insert("greeting".to_string(), "Hello".to_string());
let mut zh: HashMap<String, String> = HashMap::new();
zh.insert("common.button.submit".to_string(), "提交".to_string());
zh.insert("common.button.cancel".to_string(), "取消".to_string());
zh.insert("greeting".to_string(), "你好".to_string());
let mut translations = HashMap::new();
translations.insert(Language::ENGLISH, en);
translations.insert(Language::CHINESE_SIMPLIFIED, zh);
translations
}
#[test]
fn test_i18n_state_new() {
let state = I18nState::new(make_translations(), Language::ENGLISH);
assert_eq!(state.current, Language::ENGLISH);
assert_eq!(state.fallback, Language::ENGLISH);
}
#[test]
fn test_i18n_state_translate() {
let state = I18nState::new(make_translations(), Language::ENGLISH);
assert_eq!(state.t("common.button.submit"), Some("Submit"));
assert_eq!(state.t("common.button.cancel"), Some("Cancel"));
assert_eq!(state.t("greeting"), Some("Hello"));
assert_eq!(state.t("nonexistent.key"), None);
}
#[test]
fn test_i18n_state_fallback() {
let mut translations = make_translations();
let mut ja: HashMap<String, String> = HashMap::new();
ja.insert("greeting".to_string(), "こんにちは".to_string());
translations.insert(Language::JAPANESE, ja);
let state =
I18nState::new(translations, Language::JAPANESE).with_fallback(Language::ENGLISH);
assert_eq!(state.t("greeting"), Some("こんにちは"));
assert_eq!(state.t("common.button.submit"), Some("Submit"));
}
#[test]
fn test_i18n_state_t_or_key() {
let state = I18nState::new(make_translations(), Language::ENGLISH);
assert_eq!(state.t_or_key("common.button.submit"), "Submit");
assert_eq!(state.t_or_key("nonexistent.key"), "nonexistent.key");
}
#[test]
fn test_i18n_provider_new_and_locale() {
let provider = I18nProvider::new(make_translations(), Language::ENGLISH);
assert_eq!(provider.locale(), Language::ENGLISH);
}
#[test]
fn test_i18n_provider_set_locale() {
let provider = I18nProvider::new(make_translations(), Language::ENGLISH);
provider.set_locale(Language::CHINESE_SIMPLIFIED);
assert_eq!(provider.locale(), Language::CHINESE_SIMPLIFIED);
}
#[test]
fn test_i18n_provider_translate() {
let provider = I18nProvider::new(make_translations(), Language::ENGLISH);
assert_eq!(
provider.t("common.button.submit"),
Some("Submit".to_string())
);
assert_eq!(provider.t("nonexistent"), None);
}
#[test]
fn test_i18n_provider_translate_after_locale_change() {
let provider = I18nProvider::new(make_translations(), Language::ENGLISH);
assert_eq!(
provider.t("common.button.submit"),
Some("Submit".to_string())
);
provider.set_locale(Language::CHINESE_SIMPLIFIED);
assert_eq!(provider.t("common.button.submit"), Some("提交".to_string()));
}
#[test]
fn test_provide_and_use_locale() {
provide_i18n(Language::ENGLISH, make_translations());
assert_eq!(use_locale(), Language::ENGLISH);
}
#[test]
fn test_set_locale_via_function() {
provide_i18n(Language::ENGLISH, make_translations());
assert_eq!(use_locale(), Language::ENGLISH);
set_locale(Language::CHINESE_SIMPLIFIED);
assert_eq!(use_locale(), Language::CHINESE_SIMPLIFIED);
}
#[test]
fn test_translate_via_function() {
provide_i18n(Language::ENGLISH, make_translations());
assert_eq!(
translate("common.button.submit"),
Some("Submit".to_string())
);
set_locale(Language::CHINESE_SIMPLIFIED);
assert_eq!(translate("common.button.submit"), Some("提交".to_string()));
}
#[test]
fn test_translate_or_key_via_function() {
provide_i18n(Language::ENGLISH, make_translations());
assert_eq!(translate_or_key("common.button.submit"), "Submit");
assert_eq!(translate_or_key("missing.key"), "missing.key");
}
#[test]
fn test_context_shared_between_consumers() {
let provider = I18nProvider::new(make_translations(), Language::ENGLISH);
provider.provide();
assert_eq!(use_locale(), Language::ENGLISH);
set_locale(Language::CHINESE_SIMPLIFIED);
assert_eq!(use_locale(), Language::CHINESE_SIMPLIFIED);
assert_eq!(translate("common.button.cancel"), Some("取消".to_string()));
}
}