use std::collections::HashMap;
use crate::error::{Result, WalletError};
pub const SUPPORTED_LANGUAGES: &[(&str, &str, &str)] = &[
("en", "English", "English"),
("de", "Deutsch", "German"),
("ru", "Русский", "Russian"),
("uk", "Українська", "Ukrainian"),
("pl", "Polski", "Polish"),
("pt", "Português", "Portuguese"),
("be", "Беларуская", "Belarusian"),
("bg", "Български", "Bulgarian"),
("hi", "हिन्दी", "Hindi"),
("ca", "Català", "Catalan"),
("es", "Español", "Spanish"),
];
const LANG_EN: &str = include_str!("languages/en.json");
const LANG_DE: &str = include_str!("languages/de.json");
const LANG_RU: &str = include_str!("languages/ru.json");
const LANG_UK: &str = include_str!("languages/uk.json");
const LANG_PL: &str = include_str!("languages/pl.json");
const LANG_PT: &str = include_str!("languages/pt.json");
const LANG_BE: &str = include_str!("languages/be.json");
const LANG_BG: &str = include_str!("languages/bg.json");
const LANG_HI: &str = include_str!("languages/hi.json");
const LANG_CA: &str = include_str!("languages/ca.json");
const LANG_ES: &str = include_str!("languages/es.json");
fn get_language_json(lang: &str) -> Option<&'static str> {
match lang {
"en" => Some(LANG_EN),
"de" => Some(LANG_DE),
"ru" => Some(LANG_RU),
"uk" => Some(LANG_UK),
"pl" => Some(LANG_PL),
"pt" => Some(LANG_PT),
"be" => Some(LANG_BE),
"bg" => Some(LANG_BG),
"hi" => Some(LANG_HI),
"ca" => Some(LANG_CA),
"es" => Some(LANG_ES),
_ => None,
}
}
pub fn is_language_supported(lang: &str) -> bool {
SUPPORTED_LANGUAGES.iter().any(|(code, _, _)| *code == lang)
}
pub struct Translations {
current_lang: String,
strings: HashMap<String, String>,
english: HashMap<String, String>,
}
impl Translations {
pub fn new() -> Result<Self> {
let english = Self::load_language("en")?;
Ok(Self {
current_lang: "en".to_string(),
strings: english.clone(),
english,
})
}
fn load_language(lang: &str) -> Result<HashMap<String, String>> {
let json = get_language_json(lang)
.ok_or_else(|| WalletError::LocalizationError(
format!("Language '{}' not found", lang)
))?;
let json = json.strip_prefix('\u{feff}').unwrap_or(json);
serde_json::from_str(json)
.map_err(|e| WalletError::LocalizationError(
format!("Failed to parse language '{}': {}", lang, e)
))
}
pub fn set_language(&mut self, lang: &str) -> Result<()> {
if !is_language_supported(lang) {
return Err(WalletError::LocalizationError(
format!("Language '{}' is not supported", lang)
));
}
self.strings = Self::load_language(lang)?;
self.current_lang = lang.to_string();
Ok(())
}
pub fn get<'a>(&'a self, key: &'a str) -> &'a str {
self.strings.get(key)
.or_else(|| self.english.get(key))
.map(|s| s.as_str())
.unwrap_or(key)
}
pub fn get_opt(&self, key: &str) -> Option<&str> {
self.strings.get(key)
.or_else(|| self.english.get(key))
.map(|s| s.as_str())
}
pub fn get_en<'a>(&'a self, key: &'a str) -> &'a str {
self.english.get(key)
.map(|s| s.as_str())
.unwrap_or(key)
}
pub fn get_language(&self) -> &str {
&self.current_lang
}
pub fn get_language_name(&self) -> &str {
SUPPORTED_LANGUAGES.iter()
.find(|(code, _, _)| *code == self.current_lang)
.map(|(_, local, _)| *local)
.unwrap_or("Unknown")
}
pub fn available_languages() -> &'static [(&'static str, &'static str, &'static str)] {
SUPPORTED_LANGUAGES
}
pub fn get_english_dictionary(&self) -> &HashMap<String, String> {
&self.english
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.strings.keys()
}
}
impl Default for Translations {
fn default() -> Self {
Self::new().expect("Failed to load default translations")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_english() {
let tr = Translations::new().unwrap();
assert_eq!(tr.get_language(), "en");
}
#[test]
fn test_get_translation() {
let tr = Translations::new().unwrap();
assert_eq!(tr.get("app_name"), "IntelliWallet");
assert_eq!(tr.get("ok"), "OK");
assert_eq!(tr.get("cancel"), "Cancel");
}
#[test]
fn test_unknown_key_returns_key() {
let tr = Translations::new().unwrap();
assert_eq!(tr.get("unknown_key_xyz"), "unknown_key_xyz");
}
#[test]
fn test_set_language() {
let mut tr = Translations::new().unwrap();
tr.set_language("ru").unwrap();
assert_eq!(tr.get_language(), "ru");
let ok_ru = tr.get("ok");
assert!(!ok_ru.is_empty());
}
#[test]
fn test_fallback_to_english() {
let mut tr = Translations::new().unwrap();
tr.set_language("de").unwrap();
let app_name = tr.get("app_name");
assert!(!app_name.is_empty());
}
#[test]
fn test_unsupported_language() {
let mut tr = Translations::new().unwrap();
let result = tr.set_language("xx");
assert!(result.is_err());
}
#[test]
fn test_all_languages_load() {
for (code, _, _) in SUPPORTED_LANGUAGES {
let mut tr = Translations::new().unwrap();
tr.set_language(code).expect(&format!("Failed to load language: {}", code));
assert_eq!(tr.get_language(), *code);
}
}
#[test]
fn test_is_language_supported() {
assert!(is_language_supported("en"));
assert!(is_language_supported("ru"));
assert!(is_language_supported("de"));
assert!(!is_language_supported("xx"));
assert!(!is_language_supported("fr")); }
#[test]
fn test_get_language_name() {
let mut tr = Translations::new().unwrap();
assert_eq!(tr.get_language_name(), "English");
tr.set_language("ru").unwrap();
assert_eq!(tr.get_language_name(), "Русский");
tr.set_language("de").unwrap();
assert_eq!(tr.get_language_name(), "Deutsch");
}
#[test]
fn test_available_languages() {
let langs = Translations::available_languages();
assert_eq!(langs.len(), 11);
assert_eq!(langs[0], ("en", "English", "English"));
assert_eq!(langs[10], ("es", "Español", "Spanish"));
}
#[test]
fn test_get_opt() {
let tr = Translations::new().unwrap();
assert!(tr.get_opt("app_name").is_some());
assert!(tr.get_opt("nonexistent_key_xyz").is_none());
}
#[test]
fn test_get_en() {
let mut tr = Translations::new().unwrap();
tr.set_language("ru").unwrap();
let en_ok = tr.get_en("ok");
assert_eq!(en_ok, "OK");
}
#[test]
fn test_keys_iterator() {
let tr = Translations::new().unwrap();
let count = tr.keys().count();
assert!(count > 0);
}
#[test]
fn test_default_translations() {
let tr = Translations::default();
assert_eq!(tr.get_language(), "en");
}
#[test]
fn test_all_keys_translated() {
let tr_en = Translations::new().unwrap();
let en_dict = tr_en.get_english_dictionary();
let exceptions = [
"app_name", "ok", "ip", "premium_upgrade_why", ];
for (code, _, _) in SUPPORTED_LANGUAGES {
if *code == "en" {
continue;
}
let mut tr = Translations::new().unwrap();
tr.set_language(code).unwrap();
for (key, en_value) in en_dict {
let translated = tr.get(key);
assert!(!translated.is_empty(),
"Language {} has empty translation for key '{}'", code, key);
if !exceptions.contains(&key.as_str()) && !en_value.is_empty() {
if translated == en_value.as_str() && translated.len() > 3 {
}
}
}
}
}
}