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
447 stars 69 forks source link

Translations from API valid but not loaded correctly with custom request #99

Closed thecodingcrow closed 1 year ago

thecodingcrow commented 1 year ago

🐛 Bug Report

Im using next-i18next with a i18next-http-backend in a Next.js app inside a Nx monorepo. I got everything working first, when i used the 'loadPath' option to request the translations from a REST API.

Now I want to use our own GraphQL API, and it seems to have an issue that I cannot find myself. The returned translations come in a valid JSON format but cannot be loaded by the http-backend

To Reproduce

Im utilizing the custom request option as seen here in my next-i18next.config.js:

module.exports = {
  i18n: {
    ...
    backend: {
      loadPath: `{{lng}}/{{ns}}`,
      request: async (_, url, __, cb) => {
        const [language, mainscope] = url.split('/');

        try {
          const data = (
            await request(gqlBackendUrl, TRANSLATIONS_FOR_MAINSCOPE, {
              language,
              mainscope,
            })
          ).TranslationsForMainscope?.data;

          // data is a valid JSON containing translations in the form {key: value}
          // see the logs below for an example

          cb({ status: 200, data });
        } catch (err) {
          console.error(err);
          cb({ status: undefined, data: undefined });
        }
      },
    },
    debug: true,
  },
  serializeConfig: false,
  use: [I18NextHttpBackend],
};

in the FE I use the serverSideTranslations method inside getStaticProps to load the desired namespaces:

import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import nextI18nextConfig from '../next-i18next.config';

...
export async function getStaticProps({ locale }) {
 ...
  return {
    props: {
      ...(await serverSideTranslations(
        'de_DE',
        [
          NS.SCHRACK_CUSTOMER,
          NS.APP,
          NS.CHECKOUT,
          NS.FE_CONTAINER,
          NS.MAGE_CUSTOMER,
        ],
        nextI18nextConfig
      )),
    },
    revalidate: 10,
  };
}

And to display the translation I use the useTranslation hook:

import { useTranslation } from 'next-i18next';

...
 const { t } = useTranslation(['Mage_Customer']);

  return (
    <Layout mainNav={props.mainNav}>
      <span>{t('Welcome Email')}</span>
    </Layout>
  );

The interception works fine and our GQL backend also returns the desired information as seen in the logs. BUT, although the information seems to be correct, it wont load the translations.

> npm run start

> schrack@0.0.0 start
> nx serve

> nx run frontend:serve:development

info  - automatically enabled Fast Refresh for 1 custom loader
wait  - compiling...
event - compiled client and server successfully in 2.7s (242 modules)
[HPM] Proxy created: /api  -> http://localhost:3333
[HPM] Proxy created: /graphql  -> http://localhost:3333
[ ready ] on http://localhost:4200
wait  - compiling /_error (client and server)...
wait  - compiling...
event - compiled client and server successfully in 209 ms (243 modules)
i18next: languageChanged de_DE
i18next: initialized {
  debug: true,
  initImmediate: undefined,
  ns: [],
  defaultNS: 'Schracklive_Schrack',
  fallbackLng: [ 'de_DE' ],
  fallbackNS: false,
  supportedLngs: false,
  nonExplicitSupportedLngs: false,
  load: 'currentOnly',
  preload: [ 'de_DE', 'en_GB' ],
  simplifyPluralSuffix: true,
  keySeparator: '.',
  nsSeparator: ':',
  pluralSeparator: '_',
  contextSeparator: '_',
  partialBundledLanguages: false,
  saveMissing: false,
  updateMissing: false,
  saveMissingTo: 'fallback',
  saveMissingPlurals: true,
  missingKeyHandler: false,
  missingInterpolationHandler: false,
  postProcess: false,
  postProcessPassResolved: false,
  returnNull: true,
  returnEmptyString: true,
  returnObjects: false,
  joinArrays: false,
  returnedObjectHandler: false,
  parseMissingKeyHandler: false,
  appendNamespaceToMissingKey: false,
  appendNamespaceToCIMode: false,
  overloadTranslationOptionHandler: [Function: handle],
  interpolation: {
    escapeValue: false,
    format: [Function: bound format],
    prefix: '{{',
    suffix: '}}',
    formatSeparator: ',',
    unescapePrefix: '-',
    nestingPrefix: '$t(',
    nestingSuffix: ')',
    nestingOptionsSeparator: ',',
    maxReplaces: 1000,
    skipOnVariables: true
  },
  errorStackTraceLimit: 0,
  localeExtension: 'json',
  localePath: './public/locales',
  localeStructure: '{{lng}}/{{ns}}',
  react: { useSuspense: false },
  reloadOnPrerender: false,
  serializeConfig: false,
  use: [ [Function: Backend] { type: 'backend' } ],
  lng: 'de_DE',
  defaultLocale: 'de_DE',
  locales: [ 'de_DE', 'en_GB' ],
  backend: {
    loadPath: '{{lng}}/{{ns}}',
    request: [AsyncFunction: request],
    addPath: '/locales/add/{{lng}}/{{ns}}',
    allowMultiLoading: false,
    parse: [Function: parse],
    stringify: [Function: stringify],
    parsePayload: [Function: parsePayload],
    reloadInterval: 3600000,
    customHeaders: {},
    queryStringParams: {},
    crossDomain: false,
    withCredentials: false,
    overrideMimeType: false,
    requestOptions: { mode: 'cors', credentials: 'same-origin', cache: 'default' }
  },
  resources: undefined,
  ignoreJSONStructure: true
}
i18next: languageChanged de_DE
wait  - compiling / (client and server)...
wait  - compiling...
event - compiled client and server successfully in 1787 ms (499 modules)
i18next: languageChanged de_DE
Loading de_DE for Schracklive_Schrack
Loading en_GB for Schracklive_Schrack
i18next::backendConnector: loading namespace Schracklive_Schrack for language de_DE failed {
  status: 200,
  data: '{"Our Customer Support Take Care About Your Desire. You Have The Problem - We Have The Solution.":"Unsere Kundenberater kümmern sich um ihr Anliegen. Sie haben ein Problem - wir die Lösung.","No data for table available":"Keine Daten in der Tabelle vorhanden","admin":"Administrator","affiliate":"darf alle Bestellungen verwalten","anager":"rf im Namen verbundener Unternehmen bestelle","customer":"Einkäufer","dmin":"nden Admi","ffiliate":"rf alle Bestellungen verwalte","manager":"darf im Namen verbundener Unternehmen bestellen","No Role set":"Keine Berechtigung vergeben","o Role set":"ine Berechtigung vergebe","projectant":"Projectant","staff":"Kalkulant","taff":"rf Preise sehe","ustomer":"rf bestelle","VAT":"MWSt.","Start Contacting Us!":"Nehmen Sie mit uns Kontakt auf!","Advisors Team":"Betreuer-Team","List Price Customer":"Listenpreis Einkäufer","Product cannot be added to cart because maximum item count %d has already been reached.":"Artikel konnte nicht zum Warenkorb hinzugefügt werden, da die Maximalanzahl von %d Artikel überschritten wird.","Product cannot be added to parts list because maximum item count %d has already been reached.":"Artikel konnte nicht zur Merkliste hinzugefügt werden, da die Maximalanzahl von %d Artikel überschritten wird.","What Is Your Desire?":"Was ist Ihr Anliegen?","Your Personal Contact":"Ihr persönlicher Ansprechpartner","Please Fill Out All Required Fields":"Bitte füllen Sie alle Pflichtfelder aus!","Email Successfully Processed":"E-Mail erfolgreich gesendet!","":""}'
}
i18next::backendConnector: loading namespace Schracklive_Schrack for language en_GB failed {
  status: 200,
  data: '{"Our Customer Support Take Care About Your Desire. You Have The Problem - We Have The Solution.":"Our Customer Support Take Care About Your Desire. You Have The Problem - We Have The Solution.","No data for table available":"No data for table available","admin":"admin","affiliate":"affiliate","anager":"rf im Namen verbundener Unternehmen bestelle","customer":"Buyer","dmin":"nden Admi","ffiliate":"rf alle Bestellungen verwalte","manager":"manager","No Role set":"No Role set","o Role set":"ine Berechtigung vergebe","projectant":"Project Planner","staff":"Estimator","taff":"rf Preise sehe","ustomer":"rf bestelle","VAT":"VAT","Start Contacting Us!":"Start Contacting Us!","Advisors Team":"Advisors Team","List Price Customer":"list price buyer","Product cannot be added to cart because maximum item count %d has already been reached.":"Product cannot be added to cart because maximum item count %d has already been reached.","Product cannot be added to parts list because maximum item count %d has already been reached.":"Product cannot be added to parts list because maximum item count %d has already been reached.","What Is Your Desire?":"What is your desire?","Your Personal Contact":"Your Personal Contact","Please Fill Out All Required Fields":"Please fill out all required fields","Email Successfully Processed":"Email successfully processed","":""}'
}
i18next: languageChanged de_DE
i18next: languageChanged de_DE
i18next: init: i18next is already initialized. You should call init just once!
i18next: languageChanged de_DE
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
i18next: languageChanged de_DE
i18next: languageChanged de_DE

The issue here is, that although the json is totally valid and the status code is 200, i18next fails to load them.

Some more things I cant get my head around but that dont seem to matter so much:

The languageChanged event occurs more often than it should I guess, should only happen once on the inital load These two warnings are also present: i18next: init: i18next is already initialized. You should call init just once! react-i18next:: You will need to pass in an i18next instance by using initReactI18next

Expected behavior

What I expect from this configuration is the intended behaviour of next-i18next. The translations are loaded on the server-side, with our own GQL API being used. Then the returned translations are loaded by i18n and injected in the page via the response from the serverSideTranslations method called in getStaticProps or getServerSideProps

Your Environment

adrai commented 1 year ago

instead of cb({ status: 200, data }); write cb(null, { status: 200, data }); the first argument is the error