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