loon_embed/lib.rs
1/*!
2[](https://github.com/longbridgeapp/loon/actions/workflows/ci.yml) [](https://docs.rs/loon-embed/) [](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}