replacement in fluent_embed_derive/tests/selectors.rs at line 46
[4.1450]→[4.1450:1523](∅→∅) − assert_eq!(data.message_for_locale(&language_id), expected_message);
+ let mut buffer = Vec::new();
+ data.message_for_locale(&mut buffer, &language_id);
+ assert_eq!(String::from_utf8(buffer), Ok(expected_message));
replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 31
− let locales = match &derive_input.data {
+ let available_locales = match &derive_input.data {
replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 48
[2.1507]→[2.1507:1558](∅→∅) − impl ::fluent_embed::Localize for #ident {
+ impl<W: ::std::io::Write> ::fluent_embed::Localize<W> for #ident {
replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 52
[2.1714]→[2.1714:1853](∅→∅) − fn message_for_locale(&self, locale: &::fluent_embed::icu_locid::LanguageIdentifier) -> String {
− #message_body
+ fn available_locales(&self) -> Vec<::fluent_embed::icu_locid::LanguageIdentifier> {
+ #available_locales
replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 56
[2.1868]→[2.1868:2149](∅→∅) − fn localize(&self) -> String {
− let available_locales = #locales;
− let selected_locale = ::fluent_embed::locale_select::match_locales(&available_locales, &Self::CANONICAL_LOCALE);
−
− self.message_for_locale(&selected_locale)
+ fn message_for_locale(
+ &self,
+ writer: &mut W,
+ locale: &::fluent_embed::icu_locid::LanguageIdentifier,
+ ) -> Result<(), ::fluent_embed::LocalizationError> {
+ #message_body
replacement in fluent_embed_derive/src/macro_impl/derive.rs at line 46
[4.2509]→[4.1181:1275](∅→∅),
[4.713]→[4.1181:1275](∅→∅) − #(if locale.normalizing_eq(#additional_locales) { return #additional_messages })else*
+ #(if locale.normalizing_eq(#additional_locales) {
+ #additional_messages
+ return Ok(());
+ }) else*
edit in fluent_embed_derive/src/macro_impl/derive.rs at line 52
edit in fluent_embed_derive/src/fluent/ast.rs at line 1
+ use std::cell::OnceCell;
+
replacement in fluent_embed_derive/src/fluent/ast.rs at line 13
[4.120]→[4.352:436](∅→∅),
[4.283]→[4.352:436](∅→∅),
[4.3123]→[4.352:436](∅→∅),
[4.3166]→[4.352:436](∅→∅),
[4.6657]→[4.352:436](∅→∅),
[4.352]→[4.352:436](∅→∅) − let mut format_body = String::new();
− let mut format_arguments = Vec::new();
+ let mut write_expressions: Vec<syn::Expr> =
+ Vec::with_capacity(message_context.pattern.elements.len());
replacement in fluent_embed_derive/src/fluent/ast.rs at line 18
− PatternElement::TextElement { value } => format_body.push_str(value),
+ PatternElement::TextElement { value } => {
+ let byte_string_literal = proc_macro2::Literal::byte_string(value.as_bytes());
+ write_expressions.push(parse_quote!(writer.write_all(#byte_string_literal)?));
+ }
replacement in fluent_embed_derive/src/fluent/ast.rs at line 23
[4.640]→[4.3167:3219](∅→∅) − let expression = match expression {
+ write_expressions.push(match expression {
replacement in fluent_embed_derive/src/fluent/ast.rs at line 25
[4.3286]→[4.6714:6888](∅→∅) − let target = inline_expression(selector, message_context)?;
− let mut arms: Vec<syn::Arm> = Vec::with_capacity(variants.len());
+ let match_target = inline_expression(selector, message_context)?;
+ let default_arm = OnceCell::new();
+ let mut additional_arms = Vec::with_capacity(variants.len());
replacement in fluent_embed_derive/src/fluent/ast.rs at line 30
[4.6939]→[4.6939:7017](∅→∅) − let base_pattern: syn::Pat = match &variant.key {
+ let variant_pattern: syn::Pat = match &variant.key {
replacement in fluent_embed_derive/src/fluent/ast.rs at line 32
[4.7086]→[4.7086:7178](∅→∅),
[4.7178]→[4.269:371](∅→∅) − let ident = format_ident!("{}", name.to_pascal_case());
− parse_quote!(::fluent_embed::icu_plurals::PluralCategory::#ident)
+ let ident_pascal_case =
+ format_ident!("{}", name.to_pascal_case());
+ parse_quote!(::fluent_embed::icu_plurals::PluralCategory::#ident_pascal_case)
replacement in fluent_embed_derive/src/fluent/ast.rs at line 38
[4.7408]→[4.7408:7486](∅→∅) − // Create a new `MessageContext` for each variant
+
+ // Create a new `MessageContext` for each variant body
replacement in fluent_embed_derive/src/fluent/ast.rs at line 44
[4.7691]→[4.7691:7762](∅→∅) − let body = message_body(variant_context)?;
+ let variant_body = message_body(variant_context)?;
+
+ // The default pattern must go last so we don't generate invalid match stataments
replacement in fluent_embed_derive/src/fluent/ast.rs at line 48
[4.7763]→[4.7763:8138](∅→∅) − // Default patterns match anything else
− // TODO: this can potentially generate unreachable patterns,
− // should be replaced with a more sophisticated implementation
− let pattern = if variant.default {
− parse_quote!(#base_pattern | _)
+ if variant.default {
+ default_arm
+ .set(quote!(#variant_pattern | _ => #variant_body))
+ .expect("Multiple default patterns for match statement.");
replacement in fluent_embed_derive/src/fluent/ast.rs at line 53
[4.8175]→[4.8175:8251](∅→∅) + additional_arms.push(quote!(#variant_pattern => #variant_body));
+ }
+ }
replacement in fluent_embed_derive/src/fluent/ast.rs at line 57
[4.3625]→[4.8252:8350](∅→∅) − arms.push(parse_quote!(#pattern => #body));
− }
+ // TODO: handle this as an error
+ let default_arm = default_arm.get().unwrap();
replacement in fluent_embed_derive/src/fluent/ast.rs at line 61
[4.3664]→[4.3664:3778](∅→∅) − match plural_rules.category_for(#target) {
− #(#arms),*
+ match plural_rules.category_for(#match_target) {
+ #(#additional_arms,)*
+ #default_arm,
edit in fluent_embed_derive/src/fluent/ast.rs at line 65
[4.3808]→[4.3808:3809](∅→∅) replacement in fluent_embed_derive/src/fluent/ast.rs at line 68
[4.3913]→[4.8352:8424](∅→∅) − inline_expression(expression, message_context)?
+ let expression = inline_expression(expression, message_context)?;
+ parse_quote!(writer.write_all(#expression)?)
replacement in fluent_embed_derive/src/fluent/ast.rs at line 71
[4.4017]→[4.4017:4036](∅→∅),
[4.189]→[4.714:818](∅→∅),
[4.3208]→[4.714:818](∅→∅),
[4.4036]→[4.714:818](∅→∅),
[4.714]→[4.714:818](∅→∅) − };
− format_body.push_str("{}");
− format_arguments.push(quote!(#expression));
replacement in fluent_embed_derive/src/fluent/ast.rs at line 76
[4.849]→[4.849:943](∅→∅),
[4.1018]→[4.1018:1019](∅→∅),
[4.1019]→[4.4037:4127](∅→∅) − let format_body_literal = proc_macro2::Literal::string(format_body.to_string().as_str());
−
− Ok(parse_quote!(
− format!(#format_body_literal, #(#format_arguments),*)
− ))
+ Ok(parse_quote! {
+ {
+ #(#write_expressions;)*
+ }
+ })
replacement in fluent_embed_derive/src/fluent/ast.rs at line 89
[4.1824]→[4.1824:1936](∅→∅) − let string_literal = proc_macro2::Literal::string(value);
− parse_quote!(#string_literal)
+ let byte_string_literal = proc_macro2::Literal::byte_string(value.as_bytes());
+ parse_quote!(#byte_string_literal)
replacement in fluent_embed_derive/src/fluent/ast.rs at line 101
[4.4416]→[4.4416:4489](∅→∅) − // let ident = format_ident!("{}", id.name.to_snake_case());
+ let fluent_name = &id.name;
+ let rust_name = id.name.to_snake_case();
+
replacement in fluent_embed_derive/src/fluent/ast.rs at line 107
[4.8625]→[4.8625:8672](∅→∅) − .get(&id.name.to_snake_case())
replacement in fluent_embed_derive/src/fluent/ast.rs at line 116
[4.5925]→[4.5925:5977](∅→∅) − .find(&format!("${}", id.name))
+ .find(&format!("${fluent_name}"))
replacement in fluent_embed_derive/src/fluent/ast.rs at line 121
[4.6091]→[4.6091:6197](∅→∅) − fluent_name: id.name.clone(),
− rust_name: id.name.to_snake_case(),
+ fluent_name: fluent_name.clone(),
+ rust_name,
replacement in fluent_embed_derive/src/fluent/ast.rs at line 124
[4.6230]→[4.6230:6305](∅→∅) − span: SourceSpan::new(location.into(), id.name.len()),
+ span: SourceSpan::new(location.into(), fluent_name.len()),
replacement in fluent_embed_derive/src/fluent/ast.rs at line 140
[4.3850]→[4.3850:3923](∅→∅) − ReferenceKind::StructField => parse_quote!(self.#ident),
+ ReferenceKind::StructField => parse_quote!(message_context.#ident),
replacement in fluent_embed/src/time.rs at line 1
+ use crate::{LocalizationError, Localize};
edit in fluent_embed/src/time.rs at line 10
+ use writeable::Writeable;
edit in fluent_embed/src/time.rs at line 16
−
− /// The locale to fall back to
− pub const DEFAULT_LOCALE: LanguageIdentifier = langid!("en-US");
replacement in fluent_embed/src/time.rs at line 26
− impl Localize for RelativeTime {
− const CANONICAL_LOCALE: LanguageIdentifier = DEFAULT_LOCALE;
+ impl<W: std::io::Write> Localize<W> for RelativeTime {
+ const CANONICAL_LOCALE: LanguageIdentifier = langid!("en-US");
+
+ fn available_locales(&self) -> Vec<LanguageIdentifier> {
+ // TODO: keep track of all locales with Fluent data, and return only those
+ vec![<Self as Localize<W>>::CANONICAL_LOCALE]
+ }
replacement in fluent_embed/src/time.rs at line 34
− fn message_for_locale(&self, locale: &LanguageIdentifier) -> String {
+ fn message_for_locale(
+ &self,
+ writer: &mut W,
+ locale: &LanguageIdentifier,
+ ) -> Result<(), LocalizationError> {
replacement in fluent_embed/src/time.rs at line 112
[4.4306]→[4.4306:4358](∅→∅) − formatter.format(decimal).to_string()
− }
+ let formatted_text = formatter.format(decimal);
+
+ writer.write_all(formatted_text.write_to_string().as_bytes())?;
+ writer.flush()?;
replacement in fluent_embed/src/time.rs at line 117
− fn localize(&self) -> String {
− // TODO: select locale based on environment
− self.message_for_locale(&Self::CANONICAL_LOCALE)
replacement in fluent_embed/src/lib.rs at line 14
+ #[derive(thiserror::Error, Debug)]
+ pub enum LocalizationError {
+ #[error("invalid locale selected")]
+ InvalidLocale,
+ #[error("unable to write localized output")]
+ IO(#[from] std::io::Error),
+ }
+
+ pub trait Localize<W: std::io::Write> {
+ // TODO: this should be project-wide and tracked at build time
replacement in fluent_embed/src/lib.rs at line 26
[4.512]→[4.2853:2960](∅→∅) − fn message_for_locale(&self, locale: &LanguageIdentifier) -> String;
− fn localize(&self) -> String;
+ fn available_locales(&self) -> Vec<LanguageIdentifier>;
+ fn message_for_locale(
+ &self,
+ writer: &mut W,
+ locale: &LanguageIdentifier,
+ ) -> Result<(), LocalizationError>;
+
+ fn localize(&self, writer: &mut W) -> Result<(), LocalizationError> {
+ let available_locales = self.available_locales();
+ let selected_locale = locale_select::match_locales(
+ &available_locales,
+ &<Self as Localize<W>>::CANONICAL_LOCALE,
+ );
+ self.message_for_locale(writer, &selected_locale)
+ }
edit in fluent_embed/Cargo.toml at line 11
+ fluent_embed_derive = { path = "../fluent_embed_derive" }
replacement in fluent_embed/Cargo.toml at line 18
[4.1307]→[4.948:1006](∅→∅) − fluent_embed_derive = { path = "../fluent_embed_derive" }
+ thiserror = "1.0.61"
+ writeable = "0.5.5"
edit in Cargo.lock at line 185
+ "thiserror",
+ "writeable",