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

Multi-crate project required ftl files for non-translated crates and uses wrong translation file #139

Open
junglie85 opened this issue Jan 11, 2025 · 1 comment

Comments

@junglie85
Copy link

I have a multi crate project with the following structure:

|- nih
    |- data
    |   |- i18n
    |       |- en-GB
    |           |- nih_i18n.ftl    <-- Why do I need this file?
    |           |- nih.ftl
    |- nih
    |   |- i18n.toml               <-- Why do I need this file?
    |   |- ...
    |- nih_i18n
    |   |- i18n.toml               <-- Why do I need this file?
    |   |- ...
    |- i18n.toml
    |- ...

The contents of my main i18n.toml is:

subcrates = ["nih"]

fallback_language = "en-GB"

[fluent]
assets_dir = "data/i18n"

The docs say that all subcrates will be treated as part of the parent crate, unless they have their own i18n.toml file. If I remove either of these files from the subcrates, I get the following errors depending on which file I remove:

error: proc macro panicked
  --> nih_i18n\src\lib.rs:30:44
   |
30 |         let loader: FluentLanguageLoader = fluent_language_loader!();
   |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: message: fluent_language_loader!() had a problem reading i18n config file "D:\\Dev\\nih\\nih_i18n\\i18n.toml": Cannot read file "D:\\Dev\\nih\\nih_i18n\\i18n.toml" in the current working directory Ok("D:\\Dev\\nih") because The system cannot find the file specified. (os error 2).

or

error: fl!() had a problem reading i18n config file "D:\\Dev\\nih\\nih\\i18n.toml": Cannot read file "D:\\Dev\\nih\\nih\\i18n.toml" in the current working directory Ok("D:\\Dev\\nih") because The system cannot find the file specified. (os error 2).
       
         = help: Try creating the `i18n.toml` configuration file.
       
  --> nih\src\main.rs:28:27
   |
28 |         window.set_title(&fl!("hello-world"));
   |                           ^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the macro `$crate::i18n_embed_fl::fl` which comes from the expansion of the macro `fl` (in Nightly builds, run with -Z macro-backtrace for more info)

Why do the subcrates need the individual i18n.toml files? I can perhaps understand why it is needed in the nih crate where I have translations, although it is surprising that the global file is not used. I don't understand why it is needed in the nih_i18n crate, where there are not translations and I'm just setting up the language loader, localiser and re-exporting the fl macro as per the example:

use std::sync::OnceLock;

use i18n_embed::{
    fluent::{fluent_language_loader, FluentLanguageLoader},
    DefaultLocalizer, DesktopLanguageRequester, I18nEmbedError, LanguageLoader, Localizer,
    RustEmbedNotifyAssets,
};
pub use i18n_embed_fl;
use nih_error::Error;
use rust_embed::RustEmbed;

#[derive(RustEmbed)]
#[folder = "../data/i18n/"]
pub struct LocalizationsEmbed;

pub fn get_localisations() -> &'static RustEmbedNotifyAssets<LocalizationsEmbed> {
    pub static LOCALIZATIONS: OnceLock<RustEmbedNotifyAssets<LocalizationsEmbed>> = OnceLock::new();

    LOCALIZATIONS.get_or_init(|| {
        RustEmbedNotifyAssets::new(
            std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("data/i18n/"),
        )
    })
}

pub fn get_language_loader() -> &'static FluentLanguageLoader {
    static LANGUAGE_LOADER: OnceLock<FluentLanguageLoader> = OnceLock::new();

    LANGUAGE_LOADER.get_or_init(|| {
        let loader: FluentLanguageLoader = fluent_language_loader!();

        // Load the fallback langauge by default so that users of the
        // library don't need to if they don't care about localization.
        loader
            .load_fallback_language(get_localisations())
            .expect("Error while loading fallback language");

        loader
    })
}

#[macro_export]
macro_rules! fl {
    ($message_id:literal) => {{
        $crate::i18n_embed_fl::fl!($crate::get_language_loader(), $message_id)
    }};

    ($message_id:literal, $($args:expr),*) => {{
        $crate::i18n_embed_fl::fl!($crate::get_language_loader(), $message_id, $($args), *)
    }};
}

fn localizer() -> DefaultLocalizer<'static> {
    DefaultLocalizer::new(get_language_loader(), get_localisations())
}

pub fn init_i18n() -> Result<(), Error> {
    let localiser = localizer()
        .with_autoreload()
        .expect("failed to enable localisation autoreloader");

    let requested_languages = DesktopLanguageRequester::requested_languages();
    nih_logging::debug!("{:?}", &requested_languages);
    localiser
        .select(&requested_languages)
        .map_err(failed_to_load_i18n_languages)?;

    Ok(())
}

fn failed_to_load_i18n_languages(error: I18nEmbedError) -> Error {
    Error::new("failed to load languages for i18n").with_source(error)
}

Then, when this is working with all these extra config files, it selects the wrong translation. Below are my translation files:

nih_i18n.ftl:

hello-world = Hello World Wrong!

nih.ftl:

hello-world = Hello World Right!

And the usage code in nih/src/main.rs:

fn create() -> Result<Self, Error> {
    let window = Context::get_window();
    window.set_title(&fl!("hello-world"));

    Ok(Nih {})
}

But as you can see, it does not use the correct translation:

image

What I was expecting:

  • a single i18n.toml file at the project root.
  • not having to provide nih_i18n.ftl for a crate where I do no localisation.
  • for the fl! invocation in the nih crate to use the translation specified in nih.ftl.
  • project structure like this:
|- nih
    |- data
    |   |- i18n
    |       |- en-GB
    |           |- nih.ftl
    |- nih
    |   |- ...
    |- nih_i18n
    |   |- ...
    |- i18n.toml
    |- ...
@junglie85
Copy link
Author

junglie85 commented Jan 11, 2025

To add a bit more information, if the nih_i18n.ftl translation file is missing, I get the following error:

Exception thrown at 0x00007FFE6723FB4C in nih.exe: Microsoft C++ exception:  ?? ::st_panic at memory location 0x000000F6B4CFE5B0.
Loaded 'C:\Windows\System32\kernel.appcore.dll'. 

Which appears to originate somewhere in LanguageLoader:

fn load_fallback_language(&self, i18n_assets: &dyn I18nAssets) -> Result<(), I18nEmbedError> {
    self.load_languages(i18n_assets, &[self.fallback_language().clone()])
}

If instead the nih.ftl translation file is missing, I get the following error:

error: fl!() was unable to load the localization file for the `fallback_language` ("en-GB"): en-GB/nih.ftl
       
         = help: Try creating the required fluent localization file.
       
  --> nih\src\main.rs:26:27
   |
26 |         window.set_title(&fl!("hello-world"));
   |                           ^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the macro `$crate::i18n_embed_fl::fl` which comes from the expansion of the macro `fl` (in Nightly builds, run with -Z macro-backtrace for more info)

If in nih.ftl I change the message ID to something else, the error changes to:

error: fl!() `message_id` validation failed. `message_id` of "hello-world" does not exist in the `fallback_language` ("en-GB")
       
         = help: Enter the correct `message_id` or create the message in the localization file if the intended message does not yet exist.
         = help: Perhaps you are looking for one of the following messages?
       
       hello-world2
       
  --> nih\src\main.rs:26:31
   |
26 |         window.set_title(&fl!("hello-world"));
   |                               ^^^^^^^^^^^^^

error: could not compile `nih` (bin "nih") due to 1 previous error

But if I instead change the message ID in nih_i18n.ftl to something else, I get the following output:

image

Overall, this is very confusing and I'm hoping that there is either some configuration that I've missed or an easy fix in the code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant