dioxus_std/i18n/
use_i18n.rs

1use dioxus::prelude::*;
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, str::FromStr};
4use unic_langid::LanguageIdentifier;
5
6use super::use_init_i18n::UseInitI18Data;
7
8#[derive(Debug, Clone, Default, Deserialize, Serialize)]
9pub struct Language {
10    id: LanguageIdentifier,
11    texts: Text,
12}
13
14#[derive(Debug, Clone, Deserialize, Serialize)]
15#[serde(untagged)]
16pub enum Text {
17    Value(String),
18    Texts(HashMap<String, Text>),
19}
20
21impl Default for Text {
22    fn default() -> Self {
23        Self::Texts(HashMap::default())
24    }
25}
26
27impl Text {
28    fn query(&self, steps: &mut Vec<&str>) -> Option<String> {
29        match self {
30            Text::Texts(texts) => {
31                if steps.is_empty() {
32                    return None;
33                }
34
35                let current_path = steps.join(".");
36
37                // Try querying the next step in this list
38                let this_step = steps.remove(0);
39                let deep = texts.get(this_step)?;
40                let res = deep.query(steps);
41
42                // If not found try querying by the whole remaining path as if it was the ID
43                if res.is_none() {
44                    let res_text = texts.get(&current_path);
45                    if let Some(res_text) = res_text {
46                        return res_text.query(steps);
47                    }
48                }
49                res
50            }
51            Text::Value(value) => Some(value.to_owned()),
52        }
53    }
54}
55
56impl FromStr for Language {
57    type Err = ();
58
59    fn from_str(s: &str) -> Result<Self, Self::Err> {
60        serde_json::from_str(s).map_err(|_| ())
61    }
62}
63
64impl Language {
65    pub fn get_text(&self, path: &str, params: HashMap<&str, String>) -> Option<String> {
66        let mut steps = path.split('.').collect::<Vec<&str>>();
67
68        let mut text = self.texts.query(&mut steps).unwrap_or_default();
69
70        for (name, value) in params {
71            text = text.replacen(&format!("{{{name}}}"), &value.to_string(), 1);
72        }
73        Some(text)
74    }
75}
76
77#[derive(Clone, PartialEq, Copy)]
78pub struct UseI18 {
79    pub selected_language: Signal<LanguageIdentifier>,
80    pub data: Signal<UseInitI18Data>,
81}
82
83impl UseI18 {
84    pub fn translate_with_params(&self, id: &str, params: HashMap<&str, String>) -> String {
85        let i18n_data = self.data.read();
86
87        // Try searching in the selected language
88        for language in i18n_data.languages.iter() {
89            if language.id == *self.selected_language.read() {
90                return language.get_text(id, params).unwrap_or_default();
91            }
92        }
93
94        // Otherwise find in the fallback language
95        for language in i18n_data.languages.iter() {
96            if language.id == i18n_data.fallback_language {
97                return language.get_text(id, params).unwrap_or_default();
98            }
99        }
100
101        // Return the ID as there is no alternative
102        id.to_string()
103    }
104
105    pub fn translate(&self, id: &str) -> String {
106        self.translate_with_params(id, HashMap::default())
107    }
108
109    pub fn set_language(&mut self, id: LanguageIdentifier) {
110        *self.selected_language.write() = id;
111    }
112}
113
114pub fn use_i18() -> UseI18 {
115    use_hook(|| {
116        let selected_language = consume_context::<Signal<LanguageIdentifier>>();
117        let data = consume_context::<Signal<UseInitI18Data>>();
118
119        UseI18 {
120            selected_language,
121            data,
122        }
123    })
124}