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

Convert from i18next-xhr-backend to i18next-http-backend #85

Closed toan2948 closed 2 years ago

toan2948 commented 2 years ago

🐛 Bug Report

I am converting from i18next-xhr-backend to i18next-http-backend, as i18next-xhr-backend is deprecated. With 'xhr' my project works well, but when changes to 'http' it does not work, the language is not switched. I have changed only two lines of code as follow:

  1. import Backend from "i18next-http-backend"; import XHR from 'i18next-xhr-backend';

  2. i18n .use(LanguageDetector) .use(initReactI18next) .use(XHR) .use(Backend)

With i18next-xhr-backend, I got this console when turning on the debug:

image

With i18next-http-backend, I got this console when turning on the debug:

image

What should I do more in converting to i18next-http-backend ?

To Reproduce

The code in i18n.js:

import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from "i18next-http-backend";
import XHR from 'i18next-xhr-backend';
 import {initReactI18next}  from 'react-i18next'

import { TRANSLATIONS, BACKEND } from './constants/api';
import { selectLng, lngList } from './utils/localization';

var _typeof =
  typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
    ? function(obj) {
        return typeof obj;
      }
    : function(obj) {
        return obj &&
          typeof Symbol === 'function' &&
          obj.constructor === Symbol &&
          obj !== Symbol.prototype
          ? 'symbol'
          : typeof obj;
      };

function addQueryString(url, params) {
  if (
    params &&
    (typeof params === 'undefined' ? 'undefined' : _typeof(params)) === 'object'
  ) {
    var queryString = '',
      e = encodeURIComponent;

    // Must encode data
    for (var paramName in params) {
      queryString += '&' + e(paramName) + '=' + e(params[paramName]);
    }

    if (!queryString) {
      return url;
    }

    url = url + (url.indexOf('?') !== -1 ? '&' : '?') + queryString.slice(1);
  }

  return url;
}

i18n
  .use(LanguageDetector)
    .use(initReactI18next)
    .use(XHR)
  .init({
    backend: {
      loadPath: TRANSLATIONS + '#{{ns}}#{{lng}}',
      crossDomain: true,
      parse: data => data,
      ajax: (fullUrl, options, callback, data, cache) => {
        let url = fullUrl.split('#')[0];
        const ns = fullUrl.split('#')[1] || 'default';
        const lng = fullUrl.split('#')[2] || 'de';
        const langIndex = 1; //location of language name in URL
        let urlPathParts = url.replace(BACKEND, '').split('/');
        const urlLng = urlPathParts[langIndex];

        if(urlLng && urlLng !== lng && lngList().indexOf(urlLng) > -1){ //check if lang correspond to existing list and is different from lang in url hash
          urlPathParts[langIndex] = lng; //replace needed language
          url = BACKEND + urlPathParts.join('/'); //and recreate URL
        }

        if (ns === 'translations') {
          callback('', { status: 200 });
          return;
        }

        if (
          localStorage.getItem(lng + '_cache') &&
          localStorage.getItem('translationsBuild') //&&
          //new Date(localStorage.getItem('cacheExpired')) > new Date()
        ) {
          const translation = JSON.parse(localStorage.getItem(lng + '_cache'))[
            ns
          ];
          if (translation) {
            callback(translation, { status: 200 });
            return;
          }
        }

        if (
          data &&
          (typeof data === 'undefined' ? 'undefined' : _typeof(data)) ===
            'object'
        ) {
          if (!cache) {
            data['_t'] = new Date();
          }
          // URL encoded form data must be in querystring format
          data = addQueryString('', data).slice(1);
        }

        if (options.queryStringParams) {
          url = addQueryString(url, options.queryStringParams);
        }

        try {
          var x;
          if (XMLHttpRequest) {
            x = new XMLHttpRequest();
          } else {
            return;
          }
          x.open(data ? 'POST' : 'GET', url, 1);
          //x.setRequestHeader('Accept-Language', lng);
          if (!options.crossDomain) {
            x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
          }
          x.withCredentials = !!options.withCredentials;
          if (data) {
            x.setRequestHeader(
              'Content-type',
              'application/x-www-form-urlencoded'
            );
          }
          if (x.overrideMimeType) {
            x.overrideMimeType('application/json');
          }
          var h = options.customHeaders;
          if (h) {
            for (var i in h) {
              x.setRequestHeader(i, h[i]);
            }
          }
          x.onreadystatechange = function() {
            localStorage.setItem(lng + '_cache', x.responseText);
            /*localStorage.setItem(
              'cacheExpired',
              new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000)
            );*/
            x.readyState > 3 &&
              callback &&
              callback(JSON.parse(x.responseText)[ns], x);
          };
          x.send(data);
        } catch (e) {
          console && console.log(e);
        }
      }
    },
    fallbackLng: false,

    lng: selectLng(),
    debug: true,

    // have a common namespace used around the full app
    ns: ['translations'],
    defaultNS: 'translations',

    keySeparator: ' ', // we use content as keys

    interpolation: {
      escapeValue: false, // not needed for react!!
      formatSeparator: ','
    },
    initImmediate: false,
    react: {
       // wait: true,
      withRef: false,
      bindI18n: 'languageChanged loaded',
      bindStore: 'added removed',
      nsMode: 'default',
      omitBoundRerender: false,
      useSuspense: false
    }
  });

export default i18n;

Code in i18nconfig.js:

import i18next from 'i18next';
import i18n from '../i18n';

i18next.init(i18n);

export default i18next;

Expected behavior

The translation should work as before with i18next-xhr-backend. All namespaces are loaded. The languages are switched.

Your Environment

adrai commented 2 years ago

i18next-http-backend has no ajax option, but a request option: https://github.com/i18next/i18next-http-backend#backend-options image

toan2948 commented 2 years ago

Thank you @adrai, I have changed the ajax option to a request option as below. But it still does not work. Do I miss something else or what should I notice when changeing to a request option?

old: ajax: (fullUrl, options, callback, data, cache) => { new: request: function (options, fullUrl, data, callback, cache) {...}

image
adrai commented 2 years ago

A reproducible example would be nice... What is the console showing before the missing logs?

toan2948 commented 2 years ago

Before the missing logs, console shows this:

image

I also got this error:

image

it points to line 83 of i18n.js:

image
adrai commented 2 years ago

Did you check what is in there? console.log(localiStorage.getItem(lng + '_cache'))

toan2948 commented 2 years ago

Running console.log( localStorage.getItem(lng + '_cache')) I got this:

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /locales/add/en/footer</pre>
</body>
</html>
adrai commented 2 years ago

So that looks like to be the response from your webserver.

Clear your localstorage cache and check the arguments in your request function before: console.log({ data, url, fullUrl, options }) x.open(data ? 'POST' : 'GET', url, 1);

Sorry, but I will not continue to debug for you. You have to debug your code yourself.

If you see an issue, provide a reproducible example.

toan2948 commented 2 years ago

When I refresh the page, I got no more error as above, and the console returns this:

image

But still warnings:

image

I will make a reproducible code. If you have any opinion with the above warnings, please let me know.

adrai commented 2 years ago

probably your callback signature is still wrong... instead of callback(translations) it should be callback(null, translations)

toan2948 commented 2 years ago

I have solved the issue with your suggest on the callback function.

Here is how I changed: -callback('', { status: 200 }) +callback(null, { status: 200, })

-callback(translation, { status: 200 }) +callback(null, { status: 200, data: translation })

-callback(JSON.parse(x.responseText)[ns], x) +callback(null, {data: JSON.parse(x.responseText)[ns]})

Thank you @adrai