Skip to content

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

@junglie85

Description

@junglie85

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
    |- ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions