use std::collections::HashMap;
use std::sync::Arc;
use gpui::{App, Global, SharedString};
use super::locale::{Locale, SupportedLocale, TextDirection};
use super::loader::{EmbeddedLoader, TranslationLoader};
pub struct I18n {
pub current_locale: Locale,
pub available_locales: Vec<SupportedLocale>,
translations: HashMap<Locale, Arc<TranslationMap>>,
}
impl Global for I18n {}
impl I18n {
pub fn new() -> Self {
Self::with_locale(Locale::default())
}
pub fn with_embedded(locale: Locale) -> Self {
let mut i18n = Self::with_locale(locale);
i18n.load_all_embedded();
i18n
}
pub fn load_all_embedded(&mut self) {
let loader = EmbeddedLoader::new();
for supported in SupportedLocale::all() {
let locale = supported.to_locale();
if loader.is_available(&locale) {
if let Ok(map) = loader.load(&locale) {
self.load_translations(locale, map);
}
}
}
}
pub fn with_locale(locale: Locale) -> Self {
Self {
current_locale: locale,
available_locales: SupportedLocale::all().to_vec(),
translations: HashMap::new(),
}
}
pub fn set_locale(&mut self, locale: Locale) {
self.current_locale = locale;
}
pub fn locale(&self) -> &Locale {
&self.current_locale
}
pub fn text_direction(&self) -> TextDirection {
self.current_locale.text_direction()
}
pub fn is_rtl(&self) -> bool {
self.text_direction().is_rtl()
}
pub fn load_translations(&mut self, locale: Locale, translations: TranslationMap) {
self.translations.insert(locale, Arc::new(translations));
}
pub fn merge_translations(&mut self, locale: Locale, translations: TranslationMap) {
match self.translations.get_mut(&locale) {
Some(existing) => {
let existing_map = Arc::make_mut(existing);
existing_map.merge(translations);
}
None => {
self.load_translations(locale, translations);
}
}
}
pub fn translations(&self) -> Option<&Arc<TranslationMap>> {
self.translations.get(&self.current_locale)
}
pub fn t(&self, key: &str) -> Option<&str> {
self.translations()?.get(key)
}
}
impl Default for I18n {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Default)]
pub struct TranslationMap {
values: HashMap<String, String>,
nested: HashMap<String, TranslationMap>,
}
impl TranslationMap {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, key: &str, value: &str) {
self.values.insert(key.to_string(), value.to_string());
}
pub fn insert_nested(&mut self, key: &str, map: TranslationMap) {
self.nested.insert(key.to_string(), map);
}
pub fn get(&self, key: &str) -> Option<&str> {
if let Some(value) = self.values.get(key) {
return Some(value);
}
let parts: Vec<&str> = key.split('.').collect();
if parts.len() < 2 {
return None;
}
let mut current = self;
for (i, part) in parts.iter().enumerate() {
if i == parts.len() - 1 {
return current.values.get(*part).map(|s| s.as_str());
} else {
current = current.nested.get(*part)?;
}
}
None
}
pub fn values(&self) -> &HashMap<String, String> {
&self.values
}
pub fn nested(&self) -> &HashMap<String, TranslationMap> {
&self.nested
}
pub fn merge(&mut self, other: TranslationMap) {
for (key, value) in other.values {
self.values.insert(key, value);
}
for (key, nested_other) in other.nested {
match self.nested.get_mut(&key) {
Some(existing) => existing.merge(nested_other),
None => {
self.nested.insert(key, nested_other);
}
}
}
}
}
pub trait I18nContext {
fn i18n(&self) -> &I18n;
}
impl I18nContext for App {
fn i18n(&self) -> &I18n {
self.global::<I18n>()
}
}
pub trait Translate {
fn t(&self, key: &str) -> SharedString;
fn t_with_args(&self, key: &str, args: &HashMap<&str, &str>) -> SharedString;
}
impl Translate for App {
fn t(&self, key: &str) -> SharedString {
let i18n = self.i18n();
match i18n.t(key) {
Some(s) => s.to_string().into(),
None => key.to_string().into(),
}
}
fn t_with_args(&self, key: &str, args: &HashMap<&str, &str>) -> SharedString {
let i18n = self.i18n();
let base = match i18n.t(key) {
Some(s) => s.to_string(),
None => key.to_string(),
};
replace_placeholders(&base, args).into()
}
}
fn replace_placeholders(template: &str, args: &HashMap<&str, &str>) -> String {
let mut result = template.to_string();
for (key, value) in args {
let placeholder = format!("{{{}}}", key);
result = result.replace(&placeholder, value);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_translation_map_nested() {
let mut map = TranslationMap::new();
map.insert("hello", "Hello");
let mut nested = TranslationMap::new();
nested.insert("placeholder", "Select…");
map.insert_nested("select", nested);
assert_eq!(map.get("hello"), Some("Hello"));
assert_eq!(map.get("select.placeholder"), Some("Select…"));
}
#[test]
fn test_replace_placeholders() {
let template = "Hello {name}, you have {count} items";
let mut args = HashMap::new();
args.insert("name", "World");
args.insert("count", "5");
let result = replace_placeholders(template, &args);
assert_eq!(result, "Hello World, you have 5 items");
}
}