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

Loading namespace translation loads only default language, not fallback language #109

Closed bildungsroman closed 1 year ago

bildungsroman commented 1 year ago

🐛 Bug Report

I'm lazy loading translation files with the http-backend, but I'm not getting the fallback language. Here is what I'm seeing in the console with debug: true in init:

Screenshot 2023-03-09 at 11 11 13 AM

When I expand the en object, it's actually the ja language files:

Screenshot 2023-03-09 at 11 11 24 AM

To Reproduce

Here is my i18n.ts:

import i18n, { createInstance, InitOptions } from "i18next";
import Backend, { HttpBackendOptions } from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
import { getLocaleString } from "utils/locales";
import { Locale } from "types/locales";

const i18nInstance = createInstance();
export const fallbackLng: Locale = "en";

declare module "i18next" {
  interface CustomTypeOptions {
    returnNull: false;
  }
}

export const locale = getLocaleId();

// This is Vite's replacement of process.env. See https://vitejs.dev/guide/env-and-mode.html
const isDev = import.meta.env.DEV || false;
const path = getCustomContext("translationsAssetsPath");
const prodLoadPath = getCdnUrl(`${path}/${locale}.json`);

const loadPath = isDev
  ? "http://127.0.0.1:3000/src/i18n/locales/{{lng}}.json"
  : prodLoadPath;

const addPath = isDev
  ? "http://127.0.0.1:3000/src/i18n/locales/en.json"
  :  getCdnUrl(`${path}/en.json`);

void i18nInstance
  .use(Backend)
  .use(initReactI18next)
  .init<HttpBackendOptions>({
    // https://github.com/i18next/i18next-http-backend#backend-options
    backend: {
      loadPath,
      addPath,
    },
    lng: locale,
    fallbackLng,
    debug: true, // set to `true` to debug locally
    returnNull: false,
    react: {
      useSuspense: true,
    },
    interpolation: {
      escapeValue: false, // not needed for react
    },
  } as InitOptions);

export default i18nInstance;

Expected behavior

I would expect the fallback language to load and be used for missing keys. Instead, I'm seeing the string keys:

Screenshot 2023-03-09 at 11 25 11 AM

This only happens when deployed, not locally.

Your Environment

adrai commented 1 year ago

The problem is probably const prodLoadPath = getCdnUrl(${path}/${locale}.json);. This line of code is not executed again when the locale changed? So if the value of that locale variable is initialized with ja, it will always only load ja translations. And since you have "http://127.0.0.1:3000/src/i18n/locales/{{lng}}.json" in dev when working locally, it works. So it does not work only when deployed. The loadPath should always include {{lng}}.

bildungsroman commented 1 year ago

Would the addPath not load the hardcoded en.json in addition here?

const addPath = isDev
  ? "http://127.0.0.1:3000/src/i18n/locales/en.json"
  : getCdnUrl(`${path}/en.json`);

The locale is set on page load and is not changed again. However, I would expect the fallbackLng to also be imported, in the case of missing strings in the non-English translation files (we usually have a few days' lag time for getting back translations for non-en locales, so I need the fallbackLng loaded as well).

adrai commented 1 year ago

The addPath is only used for missingKeys... when saveMissing is set to true => https://www.i18next.com/overview/configuration-options#missing-keys (this has nothing to do with loading)

Regarding the fallback loading, yes that should work when setting the fallbackLng option. => https://www.i18next.com/principles/fallback#fallback-to-different-languages You may check if there are other warnings in your console output. Also make sure the key containers.LeftPanel.ResourceView.component really exists.

bildungsroman commented 1 year ago

I've confirmed the keys exist and are correct.

So I now have this as my options, with saveMissing now set to true:

const loadPath = isDev
  ? "http://127.0.0.1:3000/src/i18n/locales/{{lng}}.json"
  : prodLoadPath;

const addPath = isDev
  ? "http://127.0.0.1:3000/src/i18n/locales/en.json"
  : getCdnUrl(`${path}/en.json`);

void i18nInstance
  .use(Backend)
  .use(initReactI18next)
  .init<HttpBackendOptions>({
    // https://github.com/i18next/i18next-http-backend#backend-options
    backend: {
      loadPath,
      addPath,
    },
    lng: locale,
    fallbackLng,
    debug: true, // set to `true` to debug locally
    returnNull: false,
    saveMissing: true,
    react: {
      // react-i18next options
      useSuspense: true,
    },
    interpolation: {
      escapeValue: false, // not needed for React as it escapes by default
    },
  } as InitOptions);

But I'm still seeing the en strings actually being ja, despite the correct paths here:

Screenshot 2023-03-09 at 12 16 54 PM Screenshot 2023-03-09 at 12 17 20 PM
bildungsroman commented 1 year ago

Ah, ok, I did just notice this error:

Screenshot 2023-03-09 at 12 30 20 PM

I don't understand why it's failing to fetch the en.json file when it gets the ja.json file from the exact same URL just file.

adrai commented 1 year ago

Check your network tab and check the translation requests and responses... There might still be some issue in your setup. Maybe also your CORS setup. Not easy to help without a reproducible example. I can just say and confirm i18next and i18next-http-backend works as expected. There's not much I can help with more than that, sorry...

bildungsroman commented 1 year ago

Thanks @adrai, you helped me narrow down the problem and at least confirm that the setup is correct and it's something about my env. Thank you!

ajitesh13 commented 1 year ago

Hey @bildungsroman can you tell me how to fix this issue ? I am facing the exact same issue and not able to debug it further.

bildungsroman commented 1 year ago

It was an issue with our CDN @Ajitesh13. Here is my currently working code:

export function loadI18n(logger: ILogger, locale: Locale, loadPath: string) {
  const i18nInstance = createInstance();
  const fallbackLng: Locale = "en";

  void i18nInstance
    .use(Backend)
    .use(initReactI18next)
    .init({
      // https://github.com/i18next/i18next-http-backend#backend-options
      backend: {
        loadPath, // "src/i18n/locales/{{lng}}.json";
        addPath: (lng: string, namespace: string) => {
          logError(logger, `Missing locale ${lng} for namespace ${namespace}`);
        },
      },
      load: "currentOnly",
      lng: locale,
      fallbackLng,
      debug: false, // set to `true` to debug locally
      returnNull: false,
      react: {
        useSuspense: false,
      },
      interpolation: {
        escapeValue: false, // not needed for react
      },
    } as InitOptions);
  return i18nInstance;
}