Skip to content

Add support for translations without gettext #3715

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

Open
Tracked by #1494
ogoffart opened this issue Oct 20, 2023 · 8 comments
Open
Tracked by #1494

Add support for translations without gettext #3715

ogoffart opened this issue Oct 20, 2023 · 8 comments
Labels
a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) api Changes or additions to APIs

Comments

@ogoffart
Copy link
Member

We need to support user changing translations through the interface, as well as translation on platform that do not have gettext (MCU)

The idea would be somehow to have API to provide a translator.

Perhaps we can re-use an interface similar to https://docs.rs/tr/latest/tr/trait.Translator.html
and have a global setter to set it. That setter would mark all translated string as dirty.

( This should also help for #3307 )

@qarmin
Copy link

qarmin commented Nov 6, 2023

I know that this has been discussed before and that the idea was already discarded, but isn't it worthwhile, however, to give users the option of using fluent-rs instead of gettext, since it works very well under any system, including windows.

This way, the external dependence on the code in C can be removed and it will make cross-compilation easier.

@ogoffart
Copy link
Member Author

ogoffart commented Nov 7, 2023

Regarding fluent, this can be done by using a global.
The old documentation should five hint https://slint.dev/releases/1.0.2/docs/slint/src/recipes/recipes#translations
So you would have in your code something like text: Fluent.translate("my-text", ["FooBar"])
Other possibilities are also possible: #33 (comment)

@CarbonPool
Copy link

Any progress on changing the language dynamically?

@ogoffart ogoffart added enhancement New feature or request a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) api Changes or additions to APIs labels Jan 15, 2024
@tronical tronical added this to the 1.7 Release milestone Apr 16, 2024
@tronical tronical changed the title Dynamically change translations and translations without gettext Add support for translations without gettext Jun 10, 2024
@tronical
Copy link
Member

Changing translations dynamically (when using gettext) is implemented in the master branch. This ticket was split and slightly confusing, so I've refocused it on the gettext removal.

@tronical tronical removed this from the 1.7 Release milestone Jun 10, 2024
@ogoffart
Copy link
Member Author

ogoffart commented Sep 21, 2024

I'm proposing to have the following:

  • slint_build::CompilerConfiguration::bundle_translations(path, ...) in Rust
  • --bundle-translations <path> in C++ (with corresponding API in our cmake interface)

The the compiler would look at the content of and look for file with a given pattern.
Either:

  • <language>.po (eg: de.po/fr.po)
  • <language>/LC_MESSAGES/<domain_name>.po (eg de/LC_MESSAGES/gallery.po) that's what we have in our example right now.
  • <language>/LC_MESSAGES/<domain_name>.mo we also have that. This is a binary format.
  • <language>/<domain_name>.{mo,po} drop the LC_MESSAGES ?

We can use crates like gettext (to open .mo) or polib (to open .po)
Then at compile time, we translate @tr("Hello {}", name) to something like (pseudo-code)

   private_api::format_string(match private_api::language() {
       1 => "Bonjour {}",
       2 => "Hallo {}",
       _ => "Hello {}",
   },&[name]);

Or maybe

   private_api::translation(&["Hello {}", "Bonjour {}", "Hallo {}"], &[name])

We can even have warning if a string or translated string don't have the right format, (or doesn't exist ?)

Now we still need a way to set or change the language.
I'm thinking there could be slint::set_language(???) but how does it map the language string to a number? Or we could generate a slint_set_language(&str) function in the generated code. Or, we could have a slint function set-language("fr") in .slint

This solution also doesn't help to translate strings in the native C++/Rust code.

This is mostly useful for MCU or platform without file system.
I still think we should have a slint::set_translator(Box<dyn Translator>) function for desktop.

@tronical
Copy link
Member

I agree that this is the way to go, roughly.

In terms of language selection: I think the build system API should explicitly say which languages/translations to pick. This permits creating multi-language builds as well as region-specific builds. Later the list of support translations could also go into the project file, to enable access in the live-preview.

Generally, I think it makes sense to see translations like resources/assets and bundle them, also on targets with operating systems, as opt-in.

@ubruhin
Copy link
Contributor

ubruhin commented Mar 6, 2025

I'm currently struggling with setting up translations for the new LibrePCB UI and I think a generic translators API as suggested in the initial post would be very helpful.

My situation:

  • A large C++ backend with many strings, using Qt's translation toolkit and a whole workflow set up (Transifex, deployment, dynamic loading at runtime).
  • The new Slint UI contains a lot of translatable strings too, using @tr().

For the C++ backend I want to keep the current system - it would be way too much effort to change it, and actually Qt provides a mature translations toolkit which works very well and flawlessly across all platforms.

For the Slint UI there are two solutions provided:

  • Gettext: I gave it a try, but honestly this thing feels everything else than flawlessly. I could not even manage to compile it on Windows within reasonable time such that it can be used from my CMake project. The build system looks like 30 years old and not portable at all. Not really the kind of dependency I want for LibrePCB.
  • Bundled translations: Would be very strange to have UI translations compiled into the application while backend strings are loaded at runtime. Also after a quick sight I do not see the possibility to specify multiple languages as fallbacks (e.g. using "de_CH" as language, but fall back to "de" for string not existing in "de_CH").

In either way, in the end I prefer to use the same system for both the UI and the backend, to keep a common workflow, consistent behavior, and low maintenance effort. So I wonder if I could use Qt's mature and painless translation system for the Slint UI?

One option would be to use a custom callback (implemented in the backend) instead of @tr(). However, I don't see an elegant way to pass the translation context (msgctxt) without needing to specify it manually in every call. Also a custom callback is way less convenient than @tr() (no optional arguments etc.).

So I wonder if I could use @tr() and somehow register my own translation function in the backend? This would also allow to use slint-tr-extractor for the string extraction - the *.po can then easily be converted to Qt's *.ts file format (or any other).

One tricky thing however would be placeholders handling. Slint uses {} and {n} while Qt uses %1 and %n. I'm not sure if platforms like Transifex will handle Qt translation files correctly if {n} syntax is used. Probably I'd rather convert the *.po with {n} syntax to *.ts with %n syntax, and convert it back in the C++ backend.

Would such a custom @tr() handler be a reasonable solution, and maybe even a low-hanging fruit?

Any tip would be much appreciated.

@ubruhin
Copy link
Contributor

ubruhin commented Mar 7, 2025

I just saw gettext-rs even doesn't have CI for Windows: https://github.com/gettext-rs/gettext-rs/tree/master/.github/workflows

So it's not really a surprise that this crate causes troubles on Windows :-/

Now I investigated a bit the approach of a translate callback. It looks like this call from translate() to translate_gettext() is exactly the place where Qt's (or any other toolkit's) translate function could be invoked. I implemented a PoC where translate_gettext() is replaced by a callback into my C++ backend and so far this seems to work perfectly fine to replace gettext by Qt's translation toolkit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) api Changes or additions to APIs
Projects
None yet
Development

No branches or pull requests

5 participants