simple_i18n/lib.rs
1// #![doc = include_str!("../readme.md")]
2// wait stable 1.54.0
3//! # i18n-rs
4//!
5//! 
6//! 
7//! 
8//! [](https://docs.rs/simple-i18n)
9//! [](https://rust-reportcard.xuri.me/report/github.com/juzi5201314/i18n-rs)
10//! [](https://deps.rs/repo/github/juzi5201314/i18n-rs)
11//!
12//! A simple compile time i18n implementation in Rust.
13//!
14//! > *This is a personal project.
15//! If you need a stable and powerful i18n library,
16//! you may need [fluent](https://github.com/projectfluent/fluent-rs).*
17//!
18//! > If you think this crate is not easy to use, I found another similar crate: [https://github.com/terry90/internationalization-rs](https://github.com/terry90/internationalization-rs)
19//!
20//! ## Use
21//! In crates.io, the name of this package is `simple-i18n`, because the name of `i18n-rs` is occupied by an empty crate. shit...
22//!
23//! Add `simple-i18n = "0.1"` to Cargo.toml
24//!
25//! ## Examples
26//! Look [i18n-example](./examples/i18n-example)
27//! ```shell
28//! cd examples/i18n-example
29//! LOCALE_PATH=locale cargo run --package i18n-example --bin i18n-example
30//! ```
31//!
32//! ## [Docs](https://docs.rs/simple-i18n)
33//! [docs.rs](https://docs.rs/simple-i18n)
34//!
35//! [Repo](https://github.com/juzi5201314/i18n-rs)
36//!
37//! i18n-rs will load your locale (toml, json or yaml) into the code during compilation.
38//! Then you can use `lang!` (to switch the locale) and use `i18n` to get the text.
39//!
40//! ### LOCALE_PATH
41//!
42//! The `LOCALE_PATH` environment variable is used to find your locale file.
43//!
44//! *Please note that because the dependent library cannot get the current path where you actually run the build command during compilation, the safest method is actually to use the absolute path.*
45//!
46//! Usually we will store locale files in the `locale` directory in the project root directory and set `LOCALE_PATH` to `locale`.
47//!
48//! The current behavior is:
49//!
50//! >i18n-rs will find `${workspace_root}/$LOCALE_PATH` in `OUT_DIR` or `./` using `cargo metadata`.
51//!
52//! >In other words, if `LOCALE_PATH` is a relative path, it should be based on the workspace_root of the project, not the user's current path.
53//!
54//! >And it is best to run the build command in the project root directory.
55//!
56//! ### Locale files
57//! The locale file supports `json`, `toml`, `yaml`.
58//!
59//! You can use a single file or use a folder. In the case of a single file, the language code is the file name.
60//!
61//! In the case of a folder, the folder name is language code, and the subfolder and file name will be used as the field name.
62//!
63//! You can add `.` in front of the file name to avoid becoming a field name.
64//!
65//! The content will be flattened, and the key will be linked together with `.` to become the field name.
66//!
67//! Example:
68//! ```json
69//! {
70//! "words": {
71//! "greetings": {
72//! "hi": "Hi!"
73//! }
74//! }
75//! }
76//! ```
77//! equal
78//! ```json
79//! {
80//! "words.greetings.hi": "Hi!"
81//! }
82//! ```
83//!
84//! ### Strict and Loose
85//! > By default, strict checking will be used.
86//!
87//! In loose mode, if you try to get a non-existent field or a non-existent locale, the field itself will be returned.
88//!
89//! But strict mode will check your input in `lang!` and `i18n!` to make sure that you are using the existing locale and fields that exist in all locales.
90//!
91//! If there is an error, it will be `panic!`.
92//!
93//! Don't worry, all of this is checked at compile time,
94//! so strict checking will hardly affect runtime performance,
95//! and there will be not panic at runtime.
96//!
97//! > note: Because it needs to be checked at compile time,
98//! string literals must be used in strict mode
99//!
100//! Fortunately, We can freely switch between loose and strict mode.
101//! like `i18n!("xxx.x"; loose)`.
102//!
103//! ## Benchmark
104//! ```text
105//! strict contrast/no strict
106//! time: [29.048 ns 29.387 ns 29.736 ns]
107//! change: [-15.897% -13.053% -10.253%] (p = 0.00 < 0.05)
108//! Performance has improved.
109//! Found 1 outliers among 100 measurements (1.00%)
110//! 1 (1.00%) high mild
111//!
112//! strict contrast/strict time: [29.108 ns 29.431 ns 29.776 ns]
113//! change: [-2.6412% -0.8426% +1.0984%] (p = 0.38 > 0.05)
114//! No change in performance detected.
115//! Found 4 outliers among 100 measurements (4.00%)
116//! 2 (2.00%) high mild
117//! 2 (2.00%) high severe
118//!
119//! change_lang time: [148.38 ns 159.76 ns 178.01 ns]
120//! change: [+0.4039% +4.5240% +10.326%] (p = 0.05 > 0.05)
121//! No change in performance detected.
122//! Found 5 outliers among 100 measurements (5.00%)
123//! 3 (3.00%) high mild
124//! 2 (2.00%) high severe
125//! ```
126
127use std::borrow::Cow;
128use std::sync::Arc;
129
130use arc_swap::ArcSwap;
131use once_cell::sync::Lazy;
132
133use i18n_macro::build_match_func;
134#[doc(hidden)]
135pub use i18n_macro::{check_field, check_language};
136
137build_match_func!();
138
139/// Get text
140/// `i18n!("words.hello")`
141/// `i18n!("words.hello"; loose)`
142#[macro_export]
143macro_rules! i18n {
144 ($field:expr) => {{
145 $crate::check_field!($field);
146 $crate::i18n!($field; loose)
147 }};
148 ($field:expr; loose) => {
149 $crate::_match_message($crate::Language::now().name().as_ref(), $field)
150 .unwrap_or_else(|| $field)
151 };
152}
153/// Change language
154/// `lang!("en-us");`
155/// `lang!("en-us"; loose);`
156#[macro_export]
157macro_rules! lang {
158 ($lang:expr) => {
159 $crate::check_language!($lang);
160 $crate::lang!($lang; loose)
161 };
162 ($lang:expr; loose) => {
163 $crate::Language::set($crate::Language::from($lang))
164 };
165}
166
167static LANG: Lazy<Arc<ArcSwap<Language>>> =
168 Lazy::new(|| Arc::new(ArcSwap::new(Arc::new(Language::default()))));
169
170pub struct Language {
171 name: String,
172}
173
174impl Language {
175 pub fn new(code: impl ToString) -> Self {
176 Language {
177 name: code.to_string(),
178 }
179 }
180
181 pub fn set(language: Language) {
182 LANG.store(Arc::new(language))
183 }
184
185 pub fn now() -> Arc<Language> {
186 Arc::clone(&LANG.load())
187 }
188
189 pub fn name(&self) -> Cow<str> {
190 Cow::Borrowed(&self.name)
191 }
192}
193
194impl Default for Language {
195 fn default() -> Self {
196 Language {
197 name: _langs().first().unwrap_or(&"").to_string(),
198 }
199 }
200}
201
202impl<T> From<T> for Language
203where
204 T: ToString,
205{
206 fn from(code: T) -> Self {
207 Language::new(code)
208 }
209}