diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2021-05-21 17:57:59 +0000 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2021-05-28 11:54:59 +0000 |
| commit | 57f08af75f931eb0733d2f18784f2dc9872ab354 (patch) | |
| tree | 65143f28808b99d0df40eed76c9717661e036449 /android | |
| parent | befcf743bfae2f520c2b1ce0d299ec7d4e99cf1b (diff) | |
| download | mullvadvpn-57f08af75f931eb0733d2f18784f2dc9872ab354.tar.xz mullvadvpn-57f08af75f931eb0733d2f18784f2dc9872ab354.zip | |
Move `Messages` and related types to new module
Diffstat (limited to 'android')
| -rw-r--r-- | android/translations-converter/src/gettext/messages.rs | 165 | ||||
| -rw-r--r-- | android/translations-converter/src/gettext/mod.rs | 170 |
2 files changed, 173 insertions, 162 deletions
diff --git a/android/translations-converter/src/gettext/messages.rs b/android/translations-converter/src/gettext/messages.rs new file mode 100644 index 0000000000..5effebf1b5 --- /dev/null +++ b/android/translations-converter/src/gettext/messages.rs @@ -0,0 +1,165 @@ +use super::{msg_string::MsgString, parse_line, plural_form::PluralForm}; +use std::{ + collections::BTreeMap, + fs::File, + io::{BufRead, BufReader}, + mem, + path::Path, +}; + +/// A parsed gettext messages file. +#[derive(Clone, Debug, Default)] +pub struct Messages { + pub plural_form: Option<PluralForm>, + entries: Vec<MsgEntry>, +} + +/// A message entry in a gettext translation file. +#[derive(Clone, Debug)] +pub struct MsgEntry { + pub id: MsgString, + pub value: MsgValue, +} + +/// A message string or plural set in a gettext translation file. +#[derive(Clone, Debug)] +pub enum MsgValue { + Invariant(MsgString), + Plural { + plural_id: MsgString, + values: Vec<MsgString>, + }, +} + +impl Messages { + /// Load message entries from a gettext translation file. + /// + /// The only metadata that is parsed from the file is the "Plural-Form" header. It is assumed + /// that the header value is one of some hard-coded values, so if new languages that have new + /// plurals are added, the code will have to be updated. + /// + /// An gettext translation file has the format in the example below: + /// + /// ``` + /// # The start of the file can contain empty entries to include some header with meta + /// # information. Below is the header indicating the plural format. + /// msgid "" + /// msgstr "" + /// "Plural-Forms: nplurals=2; plural=(n != 1);" + /// + /// # Simple translated messages + /// msgid "Message in original language" + /// msgstr "Mesaĝo en tradukita lingvo" + /// + /// # Plural translated messages (with two forms) + /// msgid "One translated message" + /// msgid_plural "%d translated messages" + /// msgstr[0] "Unu tradukita mesaĝo" + /// msgstr[1] "%d tradukitaj mesaĝoj" + /// ``` + pub fn from_file(file_path: impl AsRef<Path>) -> Self { + let mut parsing_header = false; + let mut entries = Vec::new(); + let mut current_id = None; + let mut current_plural_id = None; + let mut plural_form = None; + let mut variants = BTreeMap::new(); + + let file = BufReader::new(File::open(file_path).expect("Failed to open gettext file")); + // Ensure there's an empty line at the end so that the "else" part of the string matching + // code will run for the last message in the file. + let lines = file + .lines() + .map(|line_result| line_result.expect("Failed to read from gettext file")) + .chain(Some(String::new())); + + for line in lines { + match_str! { (line.trim()) + ["msgid \"", msg_id, "\""] => { + current_id = Some(MsgString::from_escaped(msg_id)); + }, + ["msgstr \"", translation, "\""] => { + if let Some(id) = current_id.take() { + let value = MsgValue::Invariant(MsgString::from_escaped(translation)); + + parsing_header = id.is_empty() && translation.is_empty(); + + entries.push(MsgEntry { id, value }); + } + + current_id = None; + current_plural_id = None; + }, + ["msgid_plural \"", plural_id, "\""] => { + current_plural_id = Some(MsgString::from_escaped(plural_id)); + parsing_header = false; + }, + ["msgstr[", plural_translation, "\""] => { + let variant_id_end = plural_translation + .chars() + .position(|character| character == ']') + .expect("Invalid plural msgstr"); + let variant_id: usize = plural_translation[..variant_id_end] + .parse() + .expect("Invalid variant index"); + let variant_msg = parse_line(&plural_translation[variant_id_end..], "] \"", "") + .expect("Invalid plural msgstr"); + + variants.insert(variant_id, MsgString::from_escaped(variant_msg)); + parsing_header = false; + }, + ["\"", header, "\\n\""] => { + if parsing_header { + if let Some(plural_formula) = parse_line(header, "Plural-Forms: ", ";") { + plural_form = PluralForm::from_formula(plural_formula); + } + } + }, + _ => { + if let Some(plural_id) = current_plural_id.take() { + let id = current_id.take().expect("Missing msgid for plural message"); + let values = mem::replace(&mut variants, BTreeMap::new()) + .into_iter() + .enumerate() + .inspect(|(index, (variant_id, _))| { + assert_eq!( + index, variant_id, + "Unexpected variant ID for plural msgstr" + ) + }) + .map(|(_, (_, value))| value) + .collect(); + let value = MsgValue::Plural { plural_id, values }; + + entries.push(MsgEntry { id, value }); + } + + current_id = None; + current_plural_id = None; + variants.clear(); + parsing_header = false; + }, + } + } + + Self { + entries, + plural_form, + } + } +} + +impl IntoIterator for Messages { + type Item = MsgEntry; + type IntoIter = std::vec::IntoIter<Self::Item>; + + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } +} + +impl From<MsgString> for MsgValue { + fn from(string: MsgString) -> Self { + MsgValue::Invariant(string) + } +} diff --git a/android/translations-converter/src/gettext/mod.rs b/android/translations-converter/src/gettext/mod.rs index c76ded8711..59fcfe0b70 100644 --- a/android/translations-converter/src/gettext/mod.rs +++ b/android/translations-converter/src/gettext/mod.rs @@ -1,174 +1,20 @@ #[macro_use] mod match_str; +mod messages; mod msg_string; mod plural_form; use std::{ - collections::BTreeMap, - fs::{File, OpenOptions}, - io::{self, BufRead, BufReader, BufWriter, Write}, - mem, + fs::OpenOptions, + io::{self, BufWriter, Write}, path::Path, }; -pub use self::{msg_string::MsgString, plural_form::PluralForm}; - -/// A parsed gettext translation file. -#[derive(Clone, Debug)] -pub struct Messages { - pub plural_form: Option<PluralForm>, - entries: Vec<MsgEntry>, -} - -/// A message entry in a gettext translation file. -#[derive(Clone, Debug)] -pub struct MsgEntry { - pub id: MsgString, - pub value: MsgValue, -} - -/// A message string or plural set in a gettext translation file. -#[derive(Clone, Debug)] -pub enum MsgValue { - Invariant(MsgString), - Plural { - plural_id: MsgString, - values: Vec<MsgString>, - }, -} - -impl Messages { - /// Load message entries from a gettext translation file. - /// - /// The only metadata that is parsed from the file is the "Plural-Form" header. It is assumed - /// that the header value is one of some hard-coded values, so if new languages that have new - /// plurals are added, the code will have to be updated. - /// - /// An gettext translation file has the format in the example below: - /// - /// ``` - /// # The start of the file can contain empty entries to include some header with meta - /// # information. Below is the header indicating the plural format. - /// msgid "" - /// msgstr "" - /// "Plural-Forms: nplurals=2; plural=(n != 1);" - /// - /// # Simple translated messages - /// msgid "Message in original language" - /// msgstr "Mesaĝo en tradukita lingvo" - /// - /// # Plural translated messages (with two forms) - /// msgid "One translated message" - /// msgid_plural "%d translated messages" - /// msgstr[0] "Unu tradukita mesaĝo" - /// msgstr[1] "%d tradukitaj mesaĝoj" - /// ``` - pub fn from_file(file_path: impl AsRef<Path>) -> Self { - let mut parsing_header = false; - let mut entries = Vec::new(); - let mut current_id = None; - let mut current_plural_id = None; - let mut plural_form = None; - let mut variants = BTreeMap::new(); - - let file = BufReader::new(File::open(file_path).expect("Failed to open gettext file")); - // Ensure there's an empty line at the end so that the "else" part of the string matching - // code will run for the last message in the file. - let lines = file - .lines() - .map(|line_result| line_result.expect("Failed to read from gettext file")) - .chain(Some(String::new())); - - for line in lines { - match_str! { (line.trim()) - ["msgid \"", msg_id, "\""] => { - current_id = Some(MsgString::from_escaped(msg_id)); - }, - ["msgstr \"", translation, "\""] => { - if let Some(id) = current_id.take() { - let value = MsgValue::Invariant(MsgString::from_escaped(translation)); - - parsing_header = id.is_empty() && translation.is_empty(); - - entries.push(MsgEntry { id, value }); - } - - current_id = None; - current_plural_id = None; - }, - ["msgid_plural \"", plural_id, "\""] => { - current_plural_id = Some(MsgString::from_escaped(plural_id)); - parsing_header = false; - }, - ["msgstr[", plural_translation, "\""] => { - let variant_id_end = plural_translation - .chars() - .position(|character| character == ']') - .expect("Invalid plural msgstr"); - let variant_id: usize = plural_translation[..variant_id_end] - .parse() - .expect("Invalid variant index"); - let variant_msg = parse_line(&plural_translation[variant_id_end..], "] \"", "") - .expect("Invalid plural msgstr"); - - variants.insert(variant_id, MsgString::from_escaped(variant_msg)); - parsing_header = false; - }, - ["\"", header, "\\n\""] => { - if parsing_header { - if let Some(plural_formula) = parse_line(header, "Plural-Forms: ", ";") { - plural_form = PluralForm::from_formula(plural_formula); - } - } - }, - _ => { - if let Some(plural_id) = current_plural_id.take() { - let id = current_id.take().expect("Missing msgid for plural message"); - let values = mem::replace(&mut variants, BTreeMap::new()) - .into_iter() - .enumerate() - .inspect(|(index, (variant_id, _))| { - assert_eq!( - index, variant_id, - "Unexpected variant ID for plural msgstr" - ) - }) - .map(|(_, (_, value))| value) - .collect(); - let value = MsgValue::Plural { plural_id, values }; - - entries.push(MsgEntry { id, value }); - } - - current_id = None; - current_plural_id = None; - variants.clear(); - parsing_header = false; - } - } - } - - Self { - entries, - plural_form, - } - } -} - -impl IntoIterator for Messages { - type Item = MsgEntry; - type IntoIter = std::vec::IntoIter<Self::Item>; - - fn into_iter(self) -> Self::IntoIter { - self.entries.into_iter() - } -} - -impl From<MsgString> for MsgValue { - fn from(string: MsgString) -> Self { - MsgValue::Invariant(string) - } -} +pub use self::{ + messages::{Messages, MsgEntry, MsgValue}, + msg_string::MsgString, + plural_form::PluralForm, +}; /// Append message entries to a translation file. /// |
