Load i18n translation file after initialization (runtime)

Is it possible to load a translation file in runtime?

We have quite a few Aurelia SPAs, all sharing stadard components, one of those opens a dialog to filter for entities. I already have all the config beeing loaded dinamically (when/if the dialog is opened by the user), It’s only the translations that I didn’t figure out.

I’d like to check if the translation file was already loaded, otherwise I want to load it.

I got this working, but I’d like to know if there’s something better:

const i18n = Container.instance.get(I18N);
const translation = require("./job.json");
i18n.i18next.addResourceBundle("en", "job", translation);

Thank you!

I think that’s totally fine. Alternatively you could use loadNamespace to load a whole new, in your case “jobs” if it’s not already available. https://www.i18next.com/overview/api see loadNamespace section.

In order to do a conditional check make use of
hasResourceBundle

1 Like

that should do it, although I’ve tried to use loadNamespace with no success, I though it was something to do with the fact that we use aurelia-i18n, bu I’ll try again.

Thank you!

Tbh haven’t tried that, it just sounded like the right API for it seeing other samples. I personally went with your approach plus the has… check

1 Like

If you don’t mind loading the translations dynamically on initialization, then I would also recommend using a custom backend + namespaces. The idea is that you configure au-i18n (and thereby i18next ) with a set of custom namespaces, and a custom backend. After initialization, i18next will invoke the read method in your backend implementation. This looks like this:

async read(
  language: string, //<-- current locale
  namespace: string,  //<-- namespace for which translation is required
  callback, // error-first callback for i18next
);

i18next will invoke this method for each namespaces you have configured. The advantage is that you translation loading and aggregation logic is centralized in your backend. Apart from that, it offers more flexibility in terms of manipulating the translation resources (for example, you need call a service, and then adapt the service response to a form that is consumable by your app translation keys). Also once the current locale is changed, i18next invokes the same method in order to load the translations for the new locale. Also, it does not request for any existing namespace and/or language.

I am super biased towards this recipe, as it works for my use-case quite well and let me conveniently manage the translations using namespaces, and from heterogenous sources. However, if you want to load the resources just-in-time, then probably you are already on right track, provided you manually take care of that.

Sayan751
It’s not an option, unfortunatelly. We’ll have hundreds, maybe thoughsands of translation files, sometimes they are pertinent for a specific SPA (between quite a few SPAs that we have), and we cannot know in advance wich of them will need (I mean, we should, but…) so we’d like to load those files only when needed.
Thanks anyway.

zewa666
So, I’m trying to use “hasResourceBundle”, but it won’t work.
As I said, we’re using import { I18N } from "aurelia-i18n"; from Aurelia.

See for yourself:

As you can see, “job” is there, I know because I can use it’s translations, but “hasResourceBundle” method only gives me false. Am I doing somethign wrong?

Thank you!

So, my final solution (off course is adapted to our needs), looks like this:

private static loadI18nNamespace(i18n: I18N, namespace: string) {
        const lang = i18n.getLocale() === "en" ? "en" : "fr";

        if (i18n.i18next.translator.options.ns.indexOf(namespace) === -1) {
            try {
                //translation filePath is relative to this file
                const translation = require("../../locales/" + lang + "/" + namespace + ".json");
                i18n.i18next.addResourceBundle(lang, namespace, translation);
            } catch (ex) {
                GeniusUtils.log(`Error loading translation: ../../locales/${lang}/${namespace}.json`);
            }
        }
    }

If anybody has something to add, or has some concern with my approach, I’d love to hear!

Thank you!

@davicoradini I am not aware of how your app is architected, therefore the following comments might be completely unfounded. But as you asked for feedback, here you go :slight_smile:

IMO it might be more performant to load the translations for the initial locale in a single go rather than loading those on demand. Considering during bundling the app you typically know what components ends in the bundle, it might not be that difficult to produce tree-shaked translation bundle.

Moreover, I feel that this way every component need to worry about loading its own translation resources. However, there might be more clever solution to this problem.

1 Like

Was it registered previously as resource bundle? Alternatively maybe just try to getResource. It will eagerly fetch but at least should do it. Your approach only checks whether the namespace exists. It could be still empty. But nevertheless that most likely will be good enough for typical use cases.

1 Like

Depends on architecture as you said. If parts of the app are lazy loaded or conditionally displayed based on user actions/permissions it could definitely make sense to load on demand.

I guess a mix of both might be the right approach. So load the bulk where you’re sure that will be used, but fetch the rest later on demand

2 Likes

Those are pretty usefull comments, I appreciate your time.

Regards,

2 Likes