-
-
Notifications
You must be signed in to change notification settings - Fork 83
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
Replace rust-i18n with i18n-embed #1426
base: master
Are you sure you want to change the base?
Conversation
Thanks, I'll review this properly later today. One thing worth doing is re-introducing the I tested this locally and it seemed to work ok:
|
Indeed, that'll remove quite some noise. Though |
5a5a5ac
to
cec26eb
Compare
if self == &Self::Ttl { | ||
return "#".into(); | ||
} | ||
match self { | ||
Self::Ttl => Cow::Borrowed("#"), | ||
Self::Ttl => unreachable!(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoids introducing .into()
to all t!()
invocations at the cost of extra statements other than the match. I'll switch if you'd prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This got me thinking:
The ColumnType::name
method here returns Cow<'_, str>
only because that is what the i18n-rust
crate macro returned. Easy to fix, we just make the return type here (and a few other places) a String.
However, does that mean that i18n-embed
allocates an owned string every time? After digging through the code of the i18n_embed_fl::fl!
macro it seems it ultimate calls this:
https://github.com/kellpossible/cargo-i18n/blob/master/i18n-embed/src/fluent.rs#L133-L136
/// Get a localized message referenced by the `message_id`.
pub fn get(&self, message_id: &str) -> String {
self.get_args_fluent(message_id, None)
}
So the answer appears to be that it does allocate a new String for every translation, every time. Caveat that I only looked at it briefly so I may be missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i18n_embed::fluent::FluentLanguageLoader::get_args_fluent()
ultimately calls fluient::bundle::FluentBundle::format_pattern()
, which actually returns a Cow<_, str>
. It then turns it .into()
a String
. This is a bit frustrating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I added rust-i18n
I was a bit concerned about the extra CPU load as it did involved adding several string allocations per-frame (every 100ms by default). I did some (very unscientific) testing, I built in release mode and eyeballing the CPU graph, and it did have a small but noticeable impact. This would likely add even more so i'd want to repeat that eyeball test to see if the change is meaningful.
cec26eb
to
534d7de
Compare
@nc7s I've pushed some WIP commits to this PR (we can refine these and squash later).
No longer used so I removed it. I haven't reviewed the replacement logic yet.
See my comment above re: removing
We can use
As the translations are strictly a TUI concern I'd prefer this to live in the I'm find it confusing to have a I also removed the old locale directory in this commit. |
@nc7s on the fluent If not, can we name all file as |
Thanks for the detailed review and code.
I regret to admit it's because I simply followed its docs, which told me to put it in the root. :>
The former is for the i18n-embed crate, the latter for rust-embed. The former does not actually embed files itself, even with that word in the name.
As shown in rust-i18n docs:
I'm afraid we have to manually reimplement some of the set code to support an alternative layout, e.g. building
As shown above, |
Maybe this is a good use case to implement a few bench marks? PS. If we don't allow the language to change at runtime maybe we can use LazyCell or something like that to drop the runtime cost. I'm using it right now at work and it's pretty simple to use. You only pay for what you use and you only pay once and since it derefs into &T most code doesn't need to change. |
I used its thread safe version,
|
Oh sorry I guess I should have remember it needed to go across threads. Yes I think the lock cost is less than creating the string each time but we'd probably need to benchmark it to really know. And yes using OnceLock would also be fine. |
Arguably we could |
I think I missed something, why can't we use OnceLock? |
Uh that was intended as an idea for further optimization, |
@nc7s I pushed another wip commit, this is mostly about making the i18n impl hidden (to the extent possible) inside the locale module. I also added some tests. |
494f665
to
efb5f6e
Compare
As discussed in #1416. From there:
Probably, if strings are to be fairly simple and keep that way. Optimize with phf, even.
Anyway, the new code is here for comparison.
This is a very crude draft with quite some points up for debate: code style and placement, workaround for locale override with config value (there's a comment about this), structure of i18n resources, and MSRV (enabling
std::sync::LazyLock
, but actually not very relevant to i18n).Good news is this does not significantly affect compiled size (even down a few kilos actually).