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}