aurelia / i18n

A plugin that provides i18n support.
MIT License
93 stars 70 forks source link

Defaulttranslations gets cached when xhr requests fails #278

Closed groenlid closed 5 years ago

groenlid commented 5 years ago

I'm submitting a bug report

Please tell us about your environment:

Current behavior:

export const configureLanguage = (aurelia: Aurelia) => {
    aurelia.use.plugin(PLATFORM.moduleName('aurelia-i18n'), (instance: I18N) => {
        instance.i18next
            .use(XhrBackend)
            .use(LngDetector)
            .use(Cache);

        return instance.setup({
            backend: {
                loadPath: endpoints.translation
            },
            cache: {
                enabled: ENV === 'production',
                expirationTime: 4 * 60 * 60 * 1000,
            },
            attributes: ['t', 'i18n'],
            debug: true,
            crossDomain: true,
            fallbackLng: 'en',
            joinArrays: ', ',
            load: 'languageOnly',
            detection: {
                order: ['localStorage', 'navigator', 'htmlTag'],
                lookupLocalStorage: 'i18nextLng',
                htmlTag: document.documentElement
            }
        })
        .then(() => aurelia.container.get(AppRouter).transformTitle = (title: string) => instance.tr(title));
    });
};

In the case where the xhr request to our translation-service fails, the cache is still populated with the default-translations included in aurelia-i18n. The next times the user refreshes the page, the cache with the wrong values is returned until it expires.

zewa666 commented 5 years ago

I'm not sure I fully understand the problem. Caching is part of i18next and is nothing that is specifically touched by the aurelia-i18n plugin. But besides that it sounds like the key problem is the automatic registration of defaultTranslations, namely for the relative-time feature right? What is wrong with the translations? Or do you simply prefer loading custom ones?

Nevertheless the reason, with regards to the defaultTranslations, we could add another plugin configuration option, which when set, won't load any defaultTranslations. That means of course you'd have to provide them all by yourself for the languages of choice. Would that be helpful to solve your issue?

groenlid commented 5 years ago

Yes. The problem for us is the automatic registration. We do not use them, and by including them, I don't see how we could stop the i18next-localStorage-cache from caching when the xhr-request fails and it's only given the default-values included in aurelia-i18n.

Adding a configuration-option would fix our problem :+1:

zewa666 commented 5 years ago

Do you want to go ahead and try creating a PR for that? If you need any guidance just ping me either here or on gitter

groenlid commented 5 years ago

This issue seems to have been resolved by upgrading aurelia-i18n to the latest version + using i18next-chained-backend instead of using xhrbackend and localstorage-backend separately.

Thanks for the support! :+1:

zewa666 commented 5 years ago

Ahh cheap trick to avoid a PR :)

May I just ask you to post how your new instance setup looks like with your adaption? https://github.com/i18next/i18next-chained-backend indeed looks nice

groenlid commented 5 years ago

sure :)

Here's our current setup, but I'm looking into having the caching part in our serviceworker instead of localstorage :)

const fetchNewLanguageTexts = (i18next: any, logger: Logger) => {
    const getCacheName = (language: string) => cachePrefix + language + '-translation';
    const languages: string[] = i18next.languages;

    const toReload = languages.filter(language => {
        const storedValue = window.localStorage.getItem(getCacheName(language));
        if (storedValue === undefined || storedValue === null) return false;
        const parsed = JSON.parse(storedValue);
        const timeFetched = parsed.i18nStamp;

        const shouldReload = timeFetched < new Date().getTime() - TIME.MINUTEINMS * 30;

        i18next.on('failedLoading', (lng: string, ns: string, msg: string) => {
            logger.error('Failed reloading languagetext. Will fall back to cached texts', lng, ns, msg);
            if (lng === language)
                window.localStorage.setItem(getCacheName(language), storedValue);
        });

        return shouldReload;
    });

    if (!isEmpty(toReload)) {
        toReload.map(getCacheName).forEach(cache => window.localStorage.removeItem(cache));
        i18next.reloadResources(toReload);
    }
};

/**
 * Translations initial visit
 * - Fetch language-texts from server
 * - Render
 * - Cache language-texts
 * Translations second visit
 * - Fetch translations from cache
 * - Render
 * - Fetch language-texts from server
 * - Rerender
 * - Cache language-texts
 */
export const configureLanguage = (aurelia: Aurelia) => {
    aurelia.use.plugin(PLATFORM.moduleName('aurelia-i18n'), (instance: I18N) => {
        instance.i18next
            .use(Backend)
            .use(LngDetector);

        const CacheOptions = {
            enabled: ENV === 'production',
            expirationTime: TIME.DAYINMS * 365,
            prefix: cachePrefix
        };

        const XhrOptions = {
            loadPath: endpoints.translation
        };

        return instance.setup({
            backend: {
                backends: [ Cache, XhrBackend ],
                backendOptions: [CacheOptions, XhrOptions]
            },
            attributes: ['t', 'i18n'],
            debug: true,
            crossDomain: true,
            fallbackLng: 'en',
            joinArrays: ', ',
            load: 'languageOnly',
            detection: {
                order: ['localStorage', 'navigator', 'htmlTag'],
                lookupLocalStorage: 'i18nextLng',
                htmlTag: document.documentElement
            }
        }).then(() => {
            aurelia.container.get(AppRouter).transformTitle = (title: string) => instance.tr(title);
            if (isLocalStorageAvailable)
                fetchNewLanguageTexts(instance.i18next, getLogger("Languagetests"));
        });
    });
};