rust_i18n/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::ops::Deref;
4
5use once_cell::sync::Lazy;
6
7#[doc(hidden)]
8pub use once_cell;
9#[doc(hidden)]
10pub use rust_i18n_macro::{_minify_key, _tr, i18n};
11pub use rust_i18n_support::{
12    AtomicStr, Backend, BackendExt, CowStr, MinifyKey, SimpleBackend, DEFAULT_MINIFY_KEY,
13    DEFAULT_MINIFY_KEY_LEN, DEFAULT_MINIFY_KEY_PREFIX, DEFAULT_MINIFY_KEY_THRESH,
14};
15
16static CURRENT_LOCALE: Lazy<AtomicStr> = Lazy::new(|| AtomicStr::from("en"));
17
18/// Set current locale
19pub fn set_locale(locale: &str) {
20    CURRENT_LOCALE.replace(locale);
21}
22
23/// Get current locale
24pub fn locale() -> impl Deref<Target = str> {
25    CURRENT_LOCALE.as_str()
26}
27
28/// Replace patterns and return a new string.
29///
30/// # Arguments
31///
32/// * `input` - The input string, containing patterns like `%{name}`.
33/// * `patterns` - The patterns to replace.
34/// * `values` - The values to replace.
35///
36/// # Example
37///
38/// ```
39/// # use rust_i18n::replace_patterns;
40/// let input = "Hello, %{name}!";
41/// let patterns = &["name"];
42/// let values = &["world".to_string()];
43/// let output = replace_patterns(input, patterns, values);
44/// assert_eq!(output, "Hello, world!");
45/// ```
46pub fn replace_patterns(input: &str, patterns: &[&str], values: &[String]) -> String {
47    let input_bytes = input.as_bytes();
48    let mut pattern_pos = smallvec::SmallVec::<[usize; 64]>::new();
49    let mut stage = 0;
50    for (i, &b) in input_bytes.iter().enumerate() {
51        match (stage, b) {
52            (1, b'{') => {
53                stage = 2;
54                pattern_pos.push(i);
55            }
56            (2, b'}') => {
57                stage = 0;
58                pattern_pos.push(i);
59            }
60            (_, b'%') => {
61                stage = 1;
62            }
63            _ => {}
64        }
65    }
66    let mut output: Vec<u8> = Vec::with_capacity(input_bytes.len() + 128);
67    let mut prev_end = 0;
68    let pattern_values = patterns.iter().zip(values.iter());
69    for pos in pattern_pos.chunks_exact(2) {
70        let start = pos[0];
71        let end = pos[1];
72        let key = &input_bytes[start + 1..end];
73        if prev_end < start {
74            let prev_chunk = &input_bytes[prev_end..start - 1];
75            output.extend_from_slice(prev_chunk);
76        }
77        if let Some((_, v)) = pattern_values
78            .clone()
79            .find(|(&pattern, _)| pattern.as_bytes() == key)
80        {
81            output.extend_from_slice(v.as_bytes());
82        } else {
83            output.extend_from_slice(&input_bytes[start - 1..end + 1]);
84        }
85        prev_end = end + 1;
86    }
87    if prev_end < input_bytes.len() {
88        let remaining = &input_bytes[prev_end..];
89        output.extend_from_slice(remaining);
90    }
91    unsafe { String::from_utf8_unchecked(output) }
92}
93
94/// Get I18n text
95///
96/// This macro forwards to the `crate::_rust_i18n_t!` macro, which is generated by the [`i18n!`] macro.
97///
98/// # Arguments
99///
100/// * `expr` - The key or message for translation.
101///   - A key usually looks like `"foo.bar.baz"`.
102///   - A literal message usually looks like `"Hello, world!"`.
103///   - The variable names in the message should be wrapped in `%{}`, like `"Hello, %{name}!"`.
104///   - Dynamic messages are also supported, such as `t!(format!("Hello, {}!", name))`.
105///     However, if `minify_key` is enabled, the entire message will be hashed and used as a key for every lookup, which may consume more CPU cycles.
106/// * `locale` - The locale to use. If not specified, the current locale will be used.
107/// * `args` - The arguments to be replaced in the translated text.
108///    - These should be passed in the format `key = value` or `key => value`.
109///    - Alternatively, you can specify the value format using the `key = value : {:format_specifier}` syntax.
110///      For example, `key = value : {:08}` will format the value as a zero-padded string with a length of 8.
111///
112/// # Example
113///
114/// ```no_run
115/// #[macro_use] extern crate rust_i18n;
116///
117/// # macro_rules! t { ($($all:tt)*) => {} }
118/// # fn main() {
119/// // Simple get text with current locale
120/// t!("greeting");
121/// // greeting: "Hello world" => "Hello world"
122///
123/// // Get a special locale's text
124/// t!("greeting", locale = "de");
125/// // greeting: "Hallo Welt!" => "Hallo Welt!"
126///
127/// // With variables
128/// t!("messages.hello", name = "world");
129/// // messages.hello: "Hello, %{name}" => "Hello, world"
130/// t!("messages.foo", name = "Foo", other ="Bar");
131/// // messages.foo: "Hello, %{name} and %{other}" => "Hello, Foo and Bar"
132///
133/// // With variables and format specifiers
134/// t!("Hello, %{name}, you serial number is: %{sn}", name = "Jason", sn = 123 : {:08});
135/// // => "Hello, Jason, you serial number is: 000000123"
136///
137/// // With locale and variables
138/// t!("messages.hello", locale = "de", name = "Jason");
139/// // messages.hello: "Hallo, %{name}" => "Hallo, Jason"
140/// # }
141/// ```
142#[macro_export]
143#[allow(clippy::crate_in_macro_def)]
144macro_rules! t {
145    ($($all:tt)*) => {
146        crate::_rust_i18n_t!($($all)*)
147    }
148}
149
150/// A macro that generates a translation key and corresponding value pair from a given input value.
151///
152/// It's useful when you want to use a long string as a key, but you don't want to type it twice.
153///
154/// # Arguments
155///
156/// * `msg` - The input value.
157///
158/// # Returns
159///
160/// A tuple of `(key, msg)`.
161///
162/// # Example
163///
164/// ```no_run
165/// use rust_i18n::{t, tkv};
166///
167/// # macro_rules! t { ($($all:tt)*) => { } }
168/// # macro_rules! tkv { ($($all:tt)*) => { (1,2) } }
169///
170/// let (key, msg) = tkv!("Hello world");
171/// // => key is `"Hello world"` and msg is the translated message.
172/// // => If there is hints the minify_key logic, the key will returns a minify key.
173/// ```
174#[macro_export]
175#[allow(clippy::crate_in_macro_def)]
176macro_rules! tkv {
177    ($msg:literal) => {
178        crate::_rust_i18n_tkv!($msg)
179    };
180}
181
182/// Get available locales
183///
184/// ```no_run
185/// #[macro_use] extern crate rust_i18n;
186/// # pub fn _rust_i18n_available_locales() -> Vec<&'static str> { todo!() }
187/// # fn main() {
188/// rust_i18n::available_locales!();
189/// # }
190/// // => ["en", "zh-CN"]
191/// ```
192#[macro_export(local_inner_macros)]
193#[allow(clippy::crate_in_macro_def)]
194macro_rules! available_locales {
195    () => {
196        crate::_rust_i18n_available_locales()
197    };
198}
199
200#[cfg(test)]
201mod tests {
202    use crate::{locale, CURRENT_LOCALE};
203
204    fn assert_locale_type(s: &str, val: &str) {
205        assert_eq!(s, val);
206    }
207
208    #[test]
209    fn test_locale() {
210        assert_locale_type(&locale(), &CURRENT_LOCALE.as_str());
211        assert_eq!(&*locale(), "en");
212    }
213}