✨ (locale): Añade Lc::with_number() para plurales
`with_arg()` sólo interpola texto, y el selector de plural de Fluent
(`{ $n -> [one] ... *[other] ... }`) sólo aplica las reglas de plural
cuando el argumento es un FluentValue::Number.
This commit is contained in:
parent
5d199ef10d
commit
3df73f0551
2 changed files with 74 additions and 8 deletions
|
|
@ -12,6 +12,8 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
// **< LcKind >*************************************************************************************
|
||||||
|
|
||||||
// Tipo de localización a aplicar.
|
// Tipo de localización a aplicar.
|
||||||
//
|
//
|
||||||
// * `None` - No se aplica ninguna localización.
|
// * `None` - No se aplica ninguna localización.
|
||||||
|
|
@ -25,6 +27,31 @@ enum LcKind {
|
||||||
Translate(CowStr),
|
Translate(CowStr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< LcArg >**************************************************************************************
|
||||||
|
|
||||||
|
// Valor de un argumento de la traducción.
|
||||||
|
//
|
||||||
|
// `Text` resuelve a `FluentValue::String` para interpolar cualquier texto; pero no aplica el
|
||||||
|
// selector de plural de Fluent (`{ $n -> [one] ... *[other] ... }`). Para eso está `Number`, que
|
||||||
|
// resuelve a `FluentValue::Number` y es el único tipo con el que Fluent consulta las reglas de
|
||||||
|
// plural del idioma.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum LcArg {
|
||||||
|
Text(CowStr),
|
||||||
|
Number(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a LcArg> for fluent_templates::fluent_bundle::FluentValue<'a> {
|
||||||
|
fn from(arg: &'a LcArg) -> Self {
|
||||||
|
match arg {
|
||||||
|
LcArg::Text(s) => s.as_ref().into(),
|
||||||
|
LcArg::Number(n) => (*n).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Lc >*****************************************************************************************
|
||||||
|
|
||||||
/// Crea instancias para traducir *textos localizados*.
|
/// Crea instancias para traducir *textos localizados*.
|
||||||
///
|
///
|
||||||
/// Cada instancia puede representar:
|
/// Cada instancia puede representar:
|
||||||
|
|
@ -81,7 +108,7 @@ pub struct Lc {
|
||||||
op: LcKind,
|
op: LcKind,
|
||||||
#[default(&LOCALES_PAGETOP)]
|
#[default(&LOCALES_PAGETOP)]
|
||||||
locales: &'static Locales,
|
locales: &'static Locales,
|
||||||
args: Vec<(CowStr, CowStr)>,
|
args: Vec<(CowStr, LcArg)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Lc {
|
impl fmt::Debug for Lc {
|
||||||
|
|
@ -141,22 +168,46 @@ impl Lc {
|
||||||
// **< Lc BUILDER >*****************************************************************************
|
// **< Lc BUILDER >*****************************************************************************
|
||||||
|
|
||||||
/// Añade un argumento `{$arg}` => `value` a la traducción.
|
/// Añade un argumento `{$arg}` => `value` a la traducción.
|
||||||
|
///
|
||||||
|
/// `value` se interpreta siempre como texto. Si se necesita un argumento numérico que además
|
||||||
|
/// deba seleccionar plural (`{ $n -> [one] ... *[other] ... }`), hay que utilizar en su lugar
|
||||||
|
/// [`with_number()`](Self::with_number).
|
||||||
pub fn with_arg(mut self, arg: impl Into<CowStr>, value: impl Into<CowStr>) -> Self {
|
pub fn with_arg(mut self, arg: impl Into<CowStr>, value: impl Into<CowStr>) -> Self {
|
||||||
self.args.push((arg.into(), value.into()));
|
self.args.push((arg.into(), LcArg::Text(value.into())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Añade varios argumentos a la traducción de una vez (p. ej. usando la macro
|
/// Añade varios argumentos de texto a la traducción de una vez (p. ej. usando la macro
|
||||||
/// [`util::kv!`](crate::util::kv) o también `vec![("k", "v")]`, incluso un array de duplas u
|
/// [`util::kv!`](crate::util::kv) o también `vec![("k", "v")]`, incluso un array de duplas u
|
||||||
/// otras colecciones).
|
/// otras colecciones). Aplica sólo a texto, como [`with_arg()`](Self::with_arg).
|
||||||
pub fn with_args<I, K, V>(mut self, args: I) -> Self
|
pub fn with_args<I, K, V>(mut self, args: I) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = (K, V)>,
|
I: IntoIterator<Item = (K, V)>,
|
||||||
K: Into<CowStr>,
|
K: Into<CowStr>,
|
||||||
V: Into<CowStr>,
|
V: Into<CowStr>,
|
||||||
{
|
{
|
||||||
self.args
|
self.args.extend(
|
||||||
.extend(args.into_iter().map(|(k, v)| (k.into(), v.into())));
|
args.into_iter()
|
||||||
|
.map(|(k, v)| (k.into(), LcArg::Text(v.into()))),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Añade un argumento numérico `{$arg}` => `n` a la traducción.
|
||||||
|
///
|
||||||
|
/// A diferencia de [`with_arg()`](Self::with_arg), `n` se interpola como un número real, el
|
||||||
|
/// único tipo en el que Fluent aplica las reglas de plural del idioma para que un selector
|
||||||
|
/// `{ $n -> [one] ... *[other] ... }` elija correctamente entre singular y plural.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// // "1 año" / "3 años", según el idioma y el valor de `n`.
|
||||||
|
/// let text = Lc::l("years_count").with_number("n", 3).get();
|
||||||
|
/// ```
|
||||||
|
pub fn with_number(mut self, arg: impl Into<CowStr>, n: u32) -> Self {
|
||||||
|
self.args.push((arg.into(), LcArg::Number(n)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +262,7 @@ impl Lc {
|
||||||
} else {
|
} else {
|
||||||
let mut args = HashMap::with_capacity(self.args.len());
|
let mut args = HashMap::with_capacity(self.args.len());
|
||||||
for (k, v) in self.args.iter() {
|
for (k, v) in self.args.iter() {
|
||||||
args.insert(k.clone(), v.as_ref().into());
|
args.insert(k.clone(), v.into());
|
||||||
}
|
}
|
||||||
self.locales
|
self.locales
|
||||||
.try_lookup_with_args(language.langid(), key.as_ref(), &args)
|
.try_lookup_with_args(language.langid(), key.as_ref(), &args)
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,27 @@ async fn translation_with_plural_and_select() {
|
||||||
assert!(translation.contains("añadido 3 nuevas fotos de él"));
|
assert!(translation.contains("añadido 3 nuevas fotos de él"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `with_arg()` only interpolates text: `photoCount` as a string ("3") can never select the `[one]`
|
||||||
|
// plural branch, whatever its value. Only `with_number()` produces a real numeric argument that
|
||||||
|
// Fluent's plural rules can match against.
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn translation_with_number_selects_the_singular_plural_branch() {
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
let lc = Lc::l("test_shared_photos")
|
||||||
|
.with_arg("userName", "Roberto")
|
||||||
|
.with_number("photoCount", 1)
|
||||||
|
.with_arg("userGender", "male");
|
||||||
|
let translation = lc.lookup(&Locale::resolve("es-ES")).unwrap();
|
||||||
|
assert!(translation.contains("ha añadido una nueva foto de él"));
|
||||||
|
}
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn check_fallback_language() {
|
async fn check_fallback_language() {
|
||||||
setup().await;
|
setup().await;
|
||||||
|
|
||||||
let lc = Lc::l("test_hello_world");
|
let lc = Lc::l("test_hello_world");
|
||||||
let translation = lc.lookup(&Locale::resolve("xx-YY")); // Retrocede a "en-US".
|
let translation = lc.lookup(&Locale::resolve("xx-YY")); // Falls back to "en-US".
|
||||||
assert_eq!(translation, Some("Hello world!".to_string()));
|
assert_eq!(translation, Some("Hello world!".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue