Giter VIP home page Giter VIP logo

i18next-http-backend's Introduction

Introduction

Actions Actions deno Travis npm version

This is a simple i18next backend to be used in Node.js, in the browser and for Deno. It will load resources from a backend server using the XMLHttpRequest or the fetch API.

Get a first idea on how it is used in this i18next crash course video.

It's based on the deprecated i18next-xhr-backend and can mostly be used as a drop-in replacement.

Why i18next-xhr-backend was deprecated?

Advice:

If you don't like to manage your translation files manually or are simply looking for a better management solution, take a look at i18next-locize-backend. The i18next backed plugin for ๐ŸŒ locize โ˜๏ธ.

To see i18next-locize-backend in a working app example, check out:

Troubleshooting

Make sure you set the debug option of i18next to true. This will maybe log more information in the developer console.

Seeing failed http requests, like 404?

Are you using a language detector plugin that detects region specific languages you are not providing? i.e. you provide 'en' translations but you see a 'en-US' request first?

This is because of the default load option set to 'all'.

Try to set the load option to 'languageOnly'

i18next.init({
  load: 'languageOnly',
  // other options
})

Getting started

Source can be loaded via npm or downloaded from this repo.

There's also the possibility to directly import it via a CDN like jsdelivr or unpkg or similar.

# npm package
$ npm install i18next-http-backend

Wiring up:

import i18next from 'i18next';
import HttpApi from 'i18next-http-backend';

i18next.use(HttpApi).init(i18nextOptions);

for Deno:

import i18next from 'https://deno.land/x/i18next/index.js'
import Backend from 'https://deno.land/x/i18next_http_backend/index.js'

i18next.use(Backend).init(i18nextOptions);

for plain browser:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/i18nextHttpBackend.min.js"></script>
<!-- an example can be found in example/jquery/index.html -->
  • As with all modules you can either pass the constructor function (class) to the i18next.use or a concrete instance.
  • If you don't use a module loader it will be added to window.i18nextHttpBackend

Backend Options

{
  // path where resources get loaded from, or a function
  // returning a path:
  // function(lngs, namespaces) { return customPath; }
  // the returned path will interpolate lng, ns if provided like giving a static path
  // the function might return a promise
  // returning falsy will abort the download
  //
  // If not used with i18next-multiload-backend-adapter, lngs and namespaces will have only one element each,
  // If used with i18next-multiload-backend-adapter, lngs and namespaces can have multiple elements
  //   and also your server needs to support multiloading
  //      /locales/resources.json?lng=de+en&ns=ns1+ns2
  //   Adapter is needed to enable MultiLoading https://github.com/i18next/i18next-multiload-backend-adapter
  //   Returned JSON structure in this case is
  //   {
  //    lang : {
  //     namespaceA: {},
  //     namespaceB: {},
  //     ...etc
  //    }
  //   }
  loadPath: '/locales/{{lng}}/{{ns}}.json',

  // path to post missing resources, or a function
  // function(lng, namespace) { return customPath; }
  // the returned path will interpolate lng, ns if provided like giving a static path
  // 
  // note that this only works when initialized with { saveMissing: true }
  // (see https://www.i18next.com/overview/configuration-options)
  addPath: '/locales/add/{{lng}}/{{ns}}',

  // parse data after it has been fetched
  // in example use https://www.npmjs.com/package/json5
  // here it removes the letter a from the json (bad idea)
  parse: function(data) { return data.replace(/a/g, ''); },

  // parse data before it has been sent by addPath
  parsePayload: function(namespace, key, fallbackValue) { return { key: fallbackValue || "" } },

  // parse data before it has been sent by loadPath
  // if value returned it will send a POST request
  parseLoadPayload: function(languages, namespaces) { return undefined },

  // allow cross domain requests
  crossDomain: false,

  // allow credentials on cross domain requests
  withCredentials: false,

  // overrideMimeType sets request.overrideMimeType("application/json")
  overrideMimeType: false,

  // custom request headers sets request.setRequestHeader(key, value)
  customHeaders: {
    authorization: 'foo',
    // ...
  },
  // can also be a function, that returns the headers
  customHeaders: () => ({
    authorization: 'foo',
    // ...
  }),

  requestOptions: { // used for fetch, can also be a function (payload) => ({ method: 'GET' })
    mode: 'cors',
    credentials: 'same-origin',
    cache: 'default'
  }

  // define a custom request function
  // can be used to support XDomainRequest in IE 8 and 9
  //
  // 'options' will be this entire options object
  // 'url' will be passed the value of 'loadPath'
  // 'payload' will be a key:value object used when saving missing translations
  // 'callback' is a function that takes two parameters, 'err' and 'res'.
  //            'err' should be an error
  //            'res' should be an object with a 'status' property and a 'data' property containing a stringified object instance beeing the key:value translation pairs for the
  //            requested language and namespace, or null in case of an error.
  request: function (options, url, payload, callback) {},

  // adds parameters to resource URL. 'example.com' -> 'example.com?v=1.3.5'
  queryStringParams: { v: '1.3.5' },

  reloadInterval: false // can be used to reload resources in a specific interval (milliseconds) (useful in server environments)
}

Options can be passed in:

preferred - by setting options.backend in i18next.init:

import i18next from 'i18next';
import HttpApi from 'i18next-http-backend';

i18next.use(HttpApi).init({
  backend: options,
});

on construction:

import HttpApi from 'i18next-http-backend';
const HttpApi = new HttpApi(null, options);

via calling init:

import HttpApi from 'i18next-http-backend';
const HttpApi = new HttpApi();
HttpApi.init(null, options);

TypeScript

To properly type the backend options, you can import the HttpBackendOptions interface and use it as a generic type parameter to the i18next's init method, e.g.:

import i18n from 'i18next'
import HttpBackend, { HttpBackendOptions } from 'i18next-http-backend'

i18n
  .use(HttpBackend)
  .init<HttpBackendOptions>({
    backend: {
      // http backend options
    },

    // other i18next options
  })

Gold Sponsors


From the creators of i18next: localization as a service - locize.com

A translation management system built around the i18next ecosystem - locize.com.

locize

With using locize you directly support the future of i18next.


i18next-http-backend's People

Contributors

0x0a0d avatar 2pacalypse- avatar adrai avatar caal-15 avatar codingismy11to7 avatar dependabot[bot] avatar dpilarsk avatar evancharlton avatar fcor avatar felixmosh avatar imustafin avatar itsonlybinary avatar jacobbogers avatar jamuhl avatar jansepke avatar jayshakes avatar jb-0 avatar jdeniau avatar jgerigmeyer avatar kalbert312 avatar m235 avatar nycruslan avatar pedrodurek avatar rijk avatar truewill avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

i18next-http-backend's Issues

Translations not loaded after migrating from i18next-xhr-backend

๐Ÿ› Bug Report

Attempting to upgrade from the deprecated i18n-xhr-backend to i18next-http-backend with the same configuration approach means the translations keys are no longer loaded. They are downloaded from the server successfully, but it seems that the json response is not parsed by i18next-http-backend.

To Reproduce

Moving from this config in i18n-xhr-backend:

ajax: async (url, options, callback) => {
  try {
    const response = await customFetch(url);
    const body = await response.text();
    callback(body, response);
  } catch (e) {
    console.info('Error fetching translations', e);
    callback(null, e);
  }
}

to this one in i18n-http-backend:

request: async (options, url, payload, callback) => {
  try {
    const response = await customFetch(url);
    const body = await response.text();
    callback(body, response);
  } catch (e) {
    console.info('Error fetching translations', e);
    callback(null, e);
  }
}

breaks the translations, and results in a bunch of "missing keys" debug messages in the console.

(customFetch is just adding a number of headers and tokens to the fetch request)

Expected behavior

The translations should be displayed in my application.

Your Environment

  • Node version: 12.16.1
  • i18next version: 19.4.5
  • react-i18next version: 11.5.0
  • OS: macOS Catalina 10.15.4
  • Browser: Chrome 83.0.4103.61

New types breaks i18next-chained-backend compatibility

๐Ÿ’ฅ Regression Report

The plugin overrides the backend type for i18next. This breaks existing code where developers use i18next-chained-backend

Last working version

Worked up to version: 1.1.1

Stopped working in version: 1.2.0

To Reproduce

Steps to reproduce the behavior:

Follow the guide at https://github.com/i18next/i18next-chained-backend

Expected behavior

To work as before

Your Environment

  • runtime version: node v14.15.1
  • i18next version: 20.0.0
  • os: Windows and Linux (Alpine)

Expected identifier, string or number

After installing this plugin, an error occurs in IE/Edge.
I connect this plugin like this:

`import i18next from 'i18next';
import HttpApi from 'i18next-http-backend';

i18next
.use(HttpApi)
.init({
lng: 'ru',
fallbackLng: 'ru',
backend: {
loadPath: staticUrl + '/locales/{{lng}}/{{ns}}.json'
}
});`

I use webpack and babel:

module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ "@babel/preset-env", "@babel/preset-react", ], plugins: [ "@babel/plugin-proposal-function-bind", "@babel/plugin-proposal-class-properties", "@babel/plugin-transform-spread", "@babel/plugin-proposal-object-rest-spread" ], cacheDirectory: true } } ] },

Edge
Edge2

i18n load translations from s3bucket

I am using the i18n module in my react app which is hosted as an app (which I will refer to as live app) on S3 and has cloudfront sitting in front of it.

I want to store the s3 url in a config file as to avoid having it hardcoded in my app so that I can work against translation files stored locally in the public/locales folder when I'm developing.

Initially I had my backend options set-up so that it would always try and look up the files in the locales path. And this worked locally but stopped working on S3 although the locales folder was present in the S3 bucket. I also noticed that no request was being sent from the app to retrieve the translation files.

backend: {
    loadPath: 'locales'
  }

I then decided to upload the translation files to another S3 bucket and host them there to see if this would fix the issue.
I changed my config to hardcode the s3 bucket path. This worked both locally and on the live app. But this means that I can't use my config file to determine the loadPath option.

 backend: {
    loadPath: '<myhardcoded-s3-bucket-url>/{{lng}}/translation.json',
    crossDomain: true
  }

I then thought I could construct the url in place as so:

/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
...
...
...
backend: {
    loadPath: `${AWS_CONFIG.aws_app_translations_path}/{{lng}}/translation.json`,
    crossDomain: true
  }

Strangely again this worked locally when AWS_CONFIG.aws_app_translations_path was both http://localhost:3000/locales and <myhardcoded-s3-bucket-url>.
However once I pushed it live, it failed again. This time making a request to https://<my-apps-base-path>/undefined/en-GB/translation.json for example. So it's trying to use the app path and append why I have defined in loadPath.

I then saw that I could have loadPath as a function to construct my url. This didn't work for the live app either, but again worked locally.

backend: {
    loadPath: function (lng) {
      return `${AWS_CONFIG.aws_app_translations_path}/${lng}/translation.json`
    },
    crossDomain: true
  }

This is my entire i18n file

/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";


const options = {
  order: ['navigator', 'localStorage'],
  caches: ['localStorage'],
  fallbackLng: "en-GB",
  debug: true,
  interpolation: {
    escapeValue: false // not needed for react as it escapes by default
  },
  backend: {
    loadPath: function (lng) {
      return `${AWS_CONFIG.aws_app_translations_path}/${lng}/translation.json`
    },
    crossDomain: true
  }
}

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init(options);

export default i18n;

What could explain this odd behaviour? Am I missing configuration here?

Namespaces from BE aren't loaded correctly.

๐Ÿ› Bug Report

It seems that loading translations of a single language JSON file that has multiple namespaces within it doesn't seem to work as expected. To be clear, this isn't a single language that is split up into multiple JSON files per namespace, but rather a single JSON file that has multiple namespaces within it.

To Reproduce

JSON File

{
  "es-ES": {
    "common": {
      "date": "La fecha de hoy es: {{date, LL}}",
      "english": "Inglรฉs",
      "german" : "Alemรกn",
      "spanish": "Espaรฑol",
      "logout": "Cerrar sesiรณn",
      "help": "Ayuda",
      "changeLanguage": "Cambiar idioma",
      "nav": "Nav",
      "home": "Casa",
      "list": "Lista",
      "app": "Solicitud"
    },
    "content": {
      "greeting": "Bienvenido a la aplicaciรณn de ejemplo"
    }
  }
}

i18n setup

  i18n
    .use(LanguageDetector)
    .use(HttpApi)
    .use(initReactI18next)
    .init({
      lng: 'en',
      interpolation: {
        escapeValue: false
      },
      react: {
        useSuspense: false
      },
      defaultNs: 'common',
      ns: ['common', 'content'],
      partialBundledLanguages: true,
      nonExplicitSupportedLngs: true,
      backend: {
        loadPath: `/api/project/1/Version/1/Language/{{lng}}`,
        customHeaders: {
          'Content-Type': 'application/json; charset=utf-8',
        },
        parse: (data) => {
          const json = JSON.parse(data) // returns [es-ES: {common: {โ€ฆ}, content: {โ€ฆ}}]
          return json[0].fileContent['es-ES'] // Getting a specific language for example purposes
        }
      }
    })

checking languages loaded

// es-ES is loaded from the BE using this plugin
console.log(i18n.getResourceBundle('es-ES')) // returns {common: {โ€ฆ}, content: {โ€ฆ}}
// EN is a static resource hosted in the codebase.
console.log(i18n.getResourceBundle('en')) // returns {date: "Today's date is: {{date, LL}}", english: "English", german: "German", spanish: "Spanish", ย โ€ฆ}

Expected behavior

Returning an object with objects inside should be mapped as namespaces.

i18n setup

  i18n
    .use(LanguageDetector)
    .use(HttpApi)
    .use(initReactI18next)
    .init({
      lng: 'en',
      interpolation: {
        escapeValue: false
      },
      react: {
        useSuspense: false
      },
      defaultNs: 'common',
      ns: ['common', 'content'],
      partialBundledLanguages: true,
      nonExplicitSupportedLngs: true,
      backend: {
        loadPath: `/api/project/1/Version/1/Language/{{lng}}`,
        customHeaders: {
          'Content-Type': 'application/json; charset=utf-8',
        },
        parse: (data) => {
          const json = JSON.parse(data) // returns [es-ES: {common: {โ€ฆ}, content: {โ€ฆ}}]
          return json[0].fileContent['es-ES']
        }
      }
    })

checking languages loaded

// es-ES is loaded from the BE using this plugin
console.log(i18n.getResourceBundle('es-ES')) // returns {date: "La fecha de hoy es: {{date, LL}}", english: "Inglรฉs", german: "Alemรกn", spanish: "Espaรฑol", ย โ€ฆ}
// EN is a static resource hosted in the codebase.
console.log(i18n.getResourceBundle('en')) // returns {date: "Today's date is: {{date, LL}}", english: "English", german: "German", spanish: "Spanish", ย โ€ฆ}

Your Environment

  • react: 16.0.0
  • i18next version: 19.3.2
  • os: Mac
  • i18next-http-backend: 1.1.0

Extra Notes

I also attempted to use this plugin in combination with i18next-multiload-backend-adapter to solve this issue, but that just seemed to cause issues of its own.

I can also get ONE namespace to work if I access the namespace directly in the JSON object in the parse function. Example:

        parse: (data) => {
          const json = JSON.parse(data) // returns [es-ES: {common: {โ€ฆ}, content: {โ€ฆ}}]
          return json[0].fileContent['es-ES'].common // common namespace will work now! But I lose content....
        }

translation fetched but cannot be loaded

๐Ÿ› Bug Report

json translation files appear to be fetched, but i18n cannot load the data.
It appears that the data is not correctly downloaded from Google cloud storage bucket, but no error is returned and the fetch appears to be successful. Not sure how to debug that one.
I noticed that my fetches have a sec-fetch-dest: empty request header, but according to the MDN doc, the header seems to be set automatically by the browser and it's unclear if some requestOptions could fix the issue.

To Reproduce

Here's my config:

i18n.ts

import Backend from "i18next-chained-backend";
import HttpApi from "i18next-http-backend"; // fallback http load
import LanguageDetector from "i18next-browser-languagedetector";
import LocalStorageBackend from "i18next-localstorage-backend"; // primary use cache
import i18n from "i18next";
import { initReactI18next } from "react-i18next";

import environment from "./environment";

const { debug, localesPath } = environment;

const backendOptions = [
  {
    debug,
    // prefix for stored languages
    prefix: "i18next_res_",
    // expiration
    expirationTime: 7 * 24 * 60 * 60 * 1000,
    // Version applied to all languages, can be overriden using the option `versions`
    defaultVersion: "",
    // language versions
    versions: {},
    // can be either window.localStorage or window.sessionStorage. Default: window.localStorage
    store: window.localStorage,
    /* below options */
  },
  {
    loadPath: `${localesPath}{{lng}}/{{ns}}.json`, // xhr load path for my own fallback
    // allow cross domain requests
    crossDomain: false,
    requestOptions: {
      // used for fetch, can also be a function (payload) => ({ method: 'GET' })
      mode: "no-cors", // Same as webpack files, request header 'sec-fetch-mode: no-cors'
      credentials: "same-origin",
      cache: "default",
    },
  },
];

i18n
  // load translations from http
  .use(Backend)
  // detect user language
  .use(LanguageDetector)
  // pass i18n down to react-i18next
  .use(initReactI18next)
  .init({
    debug: true,
    backend: {
      backends: [
        LocalStorageBackend, // primary
        HttpApi, // fallback
      ],
      backendOptions,
    },
    ns: ["common", "translation", "glossary", "auth"], // NOTE: keep synced with i18nextWebpackPlugin options
    defaultNS: "common",
    supportedLngs: ["en", "ja"],
    fallbackLng: "ja",

    keySeparator: ".", // we use keys in form messages.welcome

    interpolation: {
      escapeValue: false, // react already safes from xss
    },
  });

if (process.env.NODE_ENV !== "production") {
  const { applyClientHMR } = require("i18next-hmr"); // eslint-disable-line
  applyClientHMR(i18n);
}

export default i18n;

SwitchLanguage.tsx

import i18n from "i18next";
import React, { SyntheticEvent, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";

const SwitchLanguage: React.FC = () => {
  const [lang, setLang] = useState("ja");
  const { t } = useTranslation();
  const isFirstRender = useRef(true);

  useEffect(() => {
    // Skip the effect on the first render
    if (isFirstRender.current) {
      isFirstRender.current = false;
    } else {
      i18n.changeLanguage(lang);
    }
  }, [lang]);

  const handleChange = ({ target }: SyntheticEvent): void =>
    setLang((target as HTMLSelectElement).value);

  return (
    <select onChange={handleChange} value={lang}>
      <option value="ja">{t("lang.ja")}</option>
      <option value="en">{t("lang.en")}</option>
    </select>
  );
};

export default SwitchLanguage;

Checking from the console Network tab, the files are fetched with status code 200

i18next backend connector fails to load the data:

Keys are shown instead of translations in the page:

The data can not be examined from the browser console:

Here a screenshots of request headers sent:

Accessing the file directly works fine

Expected behavior

I should be able to see the json code from the browser console and i18n should load the data correctly.
Works in the dev environment with webpack devServer


Your Environment

  • runtime version: Any browser
  • i18next version: i.e. 19.5.4
  • os: Linux

Fetch won't return data

For some strange reason, using the default fetch won't return any data from the S3 bucket where my translation files are. Here are my configs:

{
        lng: getLocales()[0].languageCode,
        fallbackLng: 'en',
        debug: false,
        interpolation: {escapeValue: false},
        keySeparator: false,
        nsSeparator: false,
        load: 'currentOnly',
        defaultNS: 'main',
        ns: 'main',
        backend: {
            crossDomain: true,
            loadPath:
                'https:/bucket.s3.eu-west-2.amazonaws.com/app/{{lng}}/{{ns}}.json',
        },
    }

The request returns a 200, but with no data:

image

image

This is a problem with fetch, I'm not sure why. Then I moved to axios:

{
        lng: getLocales()[0].languageCode,
        fallbackLng: 'en',
        debug: false,
        interpolation: {escapeValue: false},
        keySeparator: false,
        nsSeparator: false,
        load: 'currentOnly',
        defaultNS: 'main',
        ns: 'main',
        backend: {
            crossDomain: true,
            async loadPath(lng, ns) {
                const translations = await axios.get(
                    `https://bucket.s3.eu-west-2.amazonaws.com/app/${lng[0]}/${ns[0]}.json`,
                );
                return translations.data;
            },
        },
    }

This works fine, but it tries to make a strange request as well:

image

How should I configure the backend to use axios?

If I set the language is 'ko-KR', there will have two request 'ko' and 'ko-KR'

๐Ÿ› Bug Report

A clear and concise description of what the bug is.

To Reproduce

A codesandbox example or similar
or at least steps to reproduce the behavior:

import i18n from 'i18next';
import Backend from 'i18next-http-backend';

i18n
  .use(Backend)
  .init({
    fallbackLng: 'ko-KR',
    preload: ['ko-KR'],
    backend: {
      loadPath: `http://localhost/api/translation-resource/{{lng}}`,
    },
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
  });

image

Expected behavior

A clear and concise description of what you expected to happen.

only one request, or Is this normal ?

Your Environment

  • runtime version: i.e. chrome
  • i18next version: i.e. 20.1.0
  • os: Windows
  • any other relevant information

BackendOptions TypeScript interface missing reloadInterval

๐Ÿ› Bug Report

BackendOptions TypeScript interface is missing reloadInterval (at a minimum).

https://github.com/i18next/i18next-http-backend/blob/master/index.d.ts#L7

To Reproduce

const httpApiOptions: BackendOptions = {
  loadPath: '/locales/{{lng}}/{{ns}}.json',
  allowMultiLoading: false,
  crossDomain: false,
  reloadInterval: false,  // flagged by TypeScript
};

Expected behavior

No TypeScript errors.

Your Environment

  • runtime version: TypeScript 4.2.3 in VSCode
  • i18next version: 20.0.0
  • os: Mac

Add option to retry requests

๐Ÿš€ Feature Proposal

Add option to retry requests in case of error.

Motivation

Currently I'm working on an app that has all translations inside the bundle. To optimize this and reduce bundle size, we are moving translations to json files. I have found that a lot of users can get network errors, and I have not been able to find any api to re-do requests if some fails because of a network error, also this plugin is not doing any retry. This is a big real world problem in production environment for apps that receive thousands of visits every day, so the proposal is to add configuration api to allow retries.

Sorry if this feature is already available, then we can change this issue description to update docs.

Example

Proposal

{
  retry: {
      attempts: 5,
      interval: 500 /*in ms*/
  }
}

Incorrect typings for RequestResponse

I'm using i18next-http-backend with typescript. I use the request function to load the message resources from a backend and receive the messages as JSON-Object. So if I want to call the RequestCallback I have to convert the JSON-Object to an JSON String via JSON#stringify to fullfill the typing of RequestCallback because the second param is of type RequestResponse. And there data should be of type string.

If I look to the code (https://github.com/i18next/i18next-http-backend/blob/master/lib/index.js#L72) I see that data doen't have to be of type string.

So is it possible to adjust the typing to something like this:

interface RequestResponse {
  status: number,
  data: string | Record<string, unknown>
}

Or why only a string is supported ?

Documentation regarding API interface for backend `request` option is inaccurate

๐Ÿ› Bug Report

I had to step through the invocation of callback from my application code to figure out that data property indicated within the res object should be stringified JSON rather than an Object instance. It also is worth noting that there was no indication of error in this case until attempting to look up the translation for a key (e.g. "i18next::translator: missingKey ...").

Perhaps it would help to provide an example of overriding request in the examples/ within the repository.

The documentation in question (from the README):

  // define a custom request function
  // can be used to support XDomainRequest in IE 8 and 9
  //
  // 'options' will be this entire options object
  // 'url' will be passed the value of 'loadPath'
  // 'payload' will be a key:value object used when saving missing translations
  // 'callback' is a function that takes two parameters, 'err' and 'res'.
  //            'err' should be an error
  //            'res' should be an object with a 'status' property and a 'data' property the key:value translation pairs for the
  //            requested language and namespace, or null in case of an error.
  request: function (options, url, payload, callback) {},

To Reproduce

Override request option with something like the following:

request: async (
  _options: object,
  _url: string,
  _payload: object,
  callback: (err?: object, res?: object) => never,
 ): Promise<never> => {
    const en = await import('./locales/en.json');
    callback(undefined, { data: en.default });
    // callback(undefined, { data: JSON.stringify(en.default) }); // "Correct" code
  };
}

Expected behavior

Bare minimum: update the documentation on the README:

  // define a custom request function
  // can be used to support XDomainRequest in IE 8 and 9
  //
  // 'options' will be this entire options object
  // 'url' will be passed the value of 'loadPath'
  // 'payload' will be a key:value object used when saving missing translations
  // 'callback' is a function that takes two parameters, 'err' and 'res'.
  //            'err' should be an error
  //            'res' should be an object with a 'status' property and a 'data' property
  //            containing a stringified object instance containing the key:value translation pairs for the
  //            requested language and namespace, or null in case of an error.
  request: function (options, url, payload, callback) {},

Stretch goal:
Also ensure an error is thrown and/or logged when parsing fails via the default behavior (with only request overriden). I don't believe this callback invocation results in consuming application code from being notified of an issue (unless I have something misconfigured).

if (parseErr) return callback(parseErr, false)

i18next::backendConnector error Only absolute URLs are supported (SSR)

๐Ÿ› Bug Report

Hi there, does anyone know what i'm doing wrong with backend config? seems to me this error is related to i18next-http-backend
Build fails with error i18next::backendConnector: loading namespace translations for language en failed TypeError: Only absolute URLs are supported

tried different backend libs with no success, this error appears only with i18next-http-backend

Screenshot 2021-03-13 at 13 48 21

To Reproduce

**i18n.js**
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    backend: { loadPath: `/locales/{{lng}}/{{ns}}.json` },
    fallbackLng: 'en',

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

    debug: true,
    initImmediate: false,

    react: {
      wait: true
    }
  });

export default i18n;


**gatsby-ssr.js**

import React from 'react';
import { I18nextProvider } from 'react-i18next';

import { GlobalState } from './src/components/GlobalState/GlobalState';
import i18n from './src/utils/i18n';

import 'slick-carousel/slick/slick.css';

export const wrapRootElement = ({ element }) => (
  <I18nextProvider i18n={i18n}>{element}</I18nextProvider>
);

export const wrapPageElement = ({ element, props }) => (
  <GlobalState {...props}>{element}</GlobalState>
);

export const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString }) => {
  i18n.loadNamespaces(['translations'], () => {
    replaceBodyHTMLString(bodyComponent);
  });
};


**gatsby-node.js**

exports.onPostBuild = () => {
  fs.copySync(`./src/locales`, `./public/locales`);
};

Expected behavior

Build project without errors

Your Environment

  • runtime version: i.e. node v14.16.0, gatsby v2.24.91, ssr
  • i18next version: 19.8.3
  • os: Mac M1 chip, Big Sur 11.2.3

invalid JSON file notification

Hi again,

I lost 2 hours because of a missing coma in my json files (server side)

I suggest to add something (at least a message in the console) or something to handle bad json files ;)

Cheers

Language interpolation in customHeader

๐Ÿš€ Feature Proposal

Language interpolation in customHeader.

Motivation

For a project, weโ€™d like to fetch our translations from our backend. To indicate which translation for which backend should be requested, weโ€™d like to use the โ€œAccept-Languageโ€-Header instead provide the information in the URL itself.

Example

We thought about something like:

...
customHeaders: {
    โ€œAccept-Languageโ€: โ€œ{{lng}}โ€
}
...

in the backend config. Is that possible already and Iโ€™m just incapable of making it work?

Thank you very much for your help!

Firefox 34 Syntax Error

๐Ÿ› Bug Report

Firefox 34 got syntax error, the file compiled by Babel are not use older syntax.

SyntaxError: missing ; before statement

Compiled file:

const arr = [];
const each = arr.forEach;
const slice = arr.slice;

function defaults(obj) {
  each.call(slice.call(arguments, 1), source => {
    if (source) {
      for (var prop in source) {
        if (obj[prop] === undefined) obj[prop] = source[prop];
      }
    }
  });
  return obj;
}

To Reproduce

  1. Use i18next-xhr-backend for the first time with i18n.js
import i18n from 'i18next';
import Backend from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init();
  1. Open Firefox 34, the website is fine
  2. Change to use i18next-http-backend
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init();
  1. Open Firefox 34, got syntax error

Expected behavior

Use both i18next-xhr-backend and i18next-http-backend should be work fine.

Your Environment

  • runtime version: Firefox 34
  • i18next version: ^19.0.1
  • os: Mac
  • package.json
{
  "dependencies": {
    "@babel/runtime": "^7.7.6",
    "core-js": "^3.6.4",
    "i18next": "^19.0.1",
    "i18next-browser-languagedetector": "^4.0.1",
    "i18next-chained-backend": "^2.0.0",
    "i18next-http-backend": "^1.0.15",
    "i18next-localstorage-backend": "^3.0.0",
    "i18next-xhr-backend": "^3.2.2",
    "react": "^16.12.0",
    "react-i18next": "^11.2.5"
  },
  "devDependencies": {
    "@babel/core": "^7.7.4",
    "@babel/plugin-transform-runtime": "^7.7.6",
    "@babel/preset-env": "^7.7.4",
    "@babel/preset-react": "^7.7.4",
    "babel-loader": "^8.0.6",
    "babel-plugin-module-resolver": "^3.2.0"
  }
}
  • .babelrc
{
  "presets": [
    ["@babel/preset-env", { "useBuiltIns": "entry", "corejs": "3" }],
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}
  • .browserslistrc
Firefox 34

Using from own lib

๐Ÿ› Bug Report

I would like to use I18next from my own library but loadPath searching in main app (where I init-ed I18Next first time).
In my lib I would like to add a new namespace and some translation files. How can I trigger my lib dir?

To Reproduce

My lib ts:

ngOnInit(): void {
    i18next.init({
      ns: 'dialog',
      backend: {
        loadPath: '/assets/locales/{{ns}}-{{lng}}.json'
      }
    });
  }

Expected behavior

zone-evergreen.js:1068 GET http://localhost:4200/assets/locales/dialog-hu.json 404 (Not Found)

Your Environment

  • runtime version: i.e. node v14, deno, browser xy
  • i18next version: i.e. 19.5.3
  • os: Mac, Windows, Linux
  • any other relevant information

No error is thrown if the translation file download fails

๐Ÿ› Bug Report

I installed this library to download the translation files as needed (lazy loading), inside my project. If the file is not in the server or the server is offline for some reason, I want to get whatever error happened and show it nicely to the user.

The main problem happens when the file is not in the server or the server is offline for some reason. This library simply throw some warnings on the console.log and change the language anyway (even if the file was not downloaded), like so:

image

As a default behavior, I'd expect the i18n.changeLanguage Promise to throw an error so I can treat it inside a try/catch block function. Or maybe an extra configuration key called "ifFileNotDownloadedChangeLanguageAnyway" so we can prevent the language to be changed if the download fails.

To Reproduce

The installation:

npx create-react-app pig --template typescript
cd pig

Then install the libraries:

npm i react-i18next i18next i18next-http-backend @material-ui/core

My i18n.ts file:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from 'i18next-http-backend'

i18n
  .use(initReactI18next)
  .use(Backend)
  .init({
    backend:{
      loadPath: '/translations/{{lng}}.json'
    },
    react:{useSuspense:false},
    debug: true,
    lng: "en",
    keySeparator: false,
    interpolation: {
      escapeValue: false
    }
  });

  export default i18n;

My index.tsx:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import './i18n';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

reportWebVitals();

My huge App.tsx:

import { Backdrop, CircularProgress, Button } from "@material-ui/core"
import { makeStyles } from '@material-ui/core/styles';
import './App.css';
import { useTranslation } from 'react-i18next';
import { useState } from "react"

const useStyles = makeStyles((theme) => ({
  backdrop: {
    zIndex: theme.zIndex.drawer + 1,
    color: '#fff',
  },
}));

function App() {
  const [lang, setLang] = useState("en");
  const [block, setBlock] = useState(false);
  const { t, i18n, ready } = useTranslation();
  const handle = async () => {
    try{
      setBlock(true)
      const newLang = lang==="en"?"pt":"en"
      await i18n.changeLanguage(newLang)
      setLang(newLang)
      setBlock(false)
    }catch(e){
      console.log("this is never called", e)
    }
  }
  const classes = useStyles();
  return (
    <div className="App">
      <Backdrop className={classes.backdrop} open={block || !ready}>
        <CircularProgress/>
      </Backdrop>
      {ready && (
        <>
          <h1 onClick={handle}>{t("pig")}</h1>
          <Button onClick={handle} >Click me</Button>
        </>
      )}
      
    </div>
  );
}

export default App;

My folder structure/translation files:

image

Then you can simply exclude the pt.json file or block the requests to this file in the chrome dev tools > Network tab and click the "Click me" button.

My Environment

  • runtime version: node v10.24.0, npm v6.14.11,
  • i18next version: ^20.2.1
  • os: Windows

Types are out of date with [email protected]

๐Ÿ› Bug Report

[email protected] includes type definition changes which cause TypeScript errors when used with i18next-http-backend.

To Reproduce

import i18next from 'i18next';
import HttpApi from 'i18next-http-backend';

i18next.use(HttpApi).init(i18nextOptions);
node_modules/i18next-http-backend/index.d.ts:69:3 - error TS2416: Property 'readMulti' in type 'I18NextHttpBackend' is not assignable to the same property in base type 'BackendModule<BackendOptions>'.
  Type '(languages: string[], namespaces: string[], callback: ReadCallback) => void' is not assignable to type '(languages: string[], namespaces: string[], callback: MultiReadCallback) => void'.
    Types of parameters 'callback' and 'callback' are incompatible.
      Types of parameters 'data' and 'data' are incompatible.
        Type 'boolean | ResourceKey | null | undefined' is not assignable to type 'Resource | null | undefined'.
          Type 'string' is not assignable to type 'Resource | null | undefined'.

69   readMulti(languages: string[], namespaces: string[], callback: ReadCallback): void;
     ~~~~~~~~~

Expected behavior

No TypeScript errors when importing i18next-http-backend.

Your Environment

  • runtime version: node v14, typescript v4
  • i18next version: 19.9.0
  • os: Mac

Allow addPath to be a function

๐Ÿš€ Feature Proposal

This would allow to provide a function for addPath, just as it is done from loadPath.

const addPath = (lng, ns) => {
  // Decide what path to return according to ns/lng
}

Motivation

This would basically account for a certain use case, using different paths based on namespace and language to post the missing resources, we have that ability when getting resources using loadPath as a function, but the same is not extended to the addPath property.

Example

We use the namespace to fetch data from two different backends, which we can perfectly do using loadPath as a function:

const loadPath = (lngs, namespaces) => {
  if (namespaces[0] === "ns1") {
    return `/first/path/${lngs[0]}/${namespaces[0]}`
  }
  return `/second/path/${lngs[0]}/${namespaces[0]}`
}

We would also need to post missing resources to those different backends, something like:

const addPath = (lng, namespace) => {
  if (namespace === "ns1") {
    return `/first/path/add/${lng}/${namespace}`
  }
  return `/second/path/add/${lng}/${namespace}`
}

Missing type for requestOptions property in BackendOptions

๐Ÿ› Bug Report

The requestOptions property in BackendOptions cannot be used in ts files due to its type missing in index.d.ts

To Reproduce

    type RequestType = BackendOptions['request'];
    const getTranslations: RequestType = (
      options,
      url,
      payload,
      callback,
    ) => {
      const {requestOptions} = options; // TypeScript error: Property 'requestOptions' does not exist on type 'BackendOptions'.
    };

Expected behavior

No error

Your Environment

  • runtime version: VS Code 1.56.1, TypeScript 3.8.3
  • i18next-http-backend version: 1.2.4
  • os: Mac

Fallback to node-fetch in browser

๐Ÿ› Bug Report

When 'fetch' is not available in the browser the request fallback to node-fetch instead of sending an "XMLHttpRequest" or "ActiveXObject" request.

//Code snippet from `lib/request.js`
if (fetchApi) {
    // use fetch api
    return requestWithFetch(options, url, payload, callback)
}

if (typeof XMLHttpRequest === 'function' || typeof ActiveXObject === 'function') {
    // use xml http request
    return requestWithXmlHttpRequest(options, url, payload, callback)
}

This causes an issue as node-fetch sets its own user-agent

if (!headers.has('user-agent')) {
 headers.set('user-agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
}

and the issue is: Refused to set unsafe header "user-agent"

To Reproduce

Just disable fetch.
Personally I face this issue meanwhile running Cypress test which currently doesn't support fetch.

Expected behavior

I expect the node-fetch fallback only happens when we are on node/deno environment but not in the browser.

Your Environment

Cypress test environment

Extra HTTP requests

๐Ÿ› Bug Report

After loading the web application, an http request goes out to get localization data from *loadPath.
If you change the current language, then the corresponding files will come from the backend - this behavior is quite logical,because I only get files that will use.

Folders structure

- public/
--- locales/
----- ru
------- translation.json
------- filters.json
----- ua
------- translation.json
------- filters.json

correct

In i18n options I also use LanguageDetector, which helps me to save the current state of the language in local storage (init file below)

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    initImmediate: false,
    fallbackLng: 'ru',
    debug: isDebugMode,
    ns: ['filters'],
    defaultNS: 'translation',
    interpolation: {
      escapeValue: false,
      formatSeparator: ','
    },
    react: {
      wait: true,
      useSuspense: true
    },
    backend: {
      loadPath: './locales/{{lng}}/{{ns}}.json' // * where i store my locales
    },
    detection: {
      order: ['localStorage'],
      caches: ['localStorage', 'cookie'],
      cookieMinutes: 80
    }
  })

Further, after switching the language and reloading the page, I will receive a json that corresponds to the current language. But there will also be extra requests to get localization files that should not be used

wrong
*red dots mark unnecessary requests

To Reproduce

In other words.
I ran the page for the first time and got the files corresponding to 'fallbackLng' - from locales/ru/*
Then I changed the language to ua, the changes were saved in localStorage, and after reboot, I hope to only get the files from locales/ua/*.
But in the end I get files from both directories

Expected behavior

After changing the language and reloading the page, I want to receive those files that correspond to the current language, avoiding unnecessary requests

Your Environment

  • runtime version: browser -> react-i18next v11.3.4
  • i18next version: 19.3.4
  • os: Windows / Mac

Inconsistency between how i18next-http-backend and passing resources directly to init works

๐Ÿ› Bug Report

Given the json file:

{
  "ns": {
     "example": "Example"
  }
}

You can access example via t like so: t("ns:example") by passing in resources option to init.

When using i18next-http-backend to load the resource, i.e. loadPath: "/translations/{{lng}}.json", it seems the namespace is lost and you must access it via dotty notation t("ns.example").

To Reproduce

codesandbox

Expected behavior

Accessing keys should be consistent between the two packages.

Your Environment

Chrome latest
"i18next": "^19.6.3",
"i18next-http-backend": "^1.0.18",
MacOS

loadPath from a specific folder :

Hi,
I'm using i18next-http-backend to initialise my languages and i would like to know if the parameter 'loadPAth 'can point to a folder is in the 'src' sub-directory like the exemple :
loadPath: 'src/languages/{{lng}}/{{ns}}.json'

Think you for your help.

Build fails with Vite and other rollup-based bundlers

๐Ÿ› Bug Report

Vite is a blazingly fast bundler and is being increasingly used amongst devs. I've recently gotten the product team at our company to use it more because webpack-dev-server can get extremely slow for large projects.

In production mode, Vite uses rollup internally to bundle code. The problem is, it seems that this library specifically is causing builds to fail with runtime errors on initial load.

To save you some time, I've already done a little bit of research and figured out the root of this problem. node-fetch is somehow being bundled into the browser in rollup. Thus the runtime errors are from trying to access non-existent node libs in the browser.

To Reproduce

Pull from this test repo: https://github.com/diracs-delta/i18next-http-backend-rollup-demo

And then run

yarn
yarn vite build && yarn vite preview

and navigate to localhost:5000.

Expected behavior

i18next plugins should be compatible with the latest bundlers and not sometimes import node-fetch on accident.

Your Environment

  • runtime version: node 15, any
  • i18next version: see package.json
  • os: Mac

options not merged ?

Hi,

In a browser context, seem's that options are not taken.

        var boptions = {
            queryStringParams: { v: '1.2.3' },
        };

        var backend = new i18nextHttpBackend(boptions);

You can see in the network console that the query string is never passed.

After some investigation i found that.

Actual (not working, chrome browser):

      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

Expected (working)

      var options = arguments.length > 1 && arguments[0] !== undefined ? arguments[0] : {};

Cheers

Backend loadPath problem

Goodmorning,

i have a little problem when i try to load translation from my api.
Code:

import i18n from 'i18next'
import XHR  from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next'
i18n.use(XHR)
    .use(initReactI18next)
    .init({
        backend: {
            loadPath         : 'urlexample/{{lng}}/{{ns}}/',
            allowMultiLoading: false,
            crossDomain      : false
        },
          lng: locale.split("-")[0],
          ns: locale.split("-")[1],
          defaultNS: locale.split("-")[1],
          react: {
              useSuspense: false
          },
        fallbackLng: false,
        debug: true,
        react: {
            wait: true
        }
    }, (error, t) => {
        if(error)
            console.error(error);
    });

Backend i18next log is this:

Backendconn

And the translation doesn't work.

what is my problem ?

Thanks in advance.

P.S.

When i use static hardcoded json like this work well:

import i18n from 'i18next'
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector'

import itit from './translations/it-it.json'

i18n
                    .use(Backend)
                    .use(LanguageDetector)
                    .use(initReactI18next)
                    .init({
                        lng: locale.split("-")[0],
                        react: {
                             useSuspense: false
                        }
                        fallbackLng: 'en',
                        debug: true,
                        resources: { en, itit, eses, ruru, roro, vivn, envn, zaza, enza, frfr, huhu, plpl, uaua, enid, idid, usus, enus, nlnl, ensg, elgr, frma },
                        interpolation: {
                            escapeValue: false,
                        },
                    })
            },
            (error) => {
                console.log(error, "i18n error loading");
            });

Good

Usage with Webpack and Typescript importing cjs instead of esm

๐Ÿ› Bug Report

A project setup with Typescript (targeting es5) and Webpack seems to be causing i18next-http-backend to be loaded via require which is configured to load the library from the cjs distribution.

If I chage my import to import i18nextHttpBackend from "i18next-http-backend/esm"; it works as expected, but then TypeScript cannot find the type declaration.

To Reproduce

Sandbox: https://codesandbox.io/s/re1sn
(run npm start if not already running)

https://re1sn.sse.codesandbox.io/
(should show something like defaultBackend is undefined and esmBackend is function)

Expected behavior

import i18nextHttpBackend from "i18next-http-backend"; to result in i18nextHttpBackend !== undefined.

Your Environment

  • runtime version: node v14
  • i18next version: 20.1.0
  • i18next-http-backend version: 1.2.1

Does this library support python as backend ?

I know this is not a place for questions but I am really confused about what this library really does and I can't understand even after going through the documentation. I am not using nodejs in the backend. Do I still need it ?

Cannot get translation from external public URL

๐Ÿ› Bug Report

Hi. I'm trying to load translation file from public URL but I get (failed)net::ERR_FAILED. URL is correct and I can open it from my browser.

Screenshot 2020-10-06 at 16 21 39

To Reproduce

Here is my config file:

{
    debug: true,
    lng: 'en',
    fallbackLng: 'en',
    defaultNS: 'common',
    interpolation: {
      escapeValue: false,
      format: (value, format: string | undefined) => {
        if (format === 'uppercase') {
          return value.toUpperCase();
        }
        return value;
      },
    },
    backend: {
      crossDomain: true,
      loadPath() {
        return '<S3_BUCKET>.eu-central-1.amazonaws.com/{{lng}}/{{ns}}.json';
      },
    },
  }

Expected behavior

Should load translation file

Your Environment

  • runtime version: i.e. node v12.18.4
  • i18next version: i.e. 19.7.0
  • os: Mac

i18next do not complain when wrong `loadPath` is given

๐Ÿ› Bug Report

loadPath seem not to be taken into account, and whether the path is valid or not the behavior is the same.

To Reproduce

Run:

const i18next = require('i18next')
const backend = require('i18next-fs-backend')

const options = {
    lng: 'en',
    fallbackLng: 'en',
    loadPath: '/path/is/wrong/translation.json',
}

i18next.use(backend).init(options)

// sleep time expects milliseconds
function sleep (time : number) {
    return new Promise((resolve) => setTimeout(resolve, time));
}

sleep(300).then(() => {
    console.log(i18next.languages)
    console.log(i18next.language)
    console.log(i18next.dir())
    console.log(i18next.exists('mykey.mymessage'))
});

The code executes well and returns exit code 0, output:

[ 'en' ]
en
ltr
false

Additionally, setting loadPath to the real path, e.g. locales/en/translation.json, still compiles well but the output is the same, even if my key mykey.mymessage does exist in my translation.json file:

{
  "mykey": {
    "mymessage": "hehe",
    "description": "haha"
  }
}

Then, this is very hard to debug because you don't know whether the problem is that your json file is not loaded, of if it is something else.

Expected behavior

I would expect having an error because the path I give is wrong.

Your Environment

Lack of support for file schema prevents use in cordova app

๐Ÿ› Bug Report

When used in a cordova app with a local translation file the url is something like file:///android_asset/www/locale/es-US.json The fetch API does not support the file schema.

To Reproduce

Load a locale json file from a file:// url.

The following output is logged: Fetch API cannot load file:///android_asset/www/locale/es-US.json. URL scheme "file" is not supported

Expected behavior

The json loads

Your Environment

"Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4) Plus Build/NDRS26.58-33-9-16; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/84.0.4147.125 Mobile Safari/537.36"

Within a cordova app (android webview)

Custom request function for missing translations

๐Ÿš€ Feature Proposal

There is a possibility to define a custom request function for getting the translations but looks like there is no such param for adding a missing translation. A feature would define a custom add missing functions that would be called when a missing translation found, just like a custom request,

Motivation

My project uses typed api helpers generated from open API spec, so it makes sense to use API methods provided rather than configuring request options by hand.

Example

request: (
    payload,
    callback: RequestCallback,
  ) :void => {
    const {lng, ns, key} = payload;
    const params: addMissingParameters = {
      lng,
      ns,
      key,
    };
    addMissing(params).then(({ status, data }) => {
      const response: RequestResponse = {
        status,
        data: JSON.stringify(data?.[0]?.namespaces?.[0]?.values),
      };
      callback(null, response);
    }).catch((error: AxiosError) => callback(error, { status: error?.response?.status || 500, data: '' }));
  },

`create` function crashes if not provided with a callback

๐Ÿ› Bug Report

With the resolution of #45 a bug was introduced when posting missing resources, due to the fact that the create function may be called without a callback and that would cause the backend to crash.

To Reproduce

  1. Call create without a callback (backend.create(languages, namespace, key, fallbackValue))
import i18next from 'i18next'
const backend = new Http(
  {
    interpolator: i18next.services.interpolator
  }
)
// Produces "callback is not a function" error
backend.create('en', 'test', 'key', 'value')

Expected behavior

The create function should work correctly without a callback.

Your Environment

  • runtime version: i.e. node v14.15.1, Firefox v85.0
  • i18next version: i.e. 19.8.4
  • os: Linux (WSL)

Module parse failed: Unexpected token in request.js

Babel 7.9.0 fails to compile request.js.

This is the complete error that is given by npm start

Failed to compile.

./node_modules/i18next-http-backend/lib/request.js
Module parse failed: Unexpected token (47:20)
You may need an appropriate loader to handle this file type.
|     url = addQueryString(url, options.queryStringParams)
|   }
|   const headers = { ...(options.customHeaders || {}) }
|   if (payload) headers['Content-Type'] = 'application/json'
|   fetchApi(url, {

Line 47 is const headers = { ...(options.customHeaders || {}) }.

SyntaxError in Internet Explorer 11 (IE11)

๐Ÿ› Bug Report

Using the package in Internet Explorer throws a SyntaxError due to the use of non-transpiled ES6 syntax in the code.

To Reproduce

Use the package in a IE11 context.

Expected behavior

I know there is debate on whether dependencies should be shipped already transpiled or not, but even other packages of this project (i.e. i18next, react-i18next) are transpiled, so it would make sense for this one to be as well.

Temporary solution

For those facing the same issue, I modified my Webpack config and made babel-loader "un-ignore" the package like this:

{
  test: /\.js$/,
  use: 'babel-loader',
  exclude: /node_modules\/(?!i18next-http-backend)/
}

Issue when loading our common translation files

Hello,

I am reaching out because we are using your library along with i18next and react-i18next. Recently there was a string check added to your codebase that stopped our common translation files from getting loaded.

if (typeof res.data === 'string') {

The way I am reading the code is if a response comes back that is not a string it will stop there and not attempt to process any additional languages or namespaces. Because of the way our environment is set up, we sometimes receive HTML responses when a file is not found or in the case of a 403. I was able to validate this issue locally by building and linking.

Is it possible for the string check to be removed or add some kind of escape hatch for unique scenarios like ours?

To solve for the issue in the meantime, I rolled back our package file to 1.0.15 but would love to be able to use your latest releases moving forward. Any help or guidance would be appreciated.

Reload interval set to false not working

๐Ÿ› Bug Report

Running on server side, I have an init configuration that, when reading an environment variable that it is "true" allows me to load translations from and endpoint, and when the envvar is false, I add them from a js object. In this case, I would like to avoid reloading as well.
Currently I am passing down that option but it is ignored, reload happens every hour. When setting debug: true, the i18next: initialized event shows that reloadInterval is indeed false.

To Reproduce

Here's the portion of init config that enables this behavior, other parameters are not listed to simplify reading.
CONFIG.ENABLE_TRANSLATIONS is the env var that I mentioned, which is set to false,
so reloadInterval is false and request throws an error.

 backend: {
reloadInterval: CONFIG.ENABLE_TRANSLATIONS && CONFIG.TRANSLATIONS_SERVER_RELOAD_INTERVAL,
    loadPath: (lngs, namespace) =>
      `${CONFIG.API_BASE_URL}${API_ROUTES.LOCALIZATION}/${lngs}_${namespace}`,
    request: async function(options, url, payload, callback) {
      try {
        if (!CONFIG.ENABLE_TRANSLATIONS)
          throw new Error('Backoffice translations disabled  - Loading static keys')

        //REQUEST TO BACKEND
        return callback(null, {
          status: result.status,
          data: result.data && result.data.translations
        })
      } catch (error) {
        return callback(
          {
            status: error.response && error.response.status,
            data: (error.response && error.response.data) || error.message
          },
          null
        )
      }
    }
  }

Here's is the portion where I load the js object in case of error or envvar being disabled:

i18n
  .use(Backend)
  .use(i18nextMiddleware.LanguageDetector)
  .use(i18nCustomLogger)
  .init(serverOptions, i18nError => {

    if (i18nError) {
      logError('I18n Backend Connector error - Loading static keys', '', i18nError)
      i18n.addResourceBundle(LANGUAGE_TAGS.DEFAULT, NAMESPACES.COMMON, staticKeys)
    }
// some other code

Expected behavior

I would like to avoid reloading when I set reloadInterval to false.

Your Environment

  • runtime version: node v14
  • i18next version: 19.5.1
  • os: Mac

Format of backend response

I'm trying to use the backend to fetch from a backend I don't control. The endpoint contains the translations of ALL languages. I'm trying to use the parse callback to translate the data returned from the backend into something this plugin understands.

What is the expected format of the return value of parse?

Can not use i18next-http-backend for Node 13.6.0

When I use it with Node 13.6.0.

My code like this:

const httpApi = require('i18next-http-backend');

Here is error:

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /Users/thaunguyen/Documents/Projects/front-end/node_modules/i18next-http-backend/lib/index.js require() of ES modules is not supported. require() of /Users/thaunguyen/Documents/Projects/front-end/node_modules/i18next-http-backend/lib/index.js from /Users/thaunguyen/Documents/Projects/front-end/server/i18n.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules. Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /Users/thaunguyen/Documents/Projects/front-end/node_modules/i18next-http-backend/package.json.

But it works fine with node 12.x.x. Can I use require on Node 13.x.x?

Parse option not working

๐Ÿ› Bug Report

I'm not sure if I understand exactly the parse option. I suppose is a way "modify" the json file with the translations before loading it.
I have a JSON like this:

{
    "yes": {
        "message": "Si",
        "description": "General Yes message to be used across the application"
    },
    "no": {
        "message": "No",
        "description": "General No message to be used across the application"
    },
    ...

And I want to transform it like this:

{
	"yes": "Si",
	"no": "No",
        ...

I have modified the parse function to accomplish that:

parse(data) {
    // Remove the .message of each string
    const jsonData = JSON.parse(data);

    Object.entries(jsonData).forEach(([key, value]) => {
        jsonData[key] = value.message;
    });
    return JSON.stringify(jsonData);
}

The console log show the correct json, but when I do a i18next.t('yes') it does not found the message.

I have tried the most simple, like this:

parse(data) {
    return data;
}

But the i18next.t('yes.message') does not work neither to my surprise.

But if I remove the parse function totally, then it works perfectly, the i18next.t('yes.message') returns Si.

Is there something that I have not understood of this function?

To Reproduce

Add an empty parse function to the options and test it:

parse(data) {
    return data;
}

Expected behavior

To find the keys.

Your Environment

  • runtime version: node v10
  • i18next version: 19.5.1
  • os: Windows
  • any other relevant information

translation.json 404 error

๐Ÿ› Bug Report

i18next-http backend, trying to get ns "translation" even though the default name is "common". And I get 404 error on console.
My code working well but I don't want get console error.

My code:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import backend from "i18next-http-backend";
import languageDetector from "i18next-browser-languagedetector";

i18n
  .use(initReactI18next) // passes i18n down to react-i18next
  .use(backend) // for /public/locales
  .use(languageDetector)
  .init({
    fallbackLng: "en",
    supportedLngs: ["en", "tr"],

    defaultNS: "common",

    keySeparator: false, // we do not use keys in form messages.welcome

    interpolation: {
      escapeValue: false, // react already safes from xss
    },
  });

export default i18n;

My console log

request.js:60 GET https://tweet-maker-react.netlify.app/locales/en/translation.json 404

My public folder

public
-- locales
---- en
------ common.json
---- tr
------ common.json

Environment

  • Node.js v12
  • i18next version: 19.9.1
  • Netlify

Dynamic request customHeaders

๐Ÿš€ Feature Proposal

The request is to have customHeaders that can change over calls, in particular Authorization one.
Already implemented in i18next-xhr-backend but now it is deprecated, link to commit here.

Motivation

The necessity is basically the same reported i18next-xhr-backend here.

Example

Initialize i18next without any token as Authorization header, login to your app and need to set the retrieved token into i18next backend plugin, for example to add missing labels.

loading translate not working a server side

๐Ÿ› Bug Report

HttpApi doesn't work server side (I can't see translations on first page load)

To Reproduce

https://codesandbox.io/s/smoosh-thunder-o0fuw

just add plugin i18next-http-backend specify url to download translation

const NextI18Next = require('next-i18next').default;
import HttpApi from 'i18next-http-backend';
const { localeSubpaths } = require('next/config').default().publicRuntimeConfig;
const path = require('path');

module.exports = new NextI18Next({
  use: [HttpApi],
  backend: {
    loadPath: `https://pobeda37.io/api/gettranslate?lng={{lng}}&ns={{ns}}`,
    crossDomain: true
  },
  otherLanguages: ['de'],
  localeSubpaths,
  localePath: path.resolve('./public/static/locales')
});

Expected behavior

I expected translations to be loaded in the same way on the server side

Your Environment

  • node v14.15.4
  • i18next version: ^14.0.1
  • os: Windows

Cannot combine local resources with backend resources

Hello,
I am trying to config my i18n to have local static json file: "backupMessages.json",
and also to fetch other messages file from CDN, by using i18next-http-backend.

I came to this configuration but it only loads the local static file "backupMessages.json",
without the files from the CDN:

Screen Shot 2021-03-14 at 10 58 55

couldn't find a way to make it work.

any help?
thanks :)

request arguments change on usage

The description shows the request options being a function that takes (options, url, payload, callback) however the 3rd argument slot is callback when loading, and is the payload when saving missing. Anyway the argument list could be consistent?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.