diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/fluent-fallback/examples/simple-fallback.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/fluent-fallback/examples/simple-fallback.rs')
-rw-r--r-- | third_party/rust/fluent-fallback/examples/simple-fallback.rs | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/third_party/rust/fluent-fallback/examples/simple-fallback.rs b/third_party/rust/fluent-fallback/examples/simple-fallback.rs new file mode 100644 index 0000000000..efdc04af2c --- /dev/null +++ b/third_party/rust/fluent-fallback/examples/simple-fallback.rs @@ -0,0 +1,237 @@ +//! This is an example of a simple application +//! which calculates the Collatz conjecture. +//! +//! The function itself is trivial on purpose, +//! so that we can focus on understanding how +//! the application can be made localizable +//! via Fluent. +//! +//! To try the app launch `cargo run --example simple-fallback NUM (LOCALES)` +//! +//! NUM is a number to be calculated, and LOCALES is an optional +//! parameter with a comma-separated list of locales requested by the user. +//! +//! Example: +//! +//! cargo run --example simple-fallback 123 de,pl +//! +//! If the second argument is omitted, `en-US` locale is used as the +//! default one. + +use std::{env, fs, io, path::PathBuf, str::FromStr}; + +use fluent_bundle::{FluentArgs, FluentBundle, FluentResource}; +use fluent_fallback::{ + generator::{BundleGenerator, FluentBundleResult}, + types::ResourceId, + Localization, +}; +use fluent_langneg::{negotiate_languages, NegotiationStrategy}; + +use rustc_hash::FxHashSet; +use unic_langid::{langid, LanguageIdentifier}; + +/// This helper struct holds the scheme for converting +/// resource paths into full paths. It is used to customise +/// `fluent-fallback::SyncLocalization`. +struct Bundles { + res_path_scheme: PathBuf, +} + +/// This helper function allows us to read the list +/// of available locales by reading the list of +/// directories in `./examples/resources`. +/// +/// It is expected that every directory inside it +/// has a name that is a valid BCP47 language tag. +fn get_available_locales() -> io::Result<Vec<LanguageIdentifier>> { + let mut dir = env::current_dir()?; + if dir.to_string_lossy().ends_with("fluent-rs") { + dir.push("fluent-fallback"); + } + dir.push("examples"); + dir.push("resources"); + let res_dir = fs::read_dir(dir)?; + + let locales = res_dir + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.path().is_dir()) + .filter_map(|dir| { + let file_name = dir.file_name(); + let name = file_name.to_str()?; + Some(name.parse().expect("Parsing failed.")) + }) + .collect(); + Ok(locales) +} + +fn resolve_app_locales<'l>(args: &[String]) -> Vec<LanguageIdentifier> { + let default_locale = langid!("en-US"); + let available = get_available_locales().expect("Retrieving available locales failed."); + + let requested: Vec<LanguageIdentifier> = args.get(2).map_or(vec![], |arg| { + arg.split(",") + .map(|s| s.parse().expect("Parsing locale failed.")) + .collect() + }); + + negotiate_languages( + &requested, + &available, + Some(&default_locale), + NegotiationStrategy::Filtering, + ) + .into_iter() + .cloned() + .collect() +} + +fn get_resource_manager() -> Bundles { + let mut res_path_scheme = env::current_dir().expect("Failed to retrieve current dir."); + + if res_path_scheme.to_string_lossy().ends_with("fluent-rs") { + res_path_scheme.push("fluent-fallback"); + } + res_path_scheme.push("examples"); + res_path_scheme.push("resources"); + + res_path_scheme.push("{locale}"); + res_path_scheme.push("{res_id}"); + + Bundles { res_path_scheme } +} + +static L10N_RESOURCES: &[&str] = &["simple.ftl"]; + +fn main() { + let args: Vec<String> = env::args().collect(); + + let app_locales: Vec<LanguageIdentifier> = resolve_app_locales(&args); + + let bundles = get_resource_manager(); + + let loc = Localization::with_env( + L10N_RESOURCES.iter().map(|&res| res.into()), + true, + app_locales, + bundles, + ); + let bundles = loc.bundles(); + + let mut errors = vec![]; + + match args.get(1) { + Some(input) => match isize::from_str(&input) { + Ok(i) => { + let mut args = FluentArgs::new(); + args.set("input", i); + args.set("value", collatz(i)); + let value = bundles + .format_value_sync("response-msg", Some(&args), &mut errors) + .unwrap() + .unwrap(); + println!("{}", value); + } + Err(err) => { + let mut args = FluentArgs::new(); + args.set("input", input.as_str()); + args.set("reason", err.to_string()); + let value = bundles + .format_value_sync("input-parse-error-msg", Some(&args), &mut errors) + .unwrap() + .unwrap(); + println!("{}", value); + } + }, + None => { + let value = bundles + .format_value_sync("missing-arg-error", None, &mut errors) + .unwrap() + .unwrap(); + println!("{}", value); + } + } +} + +/// Collatz conjecture calculating function. +fn collatz(n: isize) -> isize { + match n { + 1 => 0, + _ => match n % 2 { + 0 => 1 + collatz(n / 2), + _ => 1 + collatz(n * 3 + 1), + }, + } +} + +/// Bundle iterator used by BundleGeneratorSync implementation for Locales. +struct BundleIter { + res_path_scheme: String, + locales: <Vec<LanguageIdentifier> as IntoIterator>::IntoIter, + res_ids: FxHashSet<ResourceId>, +} + +impl Iterator for BundleIter { + type Item = FluentBundleResult<FluentResource>; + + fn next(&mut self) -> Option<Self::Item> { + let locale = self.locales.next()?; + let res_path_scheme = self + .res_path_scheme + .as_str() + .replace("{locale}", &locale.to_string()); + let mut bundle = FluentBundle::new(vec![locale]); + + let mut errors = vec![]; + + for res_id in &self.res_ids { + let res_path = res_path_scheme.as_str().replace("{res_id}", &res_id.value); + let source = fs::read_to_string(res_path).unwrap(); + let res = match FluentResource::try_new(source) { + Ok(res) => res, + Err((res, err)) => { + errors.extend(err.into_iter().map(Into::into)); + res + } + }; + bundle.add_resource(res).unwrap(); + } + + if errors.is_empty() { + Some(Ok(bundle)) + } else { + Some(Err((bundle, errors))) + } + } +} + +impl futures::Stream for BundleIter { + type Item = FluentBundleResult<FluentResource>; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll<Option<Self::Item>> { + todo!() + } +} + +impl BundleGenerator for Bundles { + type Resource = FluentResource; + type LocalesIter = std::vec::IntoIter<LanguageIdentifier>; + type Iter = BundleIter; + type Stream = BundleIter; + + fn bundles_iter( + &self, + locales: std::vec::IntoIter<LanguageIdentifier>, + res_ids: FxHashSet<ResourceId>, + ) -> Self::Iter { + BundleIter { + res_path_scheme: self.res_path_scheme.to_string_lossy().to_string(), + locales, + res_ids, + } + } +} |