1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4extern crate alloc;
5extern crate core;
6
7pub mod escape;
8mod format;
9mod parse;
10#[cfg(test)]
11mod tests;
12mod translate;
13
14use alloc::{
15 borrow::Cow,
16 boxed::Box,
17 format,
18 rc::Rc,
19 string::{String, ToString},
20 sync::Arc,
21};
22use core::{
23 fmt::{Debug, Display, Formatter},
24 str::FromStr,
25};
26
27use compact_str::CompactString;
28
29#[derive(Debug)]
31pub struct InvalidFormat;
32
33impl Display for InvalidFormat {
34 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
35 write!(f, "invalid format")
36 }
37}
38
39#[cfg(feature = "std")]
40impl std::error::Error for InvalidFormat {}
41
42pub trait Resolver {
44 fn resolve<'s>(&'s self, template: &'s str) -> Cow<'s, str>;
46}
47
48macro_rules! impl_resolver_delegate {
49 ($typ:ty) => {
50 impl<T: Resolver> Resolver for $typ {
51 fn resolve<'s>(&'s self, template: &'s str) -> Cow<'s, str> {
52 Resolver::resolve(&**self, template)
53 }
54 }
55 };
56}
57
58impl_resolver_delegate!(&T);
59impl_resolver_delegate!(&mut T);
60impl_resolver_delegate!(Box<T>);
61impl_resolver_delegate!(Arc<T>);
62impl_resolver_delegate!(Rc<T>);
63
64#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
66pub struct NoResolver;
67
68impl Resolver for NoResolver {
69 fn resolve<'s>(&'s self, template: &'s str) -> Cow<'s, str> {
70 template.into()
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
99#[non_exhaustive]
100pub enum I18nString {
101 Literal(CompactString),
103 Template(CompactString, Box<[I18nString]>),
105}
106
107impl I18nString {
108 pub fn literal<S: Into<CompactString>>(s: S) -> Self {
121 Self::Literal(s.into())
122 }
123
124 pub fn template<S: Into<CompactString>, ARGS: IntoIterator<Item = I18nString>>(s: S, args: ARGS) -> Self {
136 Self::Template(s.into(), args.into_iter().collect())
137 }
138}
139
140impl FromStr for I18nString {
141 type Err = InvalidFormat;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 parse::parse(s)
145 }
146}
147
148impl Display for I18nString {
149 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
150 format::format_to(f, self)
151 }
152}
153
154#[cfg(feature = "serde")]
155impl serde::Serialize for I18nString {
156 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157 where
158 S: serde::Serializer,
159 {
160 serializer.serialize_str(&self.to_string())
161 }
162}
163
164#[cfg(feature = "serde")]
165impl<'de> serde::Deserialize<'de> for I18nString {
166 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
167 where
168 D: serde::Deserializer<'de>,
169 {
170 struct Visitor;
171
172 impl<'de> serde::de::Visitor<'de> for Visitor {
173 type Value = I18nString;
174
175 fn expecting(&self, formatter: &mut Formatter<'_>) -> core::fmt::Result {
176 formatter.write_str("a string")
177 }
178
179 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
180 where
181 E: serde::de::Error,
182 {
183 value.parse().map_err(serde::de::Error::custom)
184 }
185 }
186
187 deserializer.deserialize_str(Visitor)
188 }
189}
190
191pub trait I18nStringTranslateExt {
193 fn to_no_translate_string(&self) -> String;
205}
206
207impl I18nStringTranslateExt for I18nString {
208 fn to_no_translate_string(&self) -> String {
209 self.translate(NoResolver)
210 }
211}
212
213pub trait I18nStringBuilderExt {
215 fn display<D: Display + ?Sized>(display: &D) -> Self;
217
218 fn debug<D: Debug + ?Sized>(debug: &D) -> Self;
220
221 fn template_display<D: Display + ?Sized>(display: &D) -> Self;
223
224 fn template_debug<D: Debug + ?Sized>(debug: &D) -> Self;
226}
227
228impl I18nStringBuilderExt for I18nString {
229 fn display<D: Display + ?Sized>(display: &D) -> Self {
230 Self::literal(display.to_string())
231 }
232
233 fn debug<D: Debug + ?Sized>(debug: &D) -> Self {
234 Self::literal(format!("{:?}", debug))
235 }
236
237 fn template_display<D: Display + ?Sized>(display: &D) -> Self {
238 Self::template(display.to_string(), [])
239 }
240
241 fn template_debug<D: Debug + ?Sized>(debug: &D) -> Self {
242 Self::template(format!("{:?}", debug), [])
243 }
244}