Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Foreign keys to follow i18next nesting syntax and behavior #128

Merged
merged 19 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,10 +395,10 @@ But floats (`f32` and `f64`) are not accepted in match statements, so they expan
The plural above would generate code similar to this:

```rust
let plural_count = f32::from(count());
if plural_count == 0.0 {
let count = f32::from(count());
if count == 0.0 {
// render "You are broke"
} else if (..0.0).contains(&plural_count) {
} else if (..0.0).contains(&count) {
// render "You owe money"
} else {
// render "You have {{ count }}€"
Expand Down Expand Up @@ -432,7 +432,7 @@ To supply the count for the plural in the `t!` macro, use `$`:

```rust
let count = move || counter.get();
t!(i18n, click_count, $ = count)
t!(i18n, click_count, count = count)
```

### Subkeys
Expand Down
3 changes: 3 additions & 0 deletions docs/book/src/declare/01_key_value.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ If a key is present in another locale but not in the default locale, this key wi
You can specify multiple kinds of values:

- String
- Numbers
- Boolean
- Interpolated String
- Ranges
- Plurals

The next chapters of this section will cover them, apart for strings, those are self explanatory.
20 changes: 16 additions & 4 deletions docs/book/src/declare/03_plurals.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Providing the count to the `t!` macro with the `$`, this will result in:
```rust
let i18n = use_i18n();

t!(i18n, items, $ = || 0) // -> "0 items"
t!(i18n, items, $ = || 1) // -> "1 item"
t!(i18n, items, $ = || 4) // -> "4 items"
t!(i18n, items, count = || 0) // -> "0 items"
t!(i18n, items, count = || 1) // -> "1 item"
t!(i18n, items, count = || 4) // -> "4 items"
```

`{{ count }}` is a special variable when using plurals, you don't supply it as `t!(i18n, key, count = ..)` but with the `$`.
Expand Down Expand Up @@ -78,8 +78,20 @@ You can use them by using the `_ordinal` suffix:
}
```

> the `_ordinal` suffix is removed, in this example you access it with `t!(i18n, key, $ = ..)`
> the `_ordinal` suffix is removed, in this example you access it with `t!(i18n, key, count = ..)`

## How to know which to use:

There are ressources online to help you find what you should use, my personnal favorite is the [unicode CLDR Charts](https://www.unicode.org/cldr/charts/44/supplemental/language_plural_rules.html).

## What if I need multiple counts ?

If you need multiple counts, for example:

```json
{
"key": "{{ boys_count }} boys and {{ girls_count }} girls"
}
```

There isn't a way to represent this in a single key, You will need `Foreign keys` that you can read about in a future chapter.
18 changes: 15 additions & 3 deletions docs/book/src/declare/04_ranges.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,31 @@ With ranges, `{{ count }}` is a special variable that refers to the count provid
```

```rust
t!(i18n, click_count, $ = || 0);
t!(i18n, click_count, count = || 0);
```

Will result in `"You have not clicked yet"` and

```rust
t!(i18n, click_count, $ = || 5);
t!(i18n, click_count, count = || 5);
```

Will result in `"You clicked 5 times"`.

Providing `count` will create an error:

```rust
t!(i18n, click_count, count = 12, $ = || 5); // compilation error
t!(i18n, click_count, count = 12, count = || 5); // compilation error
```

## What if I need multiple counts ?

If you need multiple counts, for example:

```json
{
"key": "{{ boys_count }} boys and {{ girls_count }} girls"
}
```

There isn't a way to represent this in a single key, You will need `Foreign keys` that you can read about in a future chapter.
108 changes: 101 additions & 7 deletions docs/book/src/declare/06_foreign_keys.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Foreign keys

Foreign keys let you re-use already declared translations, you declare them like variables but with a '@' before the path:
Foreign keys let you re-use already declared translations:

```json
{
"hello_world": "Hello World!",
"reuse": "message: {{ @hello_world }}"
"reuse": "message: $t(hello_world)"
}
```

This will replace `{{ @hello_world }}` by the value of the key `hello_world`, making `reuse` equal to `"message: Hello World!"`.
This will replace `$t(hello_world)` by the value of the key `hello_world`, making `reuse` equal to `"message: Hello World!"`.

You can point to any key other than ranges and keys containing subkeys.

To point to subkeys you give the path by separating the the key by `.`: `{{ @key.subkey.subsubkey }}`.
To point to subkeys you give the path by separating the the key by `.`: `$t(key.subkey.subsubkey)`.

When using namespaces you _must_ specify the namespace of the key you are looking for, using `::`: `{{ @namespace::key }}`.
When using namespaces you _must_ specify the namespace of the key you are looking for, using `:`: `$t(namespace:key)`.

You can point to explicitly defaulted keys, but not implicitly defaulted ones.

Expand All @@ -26,12 +26,106 @@ You can also supply arguments to fill variables of the pointed key:
```json
{
"click_count": "You clicked {{ count }} times",
"clicked_twice": "{{ @click_count, count = 'two' }}"
"clicked_twice": "$t(click_count, {\"count\": \"two\"})"
}
```

This will result to `clicked_twice` to have the value `"You clicked two times"`.

Arguments must be string, delimited by either single quotes or double quotes.

**Note**: Any argument with no matching variable are just discarded, they will not emit any warning/error.
> **Note**: Any argument with no matching variable are just discarded, they will not emit any warning/error.

Arguments can be anything that could be parsed as a normal key value:

```json
{
"key": "{{ arg }}",
"string_arg": "$t(key, {\"arg\": \"str\"})",
"boolean_arg": "$t(key, {\"arg\": true})",
"number_arg": "$t(key, {\"arg\": 56})",
"interpolated_arg": "$t(key, {\"arg\": \"value: {{ new_arg }}\"})",
"foreign_key_arg": "$t(key, {\"arg\": \"value: $t(interpolated_arg)\"})"
}
```

```rust
t!(i18n, string_arg); // -> "str"
t!(i18n, boolean_arg); // -> "true"
t!(i18n, number_arg); // -> "56"
t!(i18n, interpolated_arg, new_arg = "a value"); // -> "value: a value"
t!(i18n, foreign_key_arg, new_arg = "a value"); // -> "value: value: a value"
```

## `"count"` arg for plurals/ranges

If you have a plural like

```json
{
"key_one": "one item",
"key_other": "{{ count }} items"
}
```

You can supply the count as a foreign key in 2 ways, as a variable:

```json
{
"new_key": "$t(key, {\"count\": \"{{ new_count }}\"})"
}
```

This will just rename the key:

```rust
t!(i18n, new_key, new_count = move || 1); // -> "one item"
t!(i18n, new_key, new_count = move || 2); // -> "2 items"
```

> **note**: for the `count` arg to plurals/ranges, the value provided must be single variable (whitespaces around are supported though).

Or by an actual value:

```json
{
"singular_key": "$t(key, {\"count\": 1})",
"multiple_key": "$t(key, {\"count\": 6})"
}
```

```rust
t!(i18n, singular_key); // -> "one item"
t!(i18n, multiple_key); // -> "6 items"
```

> **note**: while floats are supported, they don't carry all the informations once deserialized such as leading 0, so some truncation may occur.

## Multiple counts ranges or plurals

If you need multiple counts for a plural or a range, for example:

```json
{
"key": "{{ boys_count }} boys and {{ girls_count }} girls"
}
```

You can use `Foreign keys` to construct a single key from multiple plurals/ranges by overriding there `"count"` variable:

```json
{
"key": "$t(key_boys, {\"count\": \"{{ boys_count }}\"}) and $t(key_girls, {\"count\": \"{{ girls_count }}\"})",
"key_boys_one": "{{ count }} boy",
"key_boys_other": "{{ count }} boys",
"key_girls_one": "{{ count }} girl",
"key_girls_other": "{{ count }} girls"
}
```

```rust
t!(i18n, key, boys_count = move || 0, girls_count = move || 0); // -> "0 boys and 0 girls"
t!(i18n, key, boys_count = move || 0, girls_count = move || 1); // -> "0 boys and 1 girl"
t!(i18n, key, boys_count = move || 1, girls_count = move || 0); // -> "1 boy and 0 girls"
t!(i18n, key, boys_count = move || 56, girls_count = move || 39); // -> "56 boys and 39 girls"
```
2 changes: 1 addition & 1 deletion docs/book/src/usage/04_t_macro.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Basically `<name .../>` expand to `move |children| view! { <name ...>{children}<
Ranges expect a variable `$` that implement `Fn() -> N + Clone + 'static` where `N` is the specified type of the range (default is `i32`).

```rust
t!(i18n, key_to_range, $ = count);
t!(i18n, key_to_range, count = count);
```

## Access subkeys
Expand Down
2 changes: 1 addition & 1 deletion examples/csr/counter_ranges/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fn Counter() -> impl IntoView {
let count = move || counter.get();

view! {
<p>{t!(i18n, click_count, $ = count)}</p>
<p>{t!(i18n, click_count, count = count)}</p>
<button on:click=inc>{t!(i18n, click_to_inc)}</button>
}
}
2 changes: 1 addition & 1 deletion examples/csr/yaml/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fn Counter() -> impl IntoView {
let count = move || counter.get();

view! {
<p>{t!(i18n, click_count, $ = count)}</p>
<p>{t!(i18n, click_count, count = count)}</p>
<button on:click=inc>{t!(i18n, click_to_inc)}</button>
}
}
30 changes: 28 additions & 2 deletions leptos_i18n/src/macro_helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl DisplayBuilder {
///
/// It has no uses outside of the internals of the `t!` macro.
#[doc(hidden)]
pub trait BuildStr: Sized {
pub trait BuildLit: Sized {
#[inline]
fn builder(self) -> Self {
self
Expand All @@ -50,9 +50,35 @@ pub trait BuildStr: Sized {
}
}

impl BuildStr for &'static str {
impl BuildLit for &'static str {
#[inline]
fn display_builder(self) -> DisplayBuilder {
DisplayBuilder(Cow::Borrowed(self))
}
}

impl BuildLit for bool {
#[inline]
fn display_builder(self) -> DisplayBuilder {
match self {
true => DisplayBuilder(Cow::Borrowed("true")),
false => DisplayBuilder(Cow::Borrowed("false")),
}
}
}

macro_rules! impl_build_lit_nums {
($t:ty) => {
impl BuildLit for $t {
fn display_builder(self) -> DisplayBuilder {
DisplayBuilder(Cow::Owned(ToString::to_string(&self)))
}
}
};
($t:ty, $($tt:tt)*) => {
impl_build_lit_nums!($t);
impl_build_lit_nums!($($tt)*);
}
}

impl_build_lit_nums!(u64, i64, f64);
6 changes: 4 additions & 2 deletions leptos_i18n_macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,22 @@ proc-macro = true

[dependencies]
serde = { version = "1", features = ["rc"] }
serde_json = { version = "1", optional = true }
serde_json = { version = "1" }
serde_yaml = { version = "0.9", optional = true }
proc-macro2 = "1"
quote = "1"
syn = "2.0"
toml = "0.8"
icu = "1.5"
fixed_decimal = { version = "0.5", features = ["ryu"] }

[features]
default = ["json_files"]
serde = []
debug_interpolations = []
nightly = []
suppress_key_warnings = []
json_files = ["serde_json"]
json_files = []
yaml_files = ["serde_yaml"]
interpolate_display = []
track_locale_files = []
Expand Down
9 changes: 6 additions & 3 deletions leptos_i18n_macro/src/load_locales/declare_locales.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashMap, fmt::Display, rc::Rc};

use crate::load_locales::ranges::{RangeParseBuffer, Ranges};
use crate::utils::key::{Key, KeyPath};
use crate::load_locales::ranges::{RangeParseBuffer, Ranges, UntypedRangesInner};
use crate::utils::key::{Key, KeyPath, CACHED_VAR_COUNT_KEY};

use super::{
cfg_file::ConfigFile,
Expand Down Expand Up @@ -203,7 +203,10 @@ fn parse_ranges(

let mut ranges = match parse_range_type(&content, &mut seed)? {
TypeOrRange::Type(range_type) => Ranges::from_type(range_type),
TypeOrRange::Range(range) => Ranges::I32(vec![range]),
TypeOrRange::Range(range) => Ranges {
inner: UntypedRangesInner::I32(vec![range]),
count_key: CACHED_VAR_COUNT_KEY.with(Clone::clone),
},
};

ranges.deserialize_inner(RangeParseBuffer(content), seed)?;
Expand Down
Loading