Skip to main content

rust_i18n/
lib.rs

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