-
Notifications
You must be signed in to change notification settings - Fork 143
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
Add i18n by using rust i18n #807
Add i18n by using rust i18n #807
Conversation
Hi, I hope the review is not too late, thanks again for working on it! :)
Yeah, this is indeed a problem, but I think it is ok to use it in our first implementation, in the future, we can probably provide a custom macro to make it easier to use.
I think this PR is pretty good, we are on the right track!
Yeah, using English as the key and the fallback option makes sense to me. |
BTW, you can rebase your branch to fix the test coverage CI issue. |
No worries! We all have a life :D
Sounds like a plan! I'd be happy to design sth with you for good dx
I'll work more on it this/next week (depending on your timezone)
👍 Seemed the most appropriate for DX |
aa66d12
to
5f03eb2
Compare
@SteveLauC Still working on this. One thing I already mentioned but which I find troubling still, is that for complex statements regarding debug formatting the code gets quite ugly: debug!("Detected {:?} as {:?}", &path, &binary_name); After: debug!("{} {:?} {} {:?}", t!("Detected"), &path, t!("as"), &binary_name); I haven't found a good way to deal with this as the crate doesn't allow to pass in specific formatting hints for a variable. Is there a way to call debug!("{}", t!("Detected {:?} as {:?}", path=&path.debug_fmt(), binary_name=&binary_name.debug_fmt()); Hint: The |
To use parameters, looks like we have to define the whole sentence as a localized text: _version: 2
"binary-detection":
en: "Detected %{path} as %{binary}" then in Rust code, we would: debug!("{}", t!("binary-detection", path = format!("{:?}", &path), binary = format!("{:?}", &binary_name)) Honestly, this would make the code harder to read since we don't know what the actual string would look like unless we take a look at the localized file. And for texts like "Detected {} as {}", I think we should use the whole sentence as a localized text rather than defining texts for every word since blindly translating those words probably won't make a natural sentence. For example, an ideal translation for "Detected ~/.cargo/bin/cargo as cargo" in simplified Chinese (zh-cn) would be: "检测到 cargo: ~/.cargo/bin/cargo" Which literally means "I have found cargo: it is located in ~/.cargo/bin/cargo" in English. However, blindly translating those word looks like this: "检测到 ~/.cargo/bin/cargo 作为 cargo" which feels quite weird... Given the above problem, I think we should stop using words as localized texts and take whole sentences as the smallest units, your thoughts on this? For the readability issue, I don't have a good solution for this considering that the // Detected {path} as {binary}
debug!("{}", t!("binary-detection", path = format!("{:?}", &path), binary = format!("{:?}", &binary_name)) |
5f03eb2
to
a367e99
Compare
@SteveLauC I'm still working on this. I'm quite unsatisfied with the DX so far 🤔 I'll think about your questions and will get back to you with another suggestion on how we can do things properly and without much friction for contributors 👍 |
Absolutely agree!
I'd suggest we use the following for the example: debug!("{}", t!("Detected {path} as {binary}", path = format!("{:?}", &path), binary = format!("{:?}", &binary_name)) Reasoning behind this being that I think comments are bound to become obsolete and outdated bc there is no check if they are actually accurate and up-to-date. What about using the whole string as we have before (bc of readability and dx) and just put down the placeholder names. This does not guarantee that the placeholder won't eventually become outdated. But what it does help with is readability. It also eliminates the need for going back and forth to the translation dictionary bc it's right in the translation key. This also keeps in line with our approach for simple keys. That said, I'm still not sold on the readability issue regarding the I'll start shifting my initial implementation to this if you don't have any concerns. Minor thing: We could also think about using Only thing that might be confusing is for new contributors who think that our pseudo placeholder is actually a placeholder with meaning behind it. Which it isn't 😄 Maybe we could add a part to Sorry for the wall of text! Hope you have a great week and just let me know what you think of these changes. I'll try to find the time to get this thing to a close |
Yeah, this is much better in readability!
I just tried to write a custom custom_debug!("Detected {:?} as {:?}", &path, &binary_name) into debug!("{}", t!("Detected {path} as {binary_name}", path = format!("{:?}", &path), binary_name = format!("{:?}", &binary_name))) But I am not am expert in macros and ChatGPT says that this is not easy to implement since it needs to involve strings operation, but Rust macro operates in tokens.
This looks not bad IMO, since this is a typical pattern that is commonly supported by Rust std macros.
Yeah, we should document the approach we take in CONTRIBUTING.md no matter what it is.
No need to be sorry at all! :) |
a367e99
to
ac8decf
Compare
Updated the existing commit. Still a few todos left, but I think that should be the bulk of it 👍 Would love some assistance on double-checking the translation placeholders etc. Slowly but surely my eyes are getting worn out looking at yaml 😄 Now on to the todo items and rebasing from main! |
Open Todo-items:
@SteveLauC If I'm missing some steps here feel free to add some 👍 I'll add these todo items to the main comment so it shows up on the overview of this PR once you give me a thumbs-up for it |
I'll go over the diff later this week to try and catch easy mistakes I made. I don't mind if you do a proofread in the meantime, just be aware that there might be hasty mistakes not yet fixed |
Hi, thanks for reminding me of this! I think something like the following would be good:
Topgrade does not have a changelog file, though we record breaking changes in
We use squash merge, so it will always be 1 commit in the main branch, no need to worry about these things:)
Maybe I should document the merge style in |
Looks like this is something impossible?
Yeah, we can leave a TODO here.
Looks like we need to manually implement the #[derive(Debug)]
pub struct StepFailed;
impl std::fmt::Display for StepFailed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "A step failed")
}
}
impl std::error::Error for StepFailed { }
Looks like we need a toml parser that can be used to replace comments?
Using the first letter may only work for some languages, I think we need to have a custom transaction for every language. For English, the original string is good enough. For Chinese, I think it should probably be something like:
As you can see, for For the confirm keys, we may need to define some variables for them as well:
Topgrade also has a prompt for
Emmm, this is indeed something hard...
This is something I am not willing to translate, if the underlying step supports locale, and Topgrade needs to capture its output, and does parsing on it, we should let the output in English or we will have to parse for every language, which is quite tedious, also see: https://github.com/topgrade-rs/topgrade/blob/main/CONTRIBUTING.md#some-tips
We can use pub static REQUIRE_SUDO: Lazy<String> = Lazy::new(|| {
t!("Require sudo or counterpart but not found, skip")
}) |
In theory we could implement the thing ourselves just like we did with the error Impls. I don't think that's a very good idea though. I'm content with not translating that for the moment. I'm sure the benefit of a 99% translated software (or worst case 90%) is still very much palpable than not having any translation at all. I just now saw that this code block is in a testing module anyway. Absolutely no need to translate this imo as the agreed-upon language in software development is English anyway. No problem here 😄
I'd say we just have multiple toml files. Seems way easier imo 🤔 You can check my most recent push. I've added the config file translation(s)
We could either do that or just not translate those keys at all 🤔 It shouldn't keep people from using the software imo as it would be clear on what key to press. But we could also do that with the custom key thing. I don't mind either. Just thinking in terms of maintainability
Imo not sth we could feasibly accomplish. We would have to translate commits and the release notes for every release. I don't think that's quite possible or that the benefit of doing that warrants the costs of maintaining that. You'd need maintainer or trusted people to check that as you (probably 😄) don't speak all the translated languages.
Absolutely agree! 👌
We could also not make that a static string 🤔 Is there too much overhead in doing that? I don't think it would be. I think the cost of adding sth like the lazy macro is higher than just plopping a |
Another note: I'd like to add issues to open todo-items in hopes of better tracking them. Ie. the following code:
would get a link to an issue in this repository for i18n for the command line interface |
And one last thing regarding
Let me know what you think! |
Yeah, I agree.
Emmm, putting the whole configuration file as a translation unit does not seem good. When we have multi-language support, this file will be duplicated many time, and there is no fallback for specific entry, you need to either translate all the entries or don't do it at all. Though I don't have a good approach, I think we can leave it not translated if there is no good way to do it.
Yeah, Having those confirm keys not translated looks good to me, and it can make maintenance easier.
Yeah, let's ignore it.
:)
It looks good to me to directly use
Yeah, we can have a tracking issue for this.
Do you mean things like this? - println!("{} {}", style("Up-to-date").green().bold(), repo.as_ref().display());
+ // TODO: Properly use i18n in this one. How to deal with the colors?
+ println!("{} {}", style(t!("Up-to-date")).green().bold(), repo.as_ref().display());
What is the problem with the strategy presented in this PR? i.e., the above diff :D |
You are right I think. The maintenance overhead alone is too big when there is a new config parameter the config has to be updated by the language maintainers before a user gets a new and correct version of the config 🤔 ...
t!("config.include_comment") +
"[include]"
... I think that should work easily enough, but it's a bit tedious to implement :D
Alright! Will implement this then 👍 EDIT: Done ✅
Sth more like this: // TODO: Properly use i18n in this one. How to deal with the colors?
println!(
"\n{} {}\n",
style(t!("Only")).green().bold(),
t!("updated repositories will be shown...")
); Here we are back to translating singular words and not the whole sentence. I don't think there's a clever way to only highlight a portion of the translated text 🤔 |
732f29e
to
42e91d0
Compare
Example for the config translation stuff: use rust_i18n::t;
pub fn construct_default_config() -> String {
t!("include").to_string()
+ r#"
[include]
# paths = ["/etc/topgrade.toml"]
[misc]
"#
+ t!("pre_sudo").as_ref()
+ "# pre_sudo = false"
+ t!("sudo_command").as_ref()
+ r#"# sudo_command = "sudo""#
} and the locales/config.yaml: "include":
en: |
# Include any additional configuration file(s)
# [include] sections are processed in the order you write them
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
"pre_sudo":
en: |
# Run `sudo -v` to cache credentials at the start of the run
# This avoids a blocking password prompt in the middle of an unattended run
# (default: false)
"sudo_command":
en: "# Sudo command to be used" @SteveLauC What do you think of this? Should I continue with this or do you see a different approach? 🤔 |
This is better than translating the whole file, but it is still kinda, I wonder can we skip it for now and leave a TODO in the tracking issue?
Ahh, thanks for the explanation! It feels weird to add the effect to the whole sentence, see the demo picture below. I think we have to split the sentence, translate them separately, then do it in the following way: println!(
"\n{} {}\n",
style(t!("Only")).green().bold(),
t!("updated repositories will be shown...")
); |
Hi, I didn't review this today because I was building a locale file checker, it is right there, I have introduced 2 rules by now:
And it works pretty well, see the errors it catched: $ wget https://raw.githubusercontent.com/topgrade-rs/topgrade/fe9702330ff9c89c13e7c00e193baaf1e050fa48/locales/app.yml
$ ./target/debug/topgrade_i18n_locale_file_checker app.yml
Errors Found:
KeyEnMatches
Path expanded to
brl list: {output_stdout} {output_stderr}
Bedrock distribution {distribution}
Unknown distribution {distribution}
Checking for gnome extensions: {output}
Failed to canonicalize {profile_dir}
Found Nix profile {profile_dir}
WSL distributions: {wsl_distributions}
Startup link: {link} I am thinking about introducing another rule to catch unused keys, tried a little with ast-grep, but haven't figured out how to implement it. Will review this PR tomorrow:) |
This should be done by the locale file checker, I will implement a rule for checking duplicate keys today. Update: Looks like I don't need to implement this, Yaml, by specification, does not allow duplicate keys in a mapping, and executing the checker against a yaml with duplicate key will panic (the panic comes from serde_yaml): $ cat app.yaml
hello:
en: 1
hello:
en: 1
$ ./target/debug/topgrade_i18n_locale_file_checker app.yaml
thread 'main' panicked at src/main.rs:25:45:
called `Result::unwrap()` on an `Err` value: Error("duplicate entry with key \"hello\"", line: 1, column: 1) |
Reviewing, but still have a bunch of files to check😄, will continue this evening. |
src/command.rs
Outdated
debug!( | ||
"{}", | ||
t!( | ||
"Command failed: {error_and_context}", |
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 key Command failed: {error_and_context}
and the one Command failed: {command}
in line 179/184 are basically same except for the different parameter name, should we consider them as duplicate keys if I implement duplicate key check in the checker?
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.
That's a good question 🤔 On one side they are syntactically the same on the other they differ semantically... I think it would be easier to consider the same keys but with different placeholders as duplicates
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.
I think it would be easier to consider the same keys but with different placeholders as duplicates
Yeah, this is easier to implement since they are literally different 😁
I have some thoughts on what should be translated and what should not be, generally, things that are primarily for developers should not be translated:
And whether a step separator ( Damn, this is sutble, I am not sure if you could feel it... I realized if we revert the code that should not be translated, that will be a big refactor, I am sorry I didn't realized this at the early stage, it is unfair to let you handle them all, do you mind letting me do some work here? |
@SteveLauC |
Correct! That's one advantage with our approach 👍 |
Enjoy your vacation! Let me handle them, I won't squash commits/force push, so that you can also review them😄 |
fe97023
to
ce4106b
Compare
Hey! @SteveLauC I saw that you were able to make some changes. Give me a shout once it's ready to review. I'd gladly take a look 😊 Excited to bring topgrade to a broader audience! 🚀 |
Yeah, I will 😃 |
bfd3310
to
da6f9cb
Compare
Hi @Mastermindaxe, I finished the revert job, you can take a look now. 😪 I am too busy with my work, sorry for the delay. Once this PR is merged, I plan to release Topgrade v16.0:) BTW, the CI is not running, I am not quite sure about the reason, I guess it is because we have conflicts in this PR, no need to worry about it. |
Hey @SteveLauC! No worries! 😊 Life can be buddy at times! I'll check it out once I have some time this week. Looking forward to the release! |
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.
LGTM! We will probably see the first "real" bugs once there are translations. Hopefully nothing should break with these changes.
Thanks for the review, then I will:
🚀 |
25fcb19
to
cafddc0
Compare
cafddc0
to
2c7e8e2
Compare
Merged, thanks for the great work! ❤️ |
You're welcome! Looking forward to helping out more 😊 |
Standards checklist:
CONTRIBUTING.md
cargo build
)cargo fmt
)cargo clippy
)cargo test
)main
app.yml
to remove duplicated or unneeded keysFixes #2