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