1use std::fmt::{self, Display, Formatter};
2
3use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer};
4#[cfg(feature = "simdnbt")]
5use simdnbt::Serialize as _;
6
7use crate::{
8 base_component::BaseComponent, style::Style, text_component::TextComponent, FormattedText,
9};
10
11#[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)]
12#[serde(untagged)]
13pub enum StringOrComponent {
14 String(String),
15 FormattedText(FormattedText),
16}
17
18#[cfg(feature = "simdnbt")]
19impl simdnbt::ToNbtTag for StringOrComponent {
20 fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
21 match self {
22 StringOrComponent::String(s) => s.to_nbt_tag(),
23 StringOrComponent::FormattedText(c) => c.to_nbt_tag(),
24 }
25 }
26}
27
28#[derive(Clone, Debug, PartialEq, Eq, Hash)]
30pub struct TranslatableComponent {
31 pub base: BaseComponent,
32 pub key: String,
33 pub args: Vec<StringOrComponent>,
34}
35
36impl Serialize for TranslatableComponent {
37 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
38 where
39 S: Serializer,
40 {
41 let mut state = serializer.serialize_map(None)?;
42 state.serialize_entry("translate", &self.key)?;
43 Serialize::serialize(&self.base, FlatMapSerializer(&mut state))?;
44 state.serialize_entry("with", &self.args)?;
45 state.end()
46 }
47}
48
49#[cfg(feature = "simdnbt")]
50fn serialize_args_as_nbt(args: &[StringOrComponent]) -> simdnbt::owned::NbtList {
51 use tracing::debug;
56
57 let mut string_list = Vec::new();
58 let mut compound_list = Vec::new();
59
60 for arg in args {
61 match arg {
62 StringOrComponent::String(s) => {
63 string_list.push(s.clone());
64 }
65 StringOrComponent::FormattedText(c) => {
66 compound_list.push(c.clone().to_compound());
67 }
68 }
69 }
70
71 if !string_list.is_empty() && !compound_list.is_empty() {
72 debug!("Tried to serialize a TranslatableComponent with a mix of strings and components.");
75 return string_list.into();
76 }
77
78 if !string_list.is_empty() {
79 return string_list.into();
80 }
81
82 compound_list.into()
83}
84
85#[cfg(feature = "simdnbt")]
86impl simdnbt::Serialize for TranslatableComponent {
87 fn to_compound(self) -> simdnbt::owned::NbtCompound {
88 let mut compound = simdnbt::owned::NbtCompound::new();
89 compound.insert("translate", self.key);
90 compound.extend(self.base.style.to_compound());
91
92 compound.insert("with", serialize_args_as_nbt(&self.args));
93 compound
94 }
95}
96
97impl TranslatableComponent {
98 pub fn new(key: String, args: Vec<StringOrComponent>) -> Self {
99 Self {
100 base: BaseComponent::new(),
101 key,
102 args,
103 }
104 }
105
106 pub fn read(&self) -> Result<TextComponent, fmt::Error> {
108 let template = azalea_language::get(&self.key).unwrap_or(&self.key);
109 let mut i = 0;
112 let mut matched = 0;
113
114 let mut built_text = String::new();
117 let mut components = Vec::new();
118
119 while i < template.chars().count() {
120 if template.chars().nth(i).unwrap() == '%' {
121 let Some(char_after) = template.chars().nth(i + 1) else {
122 built_text.push('%');
123 break;
124 };
125 i += 1;
126 match char_after {
127 '%' => {
128 built_text.push('%');
129 }
130 's' => {
131 let arg_component = self
132 .args
133 .get(matched)
134 .cloned()
135 .unwrap_or_else(|| StringOrComponent::String("".to_string()));
136
137 components.push(TextComponent::new(built_text.clone()));
138 built_text.clear();
139 components.push(TextComponent::from(arg_component));
140 matched += 1;
141 }
142 _ => {
143 if let Some(d) = char_after.to_digit(10) {
145 if let Some('$') = template.chars().nth(i + 1) {
147 if let Some('s') = template.chars().nth(i + 2) {
148 i += 2;
149 built_text.push_str(
150 &self
151 .args
152 .get((d - 1) as usize)
153 .unwrap_or(&StringOrComponent::String("".to_string()))
154 .to_string(),
155 );
156 } else {
157 return Err(fmt::Error);
158 }
159 } else {
160 return Err(fmt::Error);
161 }
162 } else {
163 i -= 1;
164 built_text.push('%');
165 }
166 }
167 }
168 } else {
169 built_text.push(template.chars().nth(i).unwrap());
170 }
171
172 i += 1;
173 }
174
175 if components.is_empty() {
176 return Ok(TextComponent::new(built_text));
177 }
178
179 components.push(TextComponent::new(built_text));
180
181 Ok(TextComponent {
182 base: BaseComponent {
183 siblings: components.into_iter().map(FormattedText::Text).collect(),
184 style: Style::default(),
185 },
186 text: "".to_string(),
187 })
188 }
189}
190
191impl Display for TranslatableComponent {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 for component in FormattedText::Translatable(self.clone()).into_iter() {
195 let component_text = match &component {
196 FormattedText::Text(c) => c.text.to_string(),
197 FormattedText::Translatable(c) => match c.read() {
198 Ok(c) => c.to_string(),
199 Err(_) => c.key.to_string(),
200 },
201 };
202
203 f.write_str(&component_text)?;
204 }
205
206 Ok(())
207 }
208}
209
210impl Display for StringOrComponent {
211 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
212 match self {
213 StringOrComponent::String(s) => write!(f, "{s}"),
214 StringOrComponent::FormattedText(c) => write!(f, "{c}"),
215 }
216 }
217}
218
219impl From<StringOrComponent> for TextComponent {
220 fn from(soc: StringOrComponent) -> Self {
221 match soc {
222 StringOrComponent::String(s) => TextComponent::new(s),
223 StringOrComponent::FormattedText(c) => TextComponent::new(c.to_string()),
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_none() {
234 let c = TranslatableComponent::new("translation.test.none".to_string(), vec![]);
235 assert_eq!(c.read().unwrap().to_string(), "Hello, world!".to_string());
236 }
237 #[test]
238 fn test_complex() {
239 let c = TranslatableComponent::new(
240 "translation.test.complex".to_string(),
241 vec![
242 StringOrComponent::String("a".to_string()),
243 StringOrComponent::String("b".to_string()),
244 StringOrComponent::String("c".to_string()),
245 StringOrComponent::String("d".to_string()),
246 ],
247 );
248 assert_eq!(
250 c.read().unwrap().to_string(),
251 "Prefix, ab again b and a lastly c and also a again!".to_string()
252 );
253 }
254 #[test]
255 fn test_escape() {
256 let c = TranslatableComponent::new(
257 "translation.test.escape".to_string(),
258 vec![
259 StringOrComponent::String("a".to_string()),
260 StringOrComponent::String("b".to_string()),
261 StringOrComponent::String("c".to_string()),
262 StringOrComponent::String("d".to_string()),
263 ],
264 );
265 assert_eq!(c.read().unwrap().to_string(), "%s %a %%s %%b".to_string());
266 }
267 #[test]
268 fn test_invalid() {
269 let c = TranslatableComponent::new(
270 "translation.test.invalid".to_string(),
271 vec![
272 StringOrComponent::String("a".to_string()),
273 StringOrComponent::String("b".to_string()),
274 StringOrComponent::String("c".to_string()),
275 StringOrComponent::String("d".to_string()),
276 ],
277 );
278 assert_eq!(c.read().unwrap().to_string(), "hi %".to_string());
279 }
280 #[test]
281 fn test_invalid2() {
282 let c = TranslatableComponent::new(
283 "translation.test.invalid2".to_string(),
284 vec![
285 StringOrComponent::String("a".to_string()),
286 StringOrComponent::String("b".to_string()),
287 StringOrComponent::String("c".to_string()),
288 StringOrComponent::String("d".to_string()),
289 ],
290 );
291 assert_eq!(c.read().unwrap().to_string(), "hi % s".to_string());
292 }
293}