Skip to content

Commit 447c9f3

Browse files
committed
Add support for Android plurals in strings.xml format
Implements parsing, serialization, and conversion of <plurals> elements in Android strings.xml files. Updates the Format struct to include plurals, adds conversion logic between internal Resource and Android format for plurals, and provides comprehensive tests for plural handling and round-trip conversions.
1 parent abb2305 commit 447c9f3

2 files changed

Lines changed: 326 additions & 13 deletions

File tree

langcodec/src/converter.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,3 +606,100 @@ pub fn merge_resources(
606606

607607
Ok(merged)
608608
}
609+
610+
#[cfg(test)]
611+
mod tests {
612+
use super::*;
613+
use crate::types::{Entry, EntryStatus, Metadata, Plural, PluralCategory, Translation};
614+
use std::collections::{BTreeMap, HashMap};
615+
616+
#[test]
617+
fn test_convert_csv_to_android_strings_en() {
618+
let tmp = tempfile::tempdir().unwrap();
619+
let input = tmp.path().join("in.csv");
620+
let output = tmp.path().join("strings.xml");
621+
622+
// CSV with header and two rows
623+
std::fs::write(
624+
&input,
625+
"key,en,fr\nhello,Hello,Bonjour\nbye,Goodbye,Au revoir\n",
626+
)
627+
.unwrap();
628+
629+
convert(&input, FormatType::CSV, &output, FormatType::AndroidStrings(Some("en".into())))
630+
.unwrap();
631+
632+
// Read back as Android to verify
633+
let android = crate::formats::AndroidStringsFormat::read_from(&output).unwrap();
634+
// ensure we have only strings for the selected language
635+
assert_eq!(android.strings.len(), 2);
636+
let mut names: Vec<&str> = android.strings.iter().map(|s| s.name.as_str()).collect();
637+
names.sort();
638+
assert_eq!(names, vec!["bye", "hello"]);
639+
let hello = android.strings.iter().find(|s| s.name == "hello").unwrap();
640+
assert_eq!(hello.value, "Hello");
641+
let bye = android.strings.iter().find(|s| s.name == "bye").unwrap();
642+
assert_eq!(bye.value, "Goodbye");
643+
}
644+
645+
#[test]
646+
fn test_convert_xcstrings_plurals_to_android() {
647+
let tmp = tempfile::tempdir().unwrap();
648+
let input = tmp.path().join("in.xcstrings");
649+
let output = tmp.path().join("strings.xml");
650+
651+
// Build a Resource with a plural entry for English
652+
let mut custom = HashMap::new();
653+
custom.insert("source_language".into(), "en".into());
654+
custom.insert("version".into(), "1.0".into());
655+
656+
let mut forms = BTreeMap::new();
657+
forms.insert(PluralCategory::One, "One apple".to_string());
658+
forms.insert(PluralCategory::Other, "%d apples".to_string());
659+
660+
let res = Resource {
661+
metadata: Metadata {
662+
language: "en".into(),
663+
domain: "domain".into(),
664+
custom,
665+
},
666+
entries: vec![Entry {
667+
id: "apples".into(),
668+
value: Translation::Plural(Plural { id: "apples".into(), forms }),
669+
comment: Some("Count apples".into()),
670+
status: EntryStatus::Translated,
671+
custom: HashMap::new(),
672+
}],
673+
};
674+
675+
// Write XCStrings input
676+
let xc = crate::formats::XcstringsFormat::try_from(vec![res]).unwrap();
677+
xc.write_to(&input).unwrap();
678+
679+
// Convert to Android (English)
680+
convert(&input, FormatType::Xcstrings, &output, FormatType::AndroidStrings(Some("en".into())))
681+
.unwrap();
682+
683+
// Read back as Android
684+
let android = crate::formats::AndroidStringsFormat::read_from(&output).unwrap();
685+
assert_eq!(android.plurals.len(), 1);
686+
let p = &android.plurals[0];
687+
assert_eq!(p.name, "apples");
688+
// Should include at least 'one' and 'other'
689+
let mut qs: Vec<_> = p
690+
.items
691+
.iter()
692+
.map(|i| match i.quantity {
693+
PluralCategory::One => ("one", i.value.clone()),
694+
PluralCategory::Other => ("other", i.value.clone()),
695+
PluralCategory::Zero => ("zero", i.value.clone()),
696+
PluralCategory::Two => ("two", i.value.clone()),
697+
PluralCategory::Few => ("few", i.value.clone()),
698+
PluralCategory::Many => ("many", i.value.clone()),
699+
})
700+
.collect();
701+
qs.sort_by(|a, b| a.0.cmp(&b.0));
702+
assert!(qs.iter().any(|(q, v)| *q == "one" && v == "One apple"));
703+
assert!(qs.iter().any(|(q, v)| *q == "other" && v == "%d apples"));
704+
}
705+
}

0 commit comments

Comments
 (0)