diff --git a/Cargo.lock b/Cargo.lock index 4a74692..e8141b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2734,9 +2734,11 @@ dependencies = [ "icu", "icu_locid", "icu_provider", + "intl-memoizer", "serde 1.0.193", "serde_yaml", "sys-locale", + "thiserror", "unic-langid", ] diff --git a/l10n/Cargo.toml b/l10n/Cargo.toml index 40e2d30..3cdf475 100644 --- a/l10n/Cargo.toml +++ b/l10n/Cargo.toml @@ -15,9 +15,11 @@ fluent = { version = "0.16" } icu_locid = { version = "1" } icu_provider = { version = "1" } icu = { version = "1" } +intl-memoizer = { version = "*" } serde = { version = "1", features = [ "derive" ] } serde_yaml = { version = "0.9" } sys-locale = { version = "0.3" } +thiserror = { version = "1" } unic-langid = { version = "*" } [[bin]] diff --git a/l10n/src/lib.rs b/l10n/src/lib.rs index b970bc8..65ee465 100644 --- a/l10n/src/lib.rs +++ b/l10n/src/lib.rs @@ -1,12 +1,13 @@ use chrono::{Datelike, NaiveDate, Timelike}; use chrono_tz::Tz; use fixed_decimal::FixedDecimal; -use fluent::{FluentBundle, FluentResource}; -use fluent_ergonomics::FluentErgo; +use fluent::{bundle::FluentBundle, FluentResource}; use icu::{datetime::options::length, decimal::FixedDecimalFormatter, locid::Locale}; use icu_provider::DataLocale; -use std::{collections::HashMap, ops::Deref, path::Path}; +use std::{fs::File, io::Read, ops::Deref}; use sys_locale::get_locale; +use thiserror::Error; +use unic_langid::LanguageIdentifierError; // Re-exports. I'm doing these so that clients of this library don't have to go tracking down // additional structures @@ -46,7 +47,9 @@ impl Deref for NonEmptyList { } } +#[derive(Debug, Error)] pub enum L10NError { + #[error("Unparsable Locale")] UnparsableLocale, } @@ -56,6 +59,33 @@ impl From for L10NError { } } +#[derive(Debug, Error)] +pub enum FileLoadError { + #[error("Unparsable Locale")] + UnparsableLocale, + + #[error("Source string file not found")] + FileNotFound, + + #[error("The Fluent file is malformed")] + FluentParseError, + + #[error("An unknown IO error was found")] + IOError(std::io::Error), +} + +impl From for FileLoadError { + fn from(_: LanguageIdentifierError) -> Self { + Self::UnparsableLocale + } +} + +impl From for FileLoadError { + fn from(err: std::io::Error) -> Self { + Self::IOError(err) + } +} + // Potential Message structure. // // Let's assume the application has an enumeration that implements Message. For each element of the @@ -74,7 +104,7 @@ pub trait Message { pub struct L10N { messages_root: std::path::PathBuf, - message_bundles: Vec>, + message_bundles: Vec>, locales: NonEmptyList, zone: chrono_tz::Tz, @@ -95,34 +125,39 @@ impl L10N { let english_phrases = FluentResource::try_new */ - let message_bundles = { - /* - let mut english_messages = messages_root.clone(); - english_messages.push("en-US.ftl"); - - let langid: unic_langid::LanguageIdentifier = english.to_string().parse().unwrap(); - let mut messages = FluentErgo::new(&[langid.clone()]); - let _ = messages.add_from_file(langid, &english_messages); - - vec![messages] - */ - vec![] - }; - - Self { + let mut s = Self { messages_root, - message_bundles, + message_bundles: vec![], locales, zone, - } + }; + + s.load_messages_from_file("en-US".to_owned()).unwrap(); + + s } - pub fn load_messages_from_file( - &mut self, - locale: String, - path: &Path, - ) -> Result<(), L10NError> { - unimplemented!() + fn load_messages_from_file(&mut self, locale: String) -> Result<(), FileLoadError> { + let langid: unic_langid::LanguageIdentifier = locale.parse()?; + + let mut path = self.messages_root.clone(); + path.push(locale); + path.set_extension("ftl"); + println!("{:?}", path); + + let mut buffer = Vec::new(); + let mut f = File::open(path)?; + f.read_to_end(&mut buffer)?; + let text = String::from_utf8(buffer).unwrap(); + match FluentResource::try_new(text) { + Ok(resource) => { + let mut bundle = FluentBundle::new_concurrent(vec![langid]); + let _ = bundle.add_resource(resource); + self.message_bundles.push(bundle); + Ok(()) + } + Err((_, _error)) => Err(FileLoadError::FluentParseError), + } } // Now, whenever the user changes the locales, the list of messages has to change. How do we @@ -165,9 +200,15 @@ impl L10N { // } pub fn tr(&self, message: impl Message) -> String { - let msg = self.message_bundles[0].get_message(message.msgid()).and_then(|msg| msg.value()).unwrap(); + println!("message: {}", message.msgid()); + let msg = self.message_bundles[0] + .get_message(message.msgid()) + .and_then(|msg| msg.value()) + .unwrap(); let mut errors = vec![]; - self.message_bundles[0].format_pattern(msg, message.args().as_ref(), &mut errors).to_string() + self.message_bundles[0] + .format_pattern(msg, message.args().as_ref(), &mut errors) + .to_string() } pub fn format_date_time_utc(