i18next / i18next-http-backend

i18next-http-backend is a backend layer for i18next using in Node.js, in the browser and for Deno.
MIT License
453 stars 70 forks source link

Allow rejection of attempted load via returning falsy loadPath #88

Closed ItsOnlyBinary closed 2 years ago

ItsOnlyBinary commented 2 years ago

Reasoning for feature

As part of the plugin system for the web app I'm working on, I would like to provide the ability for plugins to provide all their translations at once or a url path to their json translation files.

When providing all the translations at once, changing language results in the default namespace's translations also being loaded in to the plugins namespace. (due to static loadPath)

To solve these issues I would like to be able to stop a translation download if a url is not stored for the loading namespace

How I intend to use this feature within the web app:

loadPath: (langs, namespaces) => {
    // If allowMultiLoading is false, langs and namespaces will have only one element
    const namespace = namespaces[0];
    return (namespace === 'translation') ?
        'static/locales/{{lng}}.json' :
        api.translationUrls[namespace];
},
adrai commented 2 years ago

How does your i18next config look like? Can you create a reproducible example that show how the default namespace 'translation' is loaded? Normally defining the namespaces with the ns option should not extra load the default namespace 'translation': ns: ["ns1", "ns2"] https://www.i18next.com/overview/configuration-options#languages-namespaces-resources

ItsOnlyBinary commented 2 years ago
    Vue.use(VueI18Next);

    i18next.use(i18nextXHR);
    i18next.init({
        whitelist: AvailableLocales.locales,
        fallbackLng: 'en-us',
        lowerCaseLng: true,
        backend: {
            loadPath: 'static/locales/{{lng}}.json',

            // allow cross domain requests
            crossDomain: false,

            // allow credentials on cross domain requests
            withCredentials: false,
        },
        interpolation: {
            // We let vuejs handle HTML output escaping
            escapeValue: false,
        },
    });

    // Build in the english translation so it can be used as a fallback
    i18next.addResourceBundle('en-us', 'translation', FallbackLocale);

App plugins adding all their translations at once (intended if only a few translations) would use i18n.addResourceBundle(lang, namespace, data); for each language with namespace being plugin-somename

Once a new namespace has been created changing the language results in each namespace trying to load, which for namespaces added by the plugins the loadPath is not correct but still loads translations.

Hope that better explains why I would like this feature

adrai commented 2 years ago

This looks like an "add after init" setup: https://www.i18next.com/how-to/add-or-load-translations#combined-with-a-backend-plugin

Try these options:

ns: [],
partialBundledLanguages: true,
resources: {}

btw: the whitelist option is deprecated / removed => supportedLngs

ItsOnlyBinary commented 2 years ago

I dont believe that is going to solve my issue, adding resources is working fine. its when changing the language im forced to return a url for every namespace regardless if one exists or not, hopefully this screenshot will help explain

image

the console logs are coming from console.log('loadPath', { namespace, lang }); within loadPath function

plugin-test already added all of its translations and i do not have a url to return for that namespace, with a static loadPath resulting in the same translations being requested twice from the server

adrai commented 2 years ago

Yes, if the namespace for a specific language has not been loaded (nor added via addResourceBundle or similar), it will try to load it via configured backend plugin. So when changing the language and the translations for that language are not present, it will try to load it. How would that namespaces be lazy loaded then, if not via configured backend? Do you have another mechanism?

btw: Does it create any problem when a namespace is requested, that is not provided via backend plugin? it's just a 404, right? This should not influence your app?

plugin-test already added all of its translations and i do not have a url to return for that namespace, with a static loadPath resulting in the same translations being requested twice from the server

if this is true, they will not be requested again, because the resources are already present in i18next's internal store

ItsOnlyBinary commented 2 years ago

The plugins namespace might not have a translation for that language at all or a url to lazy load it. But currently I am force to return a url in loadPath, if I just return the default namespaces lazy load path when no other exists it result in loading that language twice

adrai commented 2 years ago

The plugins namespace might not have a translation for that language at all or a url to lazy load it. But currently I am force to return a url in loadPath, if I just return the default namespaces lazy load path when no other exists it result in loading that language twice

I don't think so, they will not be requested again, because the resources are already present in i18next's internal store.

Can you please provide a simple reproducible example that shows this behaviour?

ItsOnlyBinary commented 2 years ago

https://jsfiddle.net/6robxatq/1/

ItsOnlyBinary commented 2 years ago

If they are not in the store and I have no url for that namespace my only option is to load the default namespaces translations into the new namespace, or intentionally return a url that cannot be reached

adrai commented 2 years ago

like suggested, just add ns: [] to the i18next options: https://jsfiddle.net/dbpk4a8o/

ItsOnlyBinary commented 2 years ago

but that would stop the default namespace from loading its translations from 'static/locales/{{lng}}.json'

ItsOnlyBinary commented 2 years ago

extended the example with plugin-{small,large}

https://jsfiddle.net/6robxatq/2/

adrai commented 2 years ago

Ok for your setup none of this options is needed. Just added supportedLngs and changed the language to a different one: https://jsfiddle.net/ohfbe1Ld/ This is my result:

image

It only loads the translations that are not present in the i18next store (so not yet loaded) in this example plugin-small for fr is not loaded twice. i18next prevents this here: https://github.com/i18next/i18next/blob/master/src/BackendConnector.js#L50

For fr the translation namespace and the plugin-large namespace are not loaded, that's why they get loaded

ItsOnlyBinary commented 2 years ago

in your changes make the change language to 'de'

The main app may support languages the plugin never will (plugin would fallback to en-us) and small plugins would never have a url to get them from (and there is no way to stop it trying)

adrai commented 2 years ago

ahhh so there are inconsistencies between your supportedLngs in your main app and the plugins? So the app could support 'en', 'fr', 'de'... but the plugin only supports 'fr' ?

ItsOnlyBinary commented 2 years ago

yes and not provide a url to obtain other langs

adrai commented 2 years ago

strange setup, but ok... normally a plugin would maintain its own i18next instance... but I see this would probably require to change more in your setup...

ItsOnlyBinary commented 2 years ago

we have support for plugins within a single html file (somewhat like vue.js single file component).

plugins having their own i18next instance would only really work if all plugins where webpacked

adrai commented 2 years ago

https://www.i18next.com/overview/api#cloneinstance would not need any sort of packaging... you could pass a clonedInstance to the plugin...

but ok... fyi: I would suggest to return an empty object {} instead of false here: https://github.com/i18next/i18next-http-backend/pull/88/files#diff-92bbac9a308cd5fcf9db165841f2d90ce981baddcb2b1e26cfff170929af3bd1R62

ItsOnlyBinary commented 2 years ago

Plugins having their own instances would add a lot of confusion in vue templates where we have a mixin to provide $t('namespace:key') access to i18next for reactive translations (they all update when the language changes)

I will push returning {} change shortly, I have also made a note of supportedLngs change that is needed in the app (thanks)

ItsOnlyBinary commented 2 years ago

Thanks, your time has been greatly appreciated

adrai commented 2 years ago

v1.4.0 is the version you are looking for ;-)

If you like this module don’t forget to star this repo. Make a tweet, share the word or have a look at our https://locize.com to support the devs of this project.

There are many ways to help this project 🙏