From b03b74a0f8f3e74a49fef24954b934dc995e2a64 Mon Sep 17 00:00:00 2001 From: Koichi Akabe Date: Fri, 3 Feb 2023 15:05:26 +0900 Subject: [PATCH 1/2] i18n and L10N --- examples/wasm/Cargo.toml | 7 +++- examples/wasm/locales/en.ftl | 6 ++++ examples/wasm/locales/ja.ftl | 6 ++++ examples/wasm/src/lib.rs | 8 +++-- examples/wasm/src/locales.rs | 58 +++++++++++++++++++++++++++++++++ examples/wasm/src/text_input.rs | 4 ++- examples/wasm/src/token_view.rs | 5 +-- 7 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 examples/wasm/locales/en.ftl create mode 100644 examples/wasm/locales/ja.ftl create mode 100644 examples/wasm/src/locales.rs diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index 70591db..ab81c1b 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -4,14 +4,19 @@ version = "0.1.0" edition = "2021" [dependencies] +fluent = "0.16.0" # MIT or Apache-2.0 +fluent-langneg = "0.13.0" # MIT or Apache-2.0 gloo-worker = "0.2.1" # MIT or Apache-2.0 +intl-memoizer = "0.5.1" # MIT or Apache-2.0 +once_cell = "1.17.0" # MIT or Apache-2.0 ouroboros = "0.15.5" # MIT or Apache-2.0 ruzstd = "0.3.0" # MIT serde = "1" # MIT or Apache-2.0 +unic-langid = { version = "0.9.1", features = ["macros"] } # MIT or Apache-2.0 vaporetto = { path = "../../vaporetto", default-features = false, features = ["std", "cache-type-score", "fix-weight-length", "tag-prediction"] } # MIT or Apache-2.0 vaporetto_rules = { path = "../../vaporetto_rules" } # MIT or Apache-2.0 wasm-bindgen = "0.2.83" # MIT or Apache-2.0 -web-sys = { version = "0.3", features = ["Event", "EventTarget", "InputEvent"] } # MIT or Apache-2.0 +web-sys = { version = "0.3.61", features = ["Event", "EventTarget", "InputEvent" ,"Navigator"] } # MIT or Apache-2.0 yew = { version = "0.20", features = ["csr"] } # MIT or Apache-2.0 [profile.release] diff --git a/examples/wasm/locales/en.ftl b/examples/wasm/locales/en.ftl new file mode 100644 index 0000000..89bc160 --- /dev/null +++ b/examples/wasm/locales/en.ftl @@ -0,0 +1,6 @@ +title = 🛥 Vaporetto Wasm Demo +project-page = [Project Page] +place-holder = Enter Japanese here +surface = Surface +tag = Tag {$id} +loading = Loading... diff --git a/examples/wasm/locales/ja.ftl b/examples/wasm/locales/ja.ftl new file mode 100644 index 0000000..ecad6cf --- /dev/null +++ b/examples/wasm/locales/ja.ftl @@ -0,0 +1,6 @@ +title = 🛥 Vaporetto Wasm デモ +project-page = [プロジェクトページ] +place-holder = ここに日本語を入力してください +surface = 表層形 +tag = タグ{$id} +loading = 読み込み中... diff --git a/examples/wasm/src/lib.rs b/examples/wasm/src/lib.rs index ccfbaf0..8702300 100644 --- a/examples/wasm/src/lib.rs +++ b/examples/wasm/src/lib.rs @@ -1,3 +1,5 @@ +pub mod locales; + pub mod text_input; pub mod token_view; @@ -178,9 +180,9 @@ impl Component for App { html! { <>
-

{"🛥 Vaporetto Wasm Demo"}

+

{ fluent_format!("title") }

@@ -207,7 +209,7 @@ impl Component for App { } } else { html! { -
{"Loading..."}
+
{ fluent_format!("loading") }
} } } diff --git a/examples/wasm/src/locales.rs b/examples/wasm/src/locales.rs new file mode 100644 index 0000000..3c9729f --- /dev/null +++ b/examples/wasm/src/locales.rs @@ -0,0 +1,58 @@ +use fluent::{bundle::FluentBundle, FluentResource}; +use fluent_langneg::NegotiationStrategy; +use intl_memoizer::concurrent::IntlLangMemoizer; +use once_cell::sync::Lazy; +use unic_langid::{langid, LanguageIdentifier}; + +pub static FLUENT_BUNDLE_DATA: Lazy> = + Lazy::new(|| { + let mut available_locales = std::collections::HashMap::new(); + + // Register locales + available_locales.insert(langid!("ja"), include_str!("../locales/en.ftl")); + available_locales.insert(langid!("en"), include_str!("../locales/ja.ftl")); + + let requested: Vec = web_sys::window() + .unwrap() + .navigator() + .languages() + .iter() + .map(|id| id.as_string().unwrap().parse().unwrap()) + .collect(); + let available: Vec = available_locales.keys().cloned().collect(); + let default: LanguageIdentifier = "en-US".parse().unwrap(); + let supported = fluent_langneg::negotiate_languages( + &requested, + &available, + Some(&default), + NegotiationStrategy::Filtering, + ); + let supported_lang = supported[0].clone(); + let res = + FluentResource::try_new(available_locales.get(&supported_lang).unwrap().to_string()) + .unwrap(); + let mut bundle = FluentBundle::new_concurrent(vec![supported_lang]); + bundle.add_resource(res).unwrap(); + bundle + }); + +#[macro_export] +macro_rules! fluent_format { + ( $id:expr ) => { + fluent_format!( $id, ) + }; + ( $id:expr, $($key:expr => $value:expr),* ) => { + { + let bundle = &$crate::locales::FLUENT_BUNDLE_DATA; + let msg = bundle.get_message($id).unwrap(); + let mut errors = vec![]; + let pattern = msg.value().unwrap(); + #[allow(unused_mut)] + let mut args: fluent::FluentArgs = fluent::FluentArgs::new(); + $( + args.set($key, $value); + )* + $crate::locales::FLUENT_BUNDLE_DATA.format_pattern(&pattern, Some(&args), &mut errors).to_string() + } + }; +} diff --git a/examples/wasm/src/text_input.rs b/examples/wasm/src/text_input.rs index 535c34e..7c24431 100644 --- a/examples/wasm/src/text_input.rs +++ b/examples/wasm/src/text_input.rs @@ -4,6 +4,8 @@ use wasm_bindgen::{JsCast, UnwrapThrowExt}; use web_sys::{Event, HtmlInputElement, InputEvent}; use yew::{html, Callback, Component, Context, Html, NodeRef, Properties}; +use crate::fluent_format; + #[derive(Clone, PartialEq, Properties)] pub struct Props { pub value: Rc, @@ -39,7 +41,7 @@ impl Component for TextInput { diff --git a/examples/wasm/src/token_view.rs b/examples/wasm/src/token_view.rs index ebf91c2..80014e2 100644 --- a/examples/wasm/src/token_view.rs +++ b/examples/wasm/src/token_view.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use yew::{function_component, html, Html, Properties}; +use crate::fluent_format; use crate::Token; #[derive(Clone, PartialEq, Properties)] @@ -18,10 +19,10 @@ pub fn token_view(props: &Props) -> Html { - + { for (1..*n_tags + 1).map(|i| html! { - + }) } From 599b0b521f5476ff5de657106265ccd647dfb3f8 Mon Sep 17 00:00:00 2001 From: Koichi Akabe Date: Fri, 3 Feb 2023 15:40:32 +0900 Subject: [PATCH 2/2] fix --- examples/wasm/src/locales.rs | 43 ++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/examples/wasm/src/locales.rs b/examples/wasm/src/locales.rs index 3c9729f..dd1ea06 100644 --- a/examples/wasm/src/locales.rs +++ b/examples/wasm/src/locales.rs @@ -9,25 +9,44 @@ pub static FLUENT_BUNDLE_DATA: Lazy = web_sys::window() + // Preffered language + let available: Vec = available_locales.keys().cloned().collect(); + let requested: LanguageIdentifier = web_sys::window() .unwrap() .navigator() - .languages() - .iter() - .map(|id| id.as_string().unwrap().parse().unwrap()) - .collect(); - let available: Vec = available_locales.keys().cloned().collect(); - let default: LanguageIdentifier = "en-US".parse().unwrap(); + .language() + .unwrap() + .parse() + .unwrap(); let supported = fluent_langneg::negotiate_languages( - &requested, + &[requested], &available, - Some(&default), + None, NegotiationStrategy::Filtering, ); - let supported_lang = supported[0].clone(); + let supported_lang = if supported.is_empty() { + // Alternative language + let requested: Vec = web_sys::window() + .unwrap() + .navigator() + .languages() + .iter() + .map(|id| id.as_string().unwrap().parse().unwrap()) + .collect(); + let default: LanguageIdentifier = "en".parse().unwrap(); + fluent_langneg::negotiate_languages( + &requested, + &available, + Some(&default), + NegotiationStrategy::Filtering, + )[0] + .clone() + } else { + supported[0].clone() + }; let res = FluentResource::try_new(available_locales.get(&supported_lang).unwrap().to_string()) .unwrap();
{"Surface"}{ fluent_format!("surface") }{"Tag "}{i.to_string()}{ fluent_format!("tag", "id" => i) }