diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-08-24 16:20:18 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-08-24 16:20:18 -0300 |
| commit | 03a0f1c83b807148f155dd71a500b24137a9d47d (patch) | |
| tree | af71d25bb1177fc3ae2e6c1fcf20440503ad297d /android | |
| parent | e6b0f6112e9dd825f2aacd66b39d9120e7cdc5a6 (diff) | |
| parent | ee01537e231507f17e4d6a3b9dcd0cf19c11918c (diff) | |
| download | mullvadvpn-03a0f1c83b807148f155dd71a500b24137a9d47d.tar.xz mullvadvpn-03a0f1c83b807148f155dd71a500b24137a9d47d.zip | |
Merge branch 'append-plurals-to-messages-template'
Diffstat (limited to 'android')
| -rw-r--r-- | android/src/main/res/values/plurals.xml | 6 | ||||
| -rw-r--r-- | android/translations-converter/src/android.rs | 65 | ||||
| -rw-r--r-- | android/translations-converter/src/gettext.rs | 32 | ||||
| -rw-r--r-- | android/translations-converter/src/main.rs | 76 |
4 files changed, 157 insertions, 22 deletions
diff --git a/android/src/main/res/values/plurals.xml b/android/src/main/res/values/plurals.xml index 224cc9cb79..b9aa90441e 100644 --- a/android/src/main/res/values/plurals.xml +++ b/android/src/main/res/values/plurals.xml @@ -1,14 +1,14 @@ <resources> <plurals name="years_left"> - <item quantity="one">%d year left</item> + <item quantity="one">1 year left</item> <item quantity="other">%d years left</item> </plurals> <plurals name="months_left"> - <item quantity="one">%d month left</item> + <item quantity="one">1 month left</item> <item quantity="other">%d months left</item> </plurals> <plurals name="days_left"> - <item quantity="one">%d day left</item> + <item quantity="one">1 day left</item> <item quantity="other">%d days left</item> </plurals> <plurals name="minutes_ago"> diff --git a/android/translations-converter/src/android.rs b/android/translations-converter/src/android.rs index 10293aab8a..ca759ffb1e 100644 --- a/android/translations-converter/src/android.rs +++ b/android/translations-converter/src/android.rs @@ -158,3 +158,68 @@ impl Display for StringResource { } } } + +/// Contents of an Android plurals resources file. +/// +/// This type can be created directly deserializing the `plurals.xml` file. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PluralResources { + #[serde(rename = "plurals")] + entries: Vec<PluralResource>, +} + +/// An entry in an Android plurals resources file. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PluralResource { + /// The plural resource ID. + pub name: String, + + /// The items of the plural resource, one for each quantity variant. + #[serde(rename = "item")] + pub items: Vec<PluralVariant>, +} + +/// A string resource for a specific quantity. +/// +/// This is part of a plural resource. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PluralVariant { + /// The quantity for this variant to be used. + pub quantity: PluralQuantity, + + /// The string value + #[serde(rename = "$value")] + pub string: String, +} + +/// A valid quantity for a plural variant. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum PluralQuantity { + Zero, + One, + Other, +} + +impl Deref for PluralResources { + type Target = Vec<PluralResource>; + + fn deref(&self) -> &Self::Target { + &self.entries + } +} + +impl DerefMut for PluralResources { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.entries + } +} + +impl IntoIterator for PluralResources { + type Item = PluralResource; + type IntoIter = std::vec::IntoIter<Self::Item>; + + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } +} diff --git a/android/translations-converter/src/gettext.rs b/android/translations-converter/src/gettext.rs index edd5fbd538..26abdbd577 100644 --- a/android/translations-converter/src/gettext.rs +++ b/android/translations-converter/src/gettext.rs @@ -15,7 +15,23 @@ lazy_static! { #[derive(Clone, Debug)] pub struct MsgEntry { pub id: String, - pub value: String, + pub value: MsgValue, +} + +/// A message string or plural set in a gettext translation file. +#[derive(Clone, Debug)] +pub enum MsgValue { + Invariant(String), + Plural { + plural_id: String, + values: Vec<String>, + }, +} + +impl From<String> for MsgValue { + fn from(string: String) -> Self { + MsgValue::Invariant(string) + } } /// Load message entries from a gettext translation file. @@ -36,7 +52,7 @@ pub fn load_file(file_path: impl AsRef<Path>) -> Vec<MsgEntry> { } else { if let Some(translation) = parse_line(line, "msgstr \"", "\"") { if let Some(id) = current_id.take() { - let value = normalize(translation); + let value = MsgValue::from(normalize(translation)); entries.push(MsgEntry { id, value }); } @@ -65,7 +81,17 @@ pub fn append_to_template( for entry in entries { writeln!(writer)?; writeln!(writer, "msgid {:?}", entry.id)?; - writeln!(writer, "msgstr {:?}", entry.value)?; + + match entry.value { + MsgValue::Invariant(value) => writeln!(writer, "msgstr {:?}", value)?, + MsgValue::Plural { plural_id, values } => { + writeln!(writer, "msgid_plural {:?}", plural_id)?; + + for (index, value) in values.into_iter().enumerate() { + writeln!(writer, "msgstr[{}] {:?}", index, value)?; + } + } + } } Ok(()) diff --git a/android/translations-converter/src/main.rs b/android/translations-converter/src/main.rs index d0cd0e36be..36a1970c2d 100644 --- a/android/translations-converter/src/main.rs +++ b/android/translations-converter/src/main.rs @@ -16,6 +16,13 @@ //! order when only named parameters are used, and Android strings only supported numbered //! parameters. //! +//! Android's plural resources aren't currently translated, but this tool will convert them to +//! gettext message templates and append them to the message template file. It's important to note +//! that the first quantity item for a plural will be used as the `msgid`, so it shouldn't +//! have any parameters. The last quantity item for a plural will be used as the `msgid_plural`, +//! and it can contain parameters. This assumes a plural resource will have at least two items. +//! While it would still work with a single item, this is an unlikely case for a plural resource. +//! //! Note that this conversion procedure is very raw and likely very brittle, so while it works for //! most cases, it is important to keep in mind that this is just a helper tool and manual steps are //! likely to be needed from time to time. @@ -31,6 +38,7 @@ use std::{ fn main() { let resources_dir = Path::new("../src/main/res"); + let strings_file = File::open(resources_dir.join("values/strings.xml")) .expect("Failed to open string resources file"); let mut string_resources: android::StringResources = @@ -62,6 +70,11 @@ fn main() { let mut missing_translations = known_strings.clone(); + let plurals_file = File::open(resources_dir.join("values/plurals.xml")) + .expect("Failed to open plurals resources file"); + let plural_resources: android::PluralResources = + serde_xml_rs::from_reader(plurals_file).expect("Failed to read plural resources file"); + let locale_dir = Path::new("../../gui/locales"); let locale_files = fs::read_dir(&locale_dir) .expect("Failed to open root locale directory") @@ -94,21 +107,50 @@ fn main() { ); } + let template_path = locale_dir.join("messages.pot"); + if !missing_translations.is_empty() { println!("Appending missing translations to template file:"); + + gettext::append_to_template( + &template_path, + missing_translations + .into_iter() + .inspect(|(missing_translation, id)| println!(" {}: {}", id, missing_translation)) + .map(|(id, _)| gettext::MsgEntry { + id, + value: String::new().into(), + }), + ) + .expect("Failed to append missing translations to message template file"); } - gettext::append_to_template( - locale_dir.join("messages.pot"), - missing_translations - .into_iter() - .inspect(|(missing_translation, id)| println!(" {}: {}", id, missing_translation)) - .map(|(id, _)| gettext::MsgEntry { - id, - value: String::new(), - }), - ) - .expect("Failed to append missing translations to message template file"); + if !plural_resources.is_empty() { + gettext::append_to_template( + &template_path, + plural_resources + .into_iter() + .inspect(|plural| { + let last_item = &plural.items.last().expect("Plural items are empty").string; + + println!(" {}: {}", plural.name, last_item); + }) + .map(|mut plural| { + let plural_id = plural.items.pop().expect("Plural items are empty").string; + plural.items.truncate(1); + let id = plural.items.remove(0).string; + + gettext::MsgEntry { + id, + value: gettext::MsgValue::Plural { + plural_id, + values: vec!["".to_owned(), "".to_owned()], + }, + } + }), + ) + .expect("Failed to append missing plural translations to message template file"); + } } /// Determines the localized value resources directory name based on a locale specification. @@ -153,11 +195,13 @@ fn generate_translations( let mut localized_resource = android::StringResources::new(); for translation in translations { - if let Some(android_key) = known_strings.remove(&translation.id) { - localized_resource.push(android::StringResource::new( - android_key, - &translation.value, - )); + if let gettext::MsgValue::Invariant(translation_value) = translation.value { + if let Some(android_key) = known_strings.remove(&translation.id) { + localized_resource.push(android::StringResource::new( + android_key, + &translation_value, + )); + } } } |
