Will need a refactor to support more complex use-cases - currently only vecs are supported.
XPGOKS6XWM2Q2R74DDEHQRMZH6BWQH2FMQGAXBLO7FW52J3VFKSQC
XDJBTEXUZNIAC2TKC4Z3OZORWAXR4ZWYUHNM6OEGXTL6WZDXOVZQC
VNSHGQYNPGKGGPYNVP4Z2RWD7JCSDJVYAADD6UXWBYL6ZRXKLE4AC
XEEXWJLGVIPIGURSDU4ETZMGAIFTFDPECM4QWFOSRHU7GMGVOUVQC
2SITVDYW6KANM24QXRHVSBL6S77UHKJLOSOHSUZQBJFL5NAAGQYAC
QFPQZR4K4UZ7R2GQZJG4NYBGVQJVL2ANIKGGTOHAMIRIBQHPSQGAC
3NMKD6I57ONAGHEN4PZIAV2KPYESVR4JL3DTWSHXKCMVJBEQ4GIQC
5TEX4MNUC4LDDRMNEOVCFNUUEZAGUXMKO3OIEQFXWRQKXSHY2NRQC
CESJ4CTO26X4GBZBPXRXLOJT3JQJOGFN5EJSNAAZELNQRZF7QSYAC
length = Item count: { LEN($items) }
numbers = { LEN($items) ->
[0] No items
[1] One item
*[other] { LEN($items) } items
}
plurals = { LEN($items) ->
[one] One item
*[other] { LEN($items) } items
}
//! End-to-end test for function support in the `l10n_embed_derive` macro
mod common;
use common::compare_message;
use icu_locale::{Locale, locale};
use l10n_embed_derive::localize;
use rstest::rstest;
const DEFAULT_LOCALE: Locale = locale!("en-US");
#[localize("tests/locale/**/functions.ftl")]
pub enum MessageEnum {
Length { items: Vec<String> },
Numbers { items: Vec<String> },
Plurals { items: Vec<String> },
}
#[localize("tests/locale/**/functions.ftl")]
pub struct Length {
items: Vec<String>,
}
#[localize("tests/locale/**/functions.ftl")]
pub struct Numbers {
items: Vec<String>,
}
#[localize("tests/locale/**/functions.ftl")]
pub struct Plurals {
items: Vec<String>,
}
#[rstest]
#[case::zero(0)]
#[case::one(1)]
#[case::two(2)]
fn len(#[case] item_count: usize) {
let items: Vec<String> = (0..item_count)
.map(|index| format!("Item {index}"))
.collect();
compare_message(
MessageEnum::Length {
items: items.clone(),
},
format!("Item count: {item_count}"),
DEFAULT_LOCALE,
);
compare_message(
Length { items },
format!("Item count: {item_count}"),
DEFAULT_LOCALE,
);
}
#[rstest]
#[case::zero(0, "No items")]
#[case::one(1, "One item")]
#[case::two(2, "2 items")]
fn numbers(#[case] item_count: usize, #[case] expected: &str) {
let items: Vec<String> = (0..item_count)
.map(|index| format!("Item {index}"))
.collect();
compare_message(
MessageEnum::Numbers {
items: items.clone(),
},
expected,
DEFAULT_LOCALE,
);
compare_message(Numbers { items }, expected, DEFAULT_LOCALE);
}
#[rstest]
#[case::zero(0, "0 items")]
#[case::one(1, "One item")]
#[case::two(2, "2 items")]
fn plurals(#[case] item_count: usize, #[case] expected: &str) {
let items: Vec<String> = (0..item_count)
.map(|index| format!("Item {index}"))
.collect();
compare_message(
MessageEnum::Plurals {
items: items.clone(),
},
expected,
DEFAULT_LOCALE,
);
compare_message(Plurals { items }, expected, DEFAULT_LOCALE);
}
#[error("unable to parse Fluent function")]
pub enum FunctionError {
InvalidName {
name: String,
#[source_code]
source_code: NamedSource<String>,
// TODO: blocked on https://github.com/projectfluent/fluent-rs/pull/373
// #[label("This key isn't in the `{canonical_locale}` locale")]
// span: SourceSpan,
},
IncorrectPositionalArgumentCount {
expected_len: usize,
actual_len: usize,
#[source_code]
source_code: NamedSource<String>,
// TODO: blocked on https://github.com/projectfluent/fluent-rs/pull/373
// #[label("This key isn't in the `{canonical_locale}` locale")]
// span: SourceSpan,
},
UnexpectedNamedArguments {
#[source_code]
source_code: NamedSource<String>,
// TODO: blocked on https://github.com/projectfluent/fluent-rs/pull/373
// #[label("This key isn't in the `{canonical_locale}` locale")]
// span: SourceSpan,
},
NonVariableReference {
#[source_code]
source_code: NamedSource<String>,
// TODO: blocked on https://github.com/projectfluent/fluent-rs/pull/373
// #[label("This key isn't in the `{canonical_locale}` locale")]
// span: SourceSpan,
},
}
#[derive(Diagnostic, Debug, Error)]
// Only dereference if enum variant
let category_reference =
match message_context.derive_context.reference_kind {
let category_reference = match selector {
// Can't dereference value returned by function
InlineExpression::FunctionReference { .. } => {
quote!(#raw_match_target)
}
// Only dereference if enum variant
_ => match message_context.derive_context.reference_kind {
InlineExpression::FunctionReference { id, arguments } => {
match id.name.to_snake_case().as_str() {
"len" => {
if !arguments.named.is_empty() {
return Err(Error::Function(FunctionError::UnexpectedNamedArguments {
source_code: message_context.source.named_source.clone(),
}));
}
let argument = match arguments.positional.as_slice() {
[single_argument] => single_argument,
_ => {
return Err(Error::Function(
FunctionError::IncorrectPositionalArgumentCount {
expected_len: 1,
actual_len: arguments.positional.len(),
source_code: message_context.source.named_source.clone(),
},
));
}
};
let variable_reference = match argument {
InlineExpression::VariableReference { .. } => {
inline_expression(argument, message_context)?
}
_ => {
return Err(Error::Function(FunctionError::NonVariableReference {
source_code: message_context.source.named_source.clone(),
}));
}
};
let vec_reference = match message_context.derive_context.reference_kind {
ReferenceKind::EnumField => quote!(#variable_reference),
ReferenceKind::StructField => quote!(&#variable_reference),
};
parse_quote!(Vec::len(#vec_reference))
}
_ => {
return Err(Error::Function(FunctionError::InvalidName {
name: id.name.clone(),
source_code: message_context.source.named_source.clone(),
}));
}
}
}