aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTert0 <tert0byte@gmail.com>2025-09-15 06:58:00 +0200
committerGitHub <noreply@github.com>2025-09-14 23:58:00 -0500
commit1a42c08030c865948e9e9b6dc8a1f4e38550063a (patch)
treec5342b8e27edc4536d0f4a19628be48de9b15651
parentbcefd0213db05b4c29c82a1031f4d6e838e1fc1f (diff)
downloadazalea-drasl-1a42c08030c865948e9e9b6dc8a1f4e38550063a.tar.xz
implement translation fallback (#244)
-rw-r--r--azalea-chat/src/component.rs72
-rw-r--r--azalea-chat/src/translatable_component.rs48
2 files changed, 116 insertions, 4 deletions
diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs
index 62e5e151..c7a69390 100644
--- a/azalea-chat/src/component.rs
+++ b/azalea-chat/src/component.rs
@@ -295,6 +295,16 @@ impl<'de> Deserialize<'de> for FormattedText {
.as_str()
.ok_or_else(|| de::Error::custom("\"translate\" must be a string"))?
.into();
+ let fallback = if let Some(fallback) = json.get("fallback") {
+ Some(
+ fallback
+ .as_str()
+ .ok_or_else(|| de::Error::custom("\"fallback\" must be a string"))?
+ .to_string(),
+ )
+ } else {
+ None
+ };
if let Some(with) = json.get("with") {
let with = with
.as_array()
@@ -316,13 +326,14 @@ impl<'de> Deserialize<'de> for FormattedText {
FormattedText::deserialize(item).map_err(de::Error::custom)?,
));
}
- component = FormattedText::Translatable(TranslatableComponent::new(
- translate, with_array,
+ component = FormattedText::Translatable(TranslatableComponent::with_fallback(
+ translate, fallback, with_array,
));
} else {
// if it doesn't have a "with", just have the with_array be empty
- component = FormattedText::Translatable(TranslatableComponent::new(
+ component = FormattedText::Translatable(TranslatableComponent::with_fallback(
translate,
+ fallback,
Vec::new(),
));
}
@@ -678,3 +689,58 @@ impl Default for FormattedText {
FormattedText::Text(TextComponent::default())
}
}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::Value;
+
+ use super::*;
+
+ #[test]
+ fn deserialize_translation() {
+ let j: Value =
+ serde_json::from_str(r#"{"translate": "translation.test.args", "with": ["a", "b"]}"#)
+ .unwrap();
+ let component = FormattedText::deserialize(&j).unwrap();
+ assert_eq!(
+ component,
+ FormattedText::Translatable(TranslatableComponent::new(
+ "translation.test.args".to_string(),
+ vec![
+ StringOrComponent::String("a".to_string()),
+ StringOrComponent::String("b".to_string())
+ ]
+ ))
+ );
+ }
+
+ #[test]
+ fn deserialize_translation_invalid_arguments() {
+ let j: Value =
+ serde_json::from_str(r#"{"translate": "translation.test.args", "with": {}}"#).unwrap();
+ assert!(FormattedText::deserialize(&j).is_err());
+ }
+
+ #[test]
+ fn deserialize_translation_fallback() {
+ let j: Value = serde_json::from_str(r#"{"translate": "translation.test.undefined", "fallback": "fallback: %s", "with": ["a"]}"#).unwrap();
+ let component = FormattedText::deserialize(&j).unwrap();
+ assert_eq!(
+ component,
+ FormattedText::Translatable(TranslatableComponent::with_fallback(
+ "translation.test.undefined".to_string(),
+ Some("fallback: %s".to_string()),
+ vec![StringOrComponent::String("a".to_string())]
+ ))
+ );
+ }
+
+ #[test]
+ fn deserialize_translation_invalid_fallback() {
+ let j: Value = serde_json::from_str(
+ r#"{"translate": "translation.test.undefined", "fallback": {"text": "invalid"}}"#,
+ )
+ .unwrap();
+ assert!(FormattedText::deserialize(&j).is_err());
+ }
+}
diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs
index 10c502a8..28700366 100644
--- a/azalea-chat/src/translatable_component.rs
+++ b/azalea-chat/src/translatable_component.rs
@@ -28,6 +28,7 @@ impl simdnbt::ToNbtTag for StringOrComponent {
pub struct TranslatableComponent {
pub base: BaseComponent,
pub key: String,
+ pub fallback: Option<String>,
pub args: Vec<StringOrComponent>,
}
@@ -97,13 +98,33 @@ impl TranslatableComponent {
Self {
base: BaseComponent::new(),
key,
+ fallback: None,
+ args,
+ }
+ }
+
+ pub fn with_fallback(
+ key: String,
+ fallback: Option<String>,
+ args: Vec<StringOrComponent>,
+ ) -> Self {
+ Self {
+ base: BaseComponent::new(),
+ key,
+ fallback,
args,
}
}
/// Convert the key and args to a FormattedText.
pub fn read(&self) -> Result<TextComponent, fmt::Error> {
- let template = azalea_language::get(&self.key).unwrap_or(&self.key);
+ let template = azalea_language::get(&self.key).unwrap_or_else(|| {
+ if let Some(fallback) = &self.fallback {
+ fallback.as_str()
+ } else {
+ &self.key
+ }
+ });
// decode the % things
let mut i = 0;
@@ -293,4 +314,29 @@ mod tests {
);
assert_eq!(c.read().unwrap().to_string(), "hi % s".to_string());
}
+
+ #[test]
+ fn test_undefined() {
+ let c = TranslatableComponent::new(
+ "translation.test.undefined".to_string(),
+ vec![StringOrComponent::String("a".to_string())],
+ );
+ assert_eq!(
+ c.read().unwrap().to_string(),
+ "translation.test.undefined".to_string()
+ );
+ }
+
+ #[test]
+ fn test_undefined_with_fallback() {
+ let c = TranslatableComponent::with_fallback(
+ "translation.test.undefined".to_string(),
+ Some("translation fallback: %s".to_string()),
+ vec![StringOrComponent::String("a".to_string())],
+ );
+ assert_eq!(
+ c.read().unwrap().to_string(),
+ "translation fallback: a".to_string()
+ );
+ }
}