use wxdragon::{translations::Translations, widgets::choice::Choice};
use crate::{LanguageInfo, LanguageManager};
pub struct WxTranslationManager {
domain: String,
current_language: String,
available_languages: Vec<LanguageInfo>,
initialized: bool,
}
impl WxTranslationManager {
#[must_use]
pub fn new(domain: &str) -> Self {
Self {
domain: domain.to_string(),
current_language: "en".to_string(),
available_languages: vec![LanguageInfo { code: "en".to_string(), name: "English".to_string() }],
initialized: false,
}
}
pub fn initialize(&mut self, wxstd_loader: impl wxdragon::translations::TranslationsLoader + 'static) -> bool {
if self.initialized {
return true;
}
let translations = Translations::new();
translations.set_loader(wxstd_loader);
let mgr = LanguageManager::new(&self.domain);
self.available_languages = mgr.available();
let raw_sys_lang = LanguageManager::system_language();
self.current_language =
best_available_language(&raw_sys_lang, &self.available_languages).unwrap_or_else(|| "en".to_string());
if self.current_language != "en" {
translations.set_language_str(&self.current_language);
}
translations.add_std_catalog();
Translations::set_global(translations);
crate::set_default_domain(&self.domain);
crate::set_locale(&self.current_language);
self.initialized = true;
true
}
pub fn set_language(
&mut self,
language_code: &str,
wxstd_loader: impl wxdragon::translations::TranslationsLoader + 'static,
) -> bool {
if !self.initialized || !self.is_language_available(language_code) {
return false;
}
self.current_language = language_code.to_string();
let translations = Translations::new();
translations.set_loader(wxstd_loader);
translations.set_language_str(language_code);
translations.add_std_catalog();
Translations::set_global(translations);
crate::set_locale(language_code);
true
}
#[must_use]
pub fn current_language(&self) -> String {
self.current_language.clone()
}
#[must_use]
pub fn available_languages(&self) -> Vec<LanguageInfo> {
self.available_languages.clone()
}
#[must_use]
pub fn is_language_available(&self, language_code: &str) -> bool {
self.available_languages.iter().any(|lang| lang.code == language_code)
}
}
#[must_use]
pub fn best_available_language(system_code: &str, available: &[LanguageInfo]) -> Option<String> {
let language_of = |code: &str| code.split(['_', '-']).next().unwrap_or(code).to_lowercase();
let wanted = system_code.to_lowercase();
let language = language_of(system_code);
let find = |matches: &dyn Fn(&str) -> bool| available.iter().find(|l| matches(&l.code)).map(|l| l.code.clone());
find(&|code| code == system_code)
.or_else(|| find(&|code| code.to_lowercase() == wanted))
.or_else(|| find(&|code| code.to_lowercase() == language))
.or_else(|| find(&|code| language_of(code) == language))
}
pub fn populate_language_choice(combo: &Choice, languages: &[LanguageInfo]) -> Vec<String> {
let mut codes = Vec::with_capacity(languages.len());
for lang in languages {
combo.append(&lang.name);
codes.push(lang.code.clone());
}
codes
}
#[must_use]
pub fn resolve_language_choice(combo: &Choice, codes: &[String]) -> Option<String> {
combo.get_selection().and_then(|index| usize::try_from(index).ok()).and_then(|index| codes.get(index).cloned())
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use wxdragon::translations::TranslationsLoader;
use super::*;
struct NoopLoader;
impl TranslationsLoader for NoopLoader {
fn load_catalog(&self, _domain: &str, _lang: &str) -> Option<Cow<'_, [u8]>> {
None
}
fn available_translations(&self, _domain: &str) -> Vec<String> {
Vec::new()
}
}
#[test]
fn new_manager_has_english_available_by_default() {
let manager = WxTranslationManager::new("test_domain");
assert_eq!(manager.current_language(), "en");
assert!(manager.is_language_available("en"));
assert!(!manager.is_language_available("zz"));
}
#[test]
fn set_language_fails_when_not_initialized() {
let mut manager = WxTranslationManager::new("test_domain");
assert!(!manager.set_language("en", NoopLoader));
assert_eq!(manager.current_language(), "en");
}
#[test]
fn available_languages_returns_clone() {
let manager = WxTranslationManager::new("test_domain");
let mut langs = manager.available_languages();
langs.push(LanguageInfo { code: "xx".to_string(), name: "Fake".to_string() });
assert!(!manager.is_language_available("xx"));
}
fn available(codes: &[&str]) -> Vec<LanguageInfo> {
codes.iter().map(|c| LanguageInfo { code: (*c).to_string(), name: (*c).to_string() }).collect()
}
#[test]
fn an_exact_code_wins() {
let langs = available(&["en", "de", "zh_CN"]);
assert_eq!(best_available_language("zh_CN", &langs).as_deref(), Some("zh_CN"));
}
#[test]
fn the_same_code_in_another_case_still_matches() {
let langs = available(&["en", "pt_br"]);
assert_eq!(best_available_language("pt_BR", &langs).as_deref(), Some("pt_br"));
}
#[test]
fn a_region_the_catalogs_do_not_have_falls_back_to_the_language() {
let langs = available(&["en", "de"]);
assert_eq!(best_available_language("de_DE", &langs).as_deref(), Some("de"));
}
#[test]
fn another_region_of_the_same_language_is_better_than_english() {
let langs = available(&["en", "zh_CN"]);
assert_eq!(best_available_language("zh_TW", &langs).as_deref(), Some("zh_CN"));
}
#[test]
fn a_language_with_no_catalog_matches_nothing() {
let langs = available(&["en", "de"]);
assert_eq!(best_available_language("ja_JP", &langs), None);
}
}