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}