summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-08-24 16:20:18 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-08-24 16:20:18 -0300
commit03a0f1c83b807148f155dd71a500b24137a9d47d (patch)
treeaf71d25bb1177fc3ae2e6c1fcf20440503ad297d /android
parente6b0f6112e9dd825f2aacd66b39d9120e7cdc5a6 (diff)
parentee01537e231507f17e4d6a3b9dcd0cf19c11918c (diff)
downloadmullvadvpn-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.xml6
-rw-r--r--android/translations-converter/src/android.rs65
-rw-r--r--android/translations-converter/src/gettext.rs32
-rw-r--r--android/translations-converter/src/main.rs76
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,
+ ));
+ }
}
}