summaryrefslogtreecommitdiffstats
path: root/third_party/rust/fluent-fallback/examples/simple-fallback.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/fluent-fallback/examples/simple-fallback.rs
parentInitial commit. (diff)
downloadfirefox-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.rs237
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,
+ }
+ }
+}