loon_embed/
lib.rs

1/*!
2[![CI](https://github.com/longbridgeapp/loon/actions/workflows/ci.yml/badge.svg)](https://github.com/longbridgeapp/loon/actions/workflows/ci.yml) [![Docs](https://docs.rs/loon-embed/badge.svg)](https://docs.rs/loon-embed/) [![Crates.io](https://img.shields.io/crates/v/loon-embed.svg)](https://crates.io/crates/loon-embed)
3
4Loon is a localization/internationalization library, inspired by [ruby-i18n](https://github.com/ruby-i18n/i18n).
5
6It use [rust-embed](https://crates.io/crates/rust-embed) for embed the localization assets into your binary.
7
8### Usage
9
10Load locales assets by RustEmbed and init Loon in your `lib.rs`
11
12```ignore
13use rust_embed::RustEmbed;
14
15// Load Loon macro, for allow you use `t!` macro in anywhere.
16#[macro_use]
17extern crate loon_embed;
18
19// Use RustEmbed to locale assets
20#[derive(RustEmbed)]
21#[folder = "locales/"]
22#[include = "*.yml"]
23struct Asset;
24
25fn main() {
26    loon_embed::init::<Asset>("en");
27}
28```
29
30You must put I18n YAML files in `locales/` folder.
31
32```bash
33locales/
34├── en.yml
35├── zh-CN.yml
36```
37
38For example of `en.yml`:
39
40```yml
41greeting: Hello world
42messages:
43  hello: Hello, {}
44```
45
46Now you can use `t!` macro in anywhere.
47
48```ignore
49t!("greeting");
50// => "Hello world"
51
52t!("messages.hello", "world");
53// => "Hello, world"
54```
55
56You can use `loon_embed::set_locale` or call `loon_embed::init` agian to change the current locale in runtime.
57
58```rs
59loon_embed::set_locale("zh-CN");
60loon_embed::locale();
61// => "zh-CN"
62```
63*/
64mod config;
65mod dict;
66mod err;
67mod key;
68mod opts;
69use std::sync::Once;
70
71/// Get I18n text with current locale
72///
73/// ```ignore
74/// t!("greeting"); // greeting: "Hello world" => "Hello world"
75/// t!("messages.hello", "world"); // messages.hello: "Hello, {}" => "Hello, world"
76/// ```
77#[macro_export]
78macro_rules! t {
79    ($key:expr) => {
80        $crate::translate($key)
81    };
82    ($key:expr, $($arg:tt)+) => {
83        {
84            let mut message = $crate::translate($key);
85            $(
86                message = message.replace("{}", $arg);
87            )+
88            message
89        }
90    };
91}
92
93use config::Config;
94use std::sync::Mutex;
95
96#[macro_use]
97extern crate lazy_static;
98
99struct CurrentDictionary {
100    default_locale: String,
101    dict: dict::Dictionary,
102}
103
104impl Default for CurrentDictionary {
105    fn default() -> Self {
106        let default_locale = "en".to_string();
107        let dict = Config::default().finish().unwrap();
108
109        CurrentDictionary {
110            default_locale,
111            dict,
112        }
113    }
114}
115
116impl CurrentDictionary {
117    fn set_locale(&mut self, locale: &str) {
118        self.default_locale = locale.to_string();
119        self.dict.default_locale = locale.to_string();
120    }
121}
122
123lazy_static! {
124    static ref _CURRENT_DICTIONARY: Mutex<CurrentDictionary> =
125        Mutex::new(CurrentDictionary::default());
126}
127
128static INITIALIZED: Once = Once::new();
129
130/// Init I18n locales assets from Rust Embed and setup default_locale.
131///
132/// This method only init asset dictionary once,
133/// when Assets has been initialized, just update default_locale.
134///
135/// ```ignore
136/// use rust_embed::RustEmbed;
137///
138/// // Use RustEmbed to locale assets
139/// #[derive(RustEmbed)]
140/// #[folder = "locales/"]
141/// #[include = "*.yml"]
142/// struct Asset;
143
144/// loon_embed::init::<Asset>("en");
145/// ```
146pub fn init<T: rust_embed::RustEmbed>(default_locale: &str) {
147    let mut current_dict = _CURRENT_DICTIONARY.lock().unwrap();
148    INITIALIZED.call_once(|| {
149        current_dict.dict = Config::default().with_embed::<T>().finish().unwrap();
150    });
151
152    current_dict.set_locale(default_locale);
153}
154
155/// Set current locale
156///
157/// ```
158/// loon_embed::set_locale("en");
159/// // or
160/// loon_embed::set_locale("zh-CN");
161/// ```
162pub fn set_locale(locale: &str) {
163    _CURRENT_DICTIONARY.lock().unwrap().set_locale(locale);
164}
165
166/// Get current locale
167///
168/// ```
169/// loon_embed::set_locale("zh-CN");
170/// loon_embed::locale();
171/// // "zh-CN"
172/// ```
173pub fn locale() -> String {
174    _CURRENT_DICTIONARY.lock().unwrap().default_locale.clone()
175}
176
177#[allow(dead_code)]
178pub fn translate(key: &str) -> String {
179    return _CURRENT_DICTIONARY
180        .lock()
181        .unwrap()
182        .dict
183        .t(key, None)
184        .unwrap_or_else(|_| key.to_string());
185}
186
187#[cfg(test)]
188mod tests {
189    use rust_embed::RustEmbed;
190
191    #[derive(RustEmbed)]
192    #[folder = "examples/locales/"]
193    struct Asset;
194
195    #[test]
196    fn test_translate() {
197        crate::init::<Asset>("en");
198        assert_eq!(t!("messages.zero"), "You have no messages.");
199        assert_eq!(t!("messages.hello", "world"), "Hello, world!");
200
201        crate::set_locale("de");
202        assert_eq!(t!("messages.hello", "world"), "messages.hello");
203    }
204}