Skip to main content

actix_cloud/
i18n.rs

1use std::collections::BTreeMap;
2
3pub use actix_cloud_codegen::i18n;
4
5/// Get I18n text
6///
7/// ```no_run
8/// use actix_cloud::{i18n::{i18n, Locale},t};
9///
10/// let mut locale = Locale::new("en-US").add_locale(i18n!("locale"));
11///
12/// // Get default locale's text
13/// t!(locale, "greeting");
14/// // With variables
15/// t!(locale, "messages.hello", name = "Jason");
16/// // Get a special locale's text
17/// t!(locale, "greeting", "de");
18/// // With locale and variables
19/// t!(locale, "messages.hello", "de", name = "Jason");
20/// ```
21#[macro_export]
22macro_rules! t {
23    ($l:expr, $key:expr) => {
24        $l.translate(&$l.default, $key)
25    };
26
27    ($l:expr, $key:expr, $($var_name:tt = $var_val:expr),+) => {
28        {
29            let mut message = $l.translate(&$l.default, $key);
30            $(
31                message = message.replace(concat!("%{", stringify!($var_name), "}"), $var_val);
32            )+
33            message
34        }
35    };
36
37    ($l:expr, $key:expr, $locale:expr) => {
38        $l.translate($locale, $key)
39    };
40
41    ($l:expr, $key:expr, $locale:expr, $($var_name:tt = $var_val:expr),+) => {
42        {
43            let mut message = $l.translate($locale, $key);
44            $(
45                message = message.replace(concat!("%{", stringify!($var_name), "}"), $var_val);
46            )+
47            message
48        }
49    };
50}
51
52/// Make map creation easier.
53///
54/// # Examples
55///
56/// ```
57/// use actix_cloud::map;
58/// let val = map!{"key" => "value"};
59/// ```
60#[macro_export]
61macro_rules! map {
62    {$($key:expr => $value:expr),+} => {{
63        let mut m = std::collections::BTreeMap::new();
64        $(
65            m.insert($key, $value);
66        )+
67        m
68    }};
69}
70
71/// Translation table plus the default locale used as fallback.
72///
73/// Load translations at compile time with `Locale::new("en-US").add_locale(i18n!("locale"))`
74/// and translate with the [`t!`] macro.
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76#[derive(Debug)]
77pub struct Locale {
78    /// Flattened map of `"locale.key"` to translated text.
79    pub locale: BTreeMap<String, String>,
80    /// Default locale identifier, e.g. `en-US`.
81    pub default: String,
82}
83
84impl Locale {
85    /// Create a locale table with `default` as the fallback locale.
86    pub fn new<S: Into<String>>(default: S) -> Self {
87        Self {
88            locale: BTreeMap::new(),
89            default: default.into(),
90        }
91    }
92
93    /// Add new locale items.
94    pub fn add_locale<S: Into<String>>(mut self, l: BTreeMap<S, S>) -> Self {
95        self.locale
96            .extend(l.into_iter().map(|(a, b)| (a.into(), b.into())));
97        self
98    }
99
100    /// Translate string.
101    /// - Fallback to default language if not exist.
102    /// - Again fallback to `key` if still not found.
103    pub fn translate<S1: AsRef<str>, S2: AsRef<str>>(&self, locale: S1, key: S2) -> String {
104        let locale_key = format!("{}.{}", locale.as_ref(), key.as_ref());
105        self.locale.get(locale_key.as_str()).map_or_else(
106            || {
107                if locale.as_ref() == self.default {
108                    key.as_ref().to_owned()
109                } else {
110                    self.translate(&self.default, key)
111                }
112            },
113            ToString::to_string,
114        )
115    }
116}