baptistemontan / leptos_i18n Goto Github PK
View Code? Open in Web Editor NEWI18n library for leptos focused on ease of use and correctness
Home Page: https://baptistemontan.github.io/leptos_i18n/
License: MIT License
I18n library for leptos focused on ease of use and correctness
Home Page: https://baptistemontan.github.io/leptos_i18n/
License: MIT License
I get: error: Could not found "[package.metadata.leptos-i18n]" in cargo manifest (Cargo.toml)
, when calling leptos_i18n::load_locales!();
. I think there's a problem when trying to load the locales when inside a worspace? Or maybe I just missunderstood something and am placing my metadata in the wrong place?
Here is my folder structure:
frontend/
├─ api_sdk/
│ ├─ .../
├─ app/
│ ├─ src/
│ │ ├─ main.rs
│ │ ├─ ...
│ ├─ translations/
│ ├─ Cargo.toml
│ ├─ index.html
├─ Cargo.toml
The app/
folder is where is my leptos code code and I am trying to use leptos_i18n. Here is my setup:
# frontend/Cargo.toml
[workspace]
resolver = "2"
members = ["app", "api_sdk"]
default-members = ["app"]
# frontend/app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[package.metadata.leptos-i18n]
default = "en"
locales = ["en", "fr"]
locales-dir = "./translations"
[dependencies.leptos]
version = "0.5.0-rc1"
features = ["csr", "tracing"]
[dependencies.leptos_i18n]
version = "0.2.0-beta5"
And so I am trying to call leptos_i18n::load_locales!();
inside frontend/app/src/main.rs
and I get the error specified.
I'm getting a lot of the error:
error: Some keys are different beetween locale files, "bn.json" is missing key: "....."
Translations are nearly always a work in progress, and based on a source language. These should be warnings, not hard errors, and automatically fallback to the default.
Hi,
when testing the t_string! functionality with an enhanced examples/csr/src/app.rs
let inc = move |_| set_counter.update(|count| *count += 1);
let count = move || counter.get();
assert_eq!(t_string!(i18n, click_count, count ),"You clicked 0 times"); // New, to test t_string!
view! {
<p>{t!(i18n, click_count, count)}</p>
// <p>{t!(i18n, click_count, count = move || counter.get())}</p>
<button on:click=inc>{t!(i18n, click_to_inc)}</button>
I did experience the following issue (Note: feature "interpolate_display" has beenenabled in Cargo.toml) :
error[E0599]: no method named `var_count_string` found for struct `click_count_builder` in the current scope
--> src/app.rs:34:45
|
34 | assert_eq!(t_string!(i18n, click_count, count ),"You clicked 0 times");
| ^^^^^
|
::: src/main.rs:4:1
|
4 | leptos_i18n::load_locales!();
| ---------------------------- method `var_count_string` not found for this struct
|
help: there is a method `var_count` with a similar name
|
34 | assert_eq!(t_string!(i18n, click_count, var_count ),"You clicked 0 times");
| ~~~~~~~~~
For more information about this error, try `rustc --explain E0599`.
error: could not compile `csr` (bin "csr") due to 1 previous error
2024-05-14T12:09:57.122064Z ERROR ❌ error
Any suggestion what I'm doing wrong. Here my toolchain, I'm using most actual i18n version
active toolchain
----------------
stable-x86_64-unknown-linux-gnu (default)
rustc 1.78.0 (9b00956e5 2024-04-29)
Thank you in advance
BR
Currently the config file is i18n.json
, but it would be better to put the configurations in the Cargo manifest, like cargo-leptos
does it.
So instead of
i18n.json
:
{
"default": "en",
"locales": ["en", "fr"]
}
we would have
Cargo.toml
:
[package.metadata.i18n]
default = "en"
locales = ["en", "fr"]
This would be a breaking changes, but I would like to bring it to v0.2.0
It would be great if you could force the t!
macro to display the key (and interpolations) rather than actually translating. This is to help find any untranslated strings that may have crept in.
The hydrate
and ssr
features define competing implementations of fetch_locale
, so they cannot both be enabled at the same time.
For example, when enabling leptos_i18n
in a multi-crate workspace environment, the following error is produced:
> cargo check
Checking leptos_i18n v0.2.1
error[E0428]: the name `fetch_locale` is defined multiple times
-->$CARGO_DIR/registry/src/index.crates.io-6f17d22bba15001f/leptos_i18n-0.2.1/src/fetch_locale.rs:10:1
|
5 | pub fn fetch_locale<T: Locale>() -> T {
| ------------------------------------- previous definition of the value `fetch_locale` here
...
10 | pub fn fetch_locale<T: Locale>() -> T {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fetch_locale` redefined here
|
= note: `fetch_locale` must be defined only once in the value namespace of this module
For more information about this error, try `rustc --explain E0428`.
62ed4424dcc37ca75bbe6b236ce6427bf9cce296
leptos_i18n
as a dependency of the shared app
. Full app/Cargo.toml
:[package]
name = "app"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
leptos.workspace = true
leptos_meta.workspace = true
leptos_router.workspace = true
leptos_axum = { workspace = true, optional = true }
http.workspace = true
cfg-if.workspace = true
thiserror.workspace = true
leptos_i18n = { version = "0.2.1", default-features = false, features = [
"json_files",
] }
[features]
default = []
hydrate = [
"leptos/hydrate",
"leptos_meta/hydrate",
"leptos_router/hydrate",
"leptos_i18n/hydrate",
]
ssr = [
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"leptos_i18n/axum",
"dep:leptos_axum",
]
cargo check
against the workspaceThis appears to be due to the way Cargo resolves features of shared dependencies: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification. In this example above, both the frontend
and server
crates depend on app
, but with different sets of features enabled. Cargo then assumes these features are additive, attempts to build app
with both the hydrate
and axum
(ssr
) features enabled, producing the original error.
The hydrate
and ssr
features can be enabled at the same time, and compile.
The following is a "functioning" version of src/fetch_locale.rs
, but is relatively ugly and may have unintended consequences:
use crate::Locale;
#[cfg(all(feature = "ssr", not(feature = "hydrate")))]
#[inline]
pub fn fetch_locale<T: Locale>() -> T {
crate::server::fetch_locale_server_side::<T>()
}
#[cfg(all(feature = "hydrate", not(feature = "ssr")))]
pub fn fetch_locale<T: Locale>() -> T {
leptos::document()
.document_element()
.and_then(|el| el.get_attribute("lang"))
.and_then(|lang| T::from_str(&lang))
.unwrap_or_default()
}
#[cfg(any(
all(feature = "hydrate", feature = "ssr"),
not(any(
feature = "ssr",
feature = "hydrate",
all(feature = "csr", feature = "cookie")
))
))]
#[inline]
pub fn fetch_locale<T: Locale>() -> T {
Default::default()
}
#[cfg(all(feature = "csr", feature = "cookie"))]
pub fn fetch_locale<T: Locale>() -> T {
fn inner<T: Locale>() -> Option<T> {
let document = super::get_html_document()?;
let cookies = document.cookie().ok()?;
cookies.split(';').find_map(|cookie| {
let (key, value) = cookie.split_once('=')?;
if key.trim() == super::COOKIE_PREFERED_LANG {
T::from_str(value)
} else {
None
}
})
}
inner().unwrap_or_default()
}
First implementation of plurals took impl Fn() -> i64 + ...
for the count, then evolved to impl Fn() -> Into<i64>
, so when being able to select the type for count
came out it moved again to impl Fn() -> Into<N>
with N
the type of count
for the plural.
I'm not a big fan of implicit conversion, and this can lead to bad type inference, for exemple when you declare your plural to u32
but don't constrain your signal type and it default to i32
, it will create an error stating that i32
don't implement Into<u32>
.
It would be better to take impl Fn() -> N
directly.
It would be nice to have a helper function to set the locale based on the browser language if it's not set already.
here is an example:
<Trans
i18nKey="myKey" // optional -> fallbacks to defaults if not provided
defaults="hello <italic>beautiful</italic> <bold>{{what}}</bold>" // optional defaultValue
values={{ what: 'world'}}
components={{ italic: <i />, bold: <strong /> }}
/>
Source: https://react.i18next.com/latest/trans-component
They extract the keys and the defaults value then writes to for example en.json, no need to edit that huge file after a while.
They also supply a function t
where you can invoke for example t('my-key', { defaultValue: 'hello {{name}}', values: 'me' }
, this is necessary since you might want to get a translation string outside a view component.
The whole point is to automatically extract translations with minimal effort avoiding the need to open, read and edit huge translation files.
Btw, there is prior art in the form of i18next, fluent-rs and now icu4x: projectfluent/fluent-rs#329
It probably makes sense to integrate icu4x into leptos_i18n
or take inspiration from it, regarding how it handles the intricacies of languages.
I am using actix and translations work fine for locales at first glance (in this example I am using EN & FR). However, when I update the values in the json files, the site does not update from the previous translations, even after rebuilding the project (or doing CTRL+Shift+R). The only way the translations update is if I change the key name in the json and within the rust implementation itself.
I have no idea why or how this is happening.
Any help would be appreciated.
I can provide any code that is needed.
This happens anywhere I try to run leptos_i18n::load_locales!();
version 0.1.3
For interpolations a builder is created, the current implementation works fine but there is two problems:
leptos::IntoView
, so you don't know what key is missing, you don't even know that a key is missing.First problem can either be done on expanded code with use of #[deprecated(note = "duplicated field foo"]
when the field is already set, or by allowing to set the value only when T = EmptyInterpolateValue
, or it can be done within the t!
macro. First solution would be the better as a custom message can be created.
Second problem is going to be hard to implement, I'm not sure how to properly do it, the only way I see is to use a build
method at the end that just serves as a check, but would have 2^N implementations, where 1 implementation would be with all values set, and the other with one or multiple values not set and use the deprecated attribute to give warning on what prop is missing, and panicking in the body with the same message (would return ()
to avoid compilation error where the returned value does not impl leptos::IntoView
).
Those 2 changes would massively increase the size of outputed code, and even if most of it would be thrown away after compilation it would still impact compiling speed, maybe gate it behind a feature? or only debug mode? I don't known the impact on compiling speed when config gating things, I'm assuming the compiler can just throw it right away.
Error:
error[E0061]: this function takes 2 arguments but 1 argument was supplied
src/lib.rs:1:1
leptos_i18n::load_locales!();
argument of type `leptos::Scope` is missing
note: method defined here
/home/xxx/.cargo/registry/src/index.crates.io-6f17d22bba15001f/leptos_dom-0.4.10/src/lib.rs:231:8231
fn collect_view(self, cx: Scope) -> View;
= note: this error originates in the macro `leptos_i18n::load_locales` (in Nightly builds, run with -Z macro-backtrace for more info)
help: provide the argument1 |
leptos_i18n::load_locales!()(leptos_i18n::load_locales!(), /* leptos::Scope */);
For some obscure or archaic reason (not trying to judge anyone here) some people still use .yml
as file extension for their YAML files.
I think the "standard" or what is recommended is to use .yaml
. but if you ever want to accomodate everyone, support for .yml should maybe be added.
Is it possible to do lazy loading of locale data?
Locale data can be huge depending on the number of keys and languages, and usually only 1 language's values are needed at runtime.
So a lot can be saved by lazy loading (which is why e.g. i18next supports it).
This change just happened: leptos-rs/leptos#918
Been struggling to get this to compile in my project. I'm not sure if I'm just using this feature wrong, or if there's a bug in the implementation.
It seems to be caused when there is only one locale available.
Here's a minimal reproduction:
Cargo.toml
[dependencies]
leptos = { version = "0.5.0", features = ["csr"] }
leptos_meta = { version = "0.5.0", features = ["csr"] }
leptos_i18n = { path = "../../leptos_i18n", default-features = false, features = [
"debug_interpolations",
"csr",
"cookie",
"yaml_files",
] }
serde = { version = "1", features = ["derive"] }
console_error_panic_hook = { version = "0.1" }
wasm-bindgen = { version = "=0.2.87" }
[package.metadata.leptos-i18n]
default = "en"
locales = ["en"]
src/app.rs
#[component]
pub fn App() -> impl IntoView {
leptos_meta::provide_meta_context();
let i18n = provide_i18n_context();
view! {
<p>{t!(i18n, result_summary, count = || 5, query_str = || "test")}</p>
}
}
locales/en.yaml
---
result_summary:
- "u64"
- ["No results for {{ query_str }}:", 0]
- ["1 result for {{ query_str }}:", 1]
- ["{{ count }} results for {{ query_str }}:"]
❯ trunk build
2023-10-27T22:24:07.047974Z INFO 📦 starting build
2023-10-27T22:24:07.048390Z INFO spawning asset pipelines
2023-10-27T22:24:07.160652Z INFO building yaml
Compiling yaml v0.1.0 (/Users/lpetherbridge/pdev/leptos_i18n/examples/yaml)
error[E0403]: the name `__var_count` is already used for a generic parameter in this item's generic parameters
--> src/main.rs:4:1
|
4 | leptos_i18n::load_locales!();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| already used
| first use of `__var_count`
|
= note: this error originates in the macro `leptos_i18n::load_locales` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0416]: identifier `var_count` is bound more than once in the same pattern
--> src/main.rs:4:1
|
4 | leptos_i18n::load_locales!();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ used in a pattern more than once
error[E0124]: field `var_count` is already declared
--> src/main.rs:4:1
|
4 | leptos_i18n::load_locales!();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| field already declared
| `var_count` first declared here
|
= note: this error originates in the macro `leptos_i18n::load_locales` (in Nightly builds, run with -Z macro-backtrace for more info)
Some errors have detailed explanations: E0124, E0403, E0416.
For more information about an error, try `rustc --explain E0124`.
error: could not compile `yaml` (bin "yaml") due to 3 previous errors
Is there a way to get cargo leptos watch
to recompile the lib (load_locales!
) when files in the locales directory change?
I tried watch-additional-files = ["locales"]
in Cargo.toml
but cargo doesn't think that anything in the lib has changed so just links.
Someone suggested support for subkeys without namespacing, that would mean supporting this structure:
{
"primary_key": {
"sub_key_1": "this is a subkey",
"sub_key_2": "this is another subkey",
}
}
with
t!(i18n, primary_key.sub_key_1)
This won't be that much difficult to implement but would break the current structure for plurals
My proposal would be to migrate plurals from a map to a sequence, which would make also more sense as plurals are order dependents and rely on serde_json
being a "lazy" parser and keeping the order.
So plurals would go from
{
"click_count": {
"0": "You have not clicked yet",
"1": "You clicked once",
"_": "You clicked {{ count }} times"
}
}
to
{
"click_count": [
["0", "You have not clicked yet"],
["1", "You clicked once"],
["_", "You clicked {{ count }} times"]
]
}
This would be one way to do it, any suggestions are welcome.
As this is a breaking change it would come with v0.2.0
(if ready by then).
Curently t!(i18n, ..)
is basically move || td!(i18n.get_locale(), ..)
, the reason td!
is not wrapped in a closure is because it was a way to access the underlying &str
for a given locale.
Now that td_string!
and td_display!
exists they offer both access to the &str
with more meaning in the name of the macro, most users that want static access to the &str
would use td_string!
or td_display!
, so this leave td!
in a weird spot that makes it being almost exclusively used with move || td!(locale, ..)
.
td!
should behave the same way as t!
, this would be a breaking change though.
For now the locale is just set as a cookie (if feature enabled) or retrieved from the client request, but it would be great to also insert the locale in the URL:
domain/path
domain/locale/path
I will if this is possible and how to implement that.
I have not tested if this crate works with csr
, but the only backend/hydratation related operations are retrieving what locale to use.
If neither hydrate
or actix
/axum
features are enabled, it's always the default locale that is selected.
So csr
should work but the cookie
feature is'nt used by csr
, adding a csr
feature that check the cookies for the the locale would be all that need to be done (I think ?).
Hi,
thx for the crate, it works pretty fine.
During the execution (I use V0.3.0-rc) I get in the console the following warning from Leptos
At /home/.cargo/registry/src/index.crates.io-6f17d22bba15001f/leptos_i18n-0.3.0-rc/src/context.rs:18:16, you access a signal or memo (defined at /home/.cargo/registry/src/index.crates.io-6f17d22bba15001f/leptos_i18n-0.3.0-rc/src/context.rs:67:18) outside a reactive tracking context. This might mean your app is not responding to changes in signal values in the way you expect.
Here’s how to fix it:
1. If this is inside a `view!` macro, make sure you are passing a function, not a value.
❌ NO <p>{x.get() * 2}</p>
✅ YES <p>{move || x.get() * 2}</p>
2. If it’s in the body of a component, try wrapping this access in a closure:
❌ NO let y = x.get() * 2
✅ YES let y = move || x.get() * 2.
3. If you’re *trying* to access the value without tracking, use `.get_untracked()` or `.with_untracked()` instead.
You maybe find some time to take a look at it, I tried with exchanging
#[inline]
pub fn get_locale(self) -> T {
self.0.get()
}
with
#[inline]
pub fn get_locale(self) -> T {
(move || self.0.get())()
}
Which was not succesful.
this one fails:
<article><h2>Effortless Process: From Virtual Introduction to Contract with Our Talent Partner</h2><p>Our talent partner simplifies your recruitment journey for maximum efficiency, whether you're seeking candidates for hybrid, remote, or onsite positions. Our adaptable approach ensures you're informed every step of the way, encompassing permanent positions, headhunting, and contract limited time roles. Here's a detailed look at our process, including virtual meetings, contract signing, fee details, and the one-time replacement guarantee:<h3>1. Schedule Virtual Meeting Invite</h3><p>To kickstart our collaboration, schedule a virtual meeting invite. During this meeting, we'll discuss your recruitment needs, spanning hybrid, remote, or onsite positions, including permanent roles, headhunting, and contract limited time opportunities. This initial step sets the stage for our partnership.<h3>2. Understanding Our Versatile Approach</h3><p>Our recruitment model encompasses various options to suit your hiring requirements:<ul><li><strong>Contingency Hiring:</strong> This model is based on contingency, meaning we only generate revenue upon successfully placing an employee with you. Until that point, we don't charge any fees. The fees charged are influenced by various factors, including the complexity of the role, candidate's first-year salary, and adherence to relevant regulations. On average, typical recruitment fees range from 15% to 25% of the candidate's first-year salary.<li><strong>Retainer Model:</strong> Alternatively, we accept retainer model agreements. With this option, companies pay an upfront fee to create an exclusive contract for the recruitment process. This approach allows for greater dedication and personalized attention to your needs. Part of the fee is paid upfront, demonstrating commitment and enabling us to initiate the process promptly. The remaining payment is made at varying points of the process. Despite the variation, the total percentage paid remains within the range of 15% to 25% of the candidate's first-year salary.<li><strong>Contract Placement:</strong> For contract placements, we provide an internal payroll solution. We handle the employee's payroll and charge the client based on a comprehensive fee structure. This fee includes the employee's wages along with an additional amount to cover expenses. Typically, this fee equates to around 1.5 times the employee's wages. For instance, if an employee earns $20 per hour, the agency would charge the client $20 times 1.5, resulting in a rate of $30 per hour. Contract placements often pave the way for potential temp-to-Hire roles.</ul><p><strong>Guarantee and Refund Policy:</strong> Our one-time replacement guarantee ensures your satisfaction. If a candidate doesn't meet your expectations within the agreed period, we offer a replacement candidate. The guarantee covers a single replacement, and any additional terms will be outlined in the agreement. This policy is open to negotiation, ensuring that your unique needs are met.<p>Following our detailed discussion, we'll tailor agreements that encapsulate the fee structure, guarantee terms, and the chosen recruitment model, including our negotiable guarantee and refund policy. Rest assured, our process ensures compliance with regulations and dedication to your unique recruitment needs.<h3>3. Diligent Search</h3><p>Our search commences, leveraging our extensive network and expertise to identify ideal candidates.<h3>4. Candidate-Employer Match and Multiple Choices</h3><p>We meticulously evaluate candidates to ensure a seamless alignment with your criteria and company culture. In situations where you present multiple candidates, our evaluation extends to all choices.<h3>5. Candidate Interview Process and Contract Preparation</h3><p>As candidates are chosen continuously, this step continues until you find a candidate you like. We facilitate the interview scheduling process with your team. Our goal is to ensure seamless communication and coordination between the candidates and your employees, culminating in a successful mutual agreement. Following the successful interviews, comprehensive contracts are prepared for both parties involved.<h3>6. Collaboration for Agreement</h3><p>Contracts are shared for review, emphasizing open lines of communication. Once both the candidate and your team agree, the contracts are officially executed, solidifying the agreement.<h3>7. Payment After Placement - Invoice Sent Upon Start</h3><p>Following the candidate's start date, an invoice will be promptly sent to your organization for payment. This invoice corresponds to the agreed-upon fee structure as outlined in the agreement. The payment is due within 14 days from the candidate's start date.<h3>9. Continuing Partnership</h3><p>Our dedication endures past placement. We ensure a smooth transition and nurture lasting relationships.<p>Our process ensures you're informed, from scheduling a virtual meeting invite to candidate placement across various positions. The adaptable fee structure, aligned with your chosen recruitment model, intrinsically tied to the complexity of the role and the candidate's first-year salary, reflects our dedication to streamlining your hiring process.<p>Experience recruitment ease with our talent partner. With the added assurance of our one-time replacement guarantee and compliance with relevant regulations, we're steadfastly with you at every step.</article>
error[E0599]: no method named `comp_h3` found for struct `__page_home_section_process_content_builder` in the current scope
--> src/components/home/section_process.rs:21:10
|
16 | {i18n::t!(
| ________-
17 | | i18n,
18 | | page_home_section_process_content,
19 | | <article> = |cx, children| view!{ cx, <article class=format!("col-xs-12 {}", ClassName::CONTENT)>{chil...
20 | | <h2> = |cx, children| view!{ cx, <h2>{children(cx)}</h2> },
21 | | <h3> = |cx, children| view!{ cx, <h3>{children(cx)}</h3> },
| | -^^ help: there is a method with a similar name: `comp_h2
It's minified and verified valid HTML.
The rust code used:
{i18n::t!(
i18n,
page_home_section_process_content,
<article> = |cx, children| view!{ cx, <article class=format!("col-xs-12 {}", ClassName::CONTENT)>{children(cx)}</article> },
<h2> = |cx, children| view!{ cx, <h2>{children(cx)}</h2> },
<h3> = |cx, children| view!{ cx, <h3>{children(cx)}</h3> },
<p> = |cx, children| view!{ cx, <p>{children(cx)}</p> },
<strong> = |cx, children| view!{ cx, <strong>{children(cx)}</strong> },
<ul> = |cx, children| view!{ cx, <ul>{children(cx)}</ul> },
<li> = |cx, children| view!{ cx, <li>{children(cx)}</li> }
)}
Hi,
Just leaving this here, I know it is most likely a big endeavour. But would you be open to support yaml file for the translations? I am not asking to replace json, as some people might prefer that, but maybe a way to support both a bit like rust-i18n does?
It's far from important or anything, json works just fine, just a personal preference on my end. :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.