i18next / i18next-http-backend

i18next-http-backend is a backend layer for i18next using in Node.js, in the browser and for Deno.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Failed to load path from API to get translation content

TommyLeong opened this issue Β· comments

πŸ› Bug Report

I'm trying to get my translation content from a API, the payload response structure is designed as below. (It's not necessarily to follow such structure, as I'm currently concept proofing the reading from API is possible)

en -> language
ns1 -> namespace
anything inside namespace is just key:value translation

{
    "en": {
        "ns1": {
            "welcome": "hello world"
        }
    }
}

I'm however getting the following error message while visiting the page itself (loading the page itself has no issue, everything render fine)

i18next::backendConnector: loading namespace ns1 for language en failed TypeError: Failed to parse URL from maskedDirectoryPath/public/locales/en/ns1.json
    at Object.fetch (maskedDirectoryPath/node_modules/next/dist/compiled/undici/index.js:1:26669)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  [cause]: TypeError [ERR_INVALID_URL]: Invalid URL
      at new NodeError (node:internal/errors:387:5)
      at URL.onParseError (node:internal/url:565:9)
      at new URL (node:internal/url:641:5)
      at new Request (maskedDirectoryPath/node_modules/next/dist/compiled/undici/index.js:2:42745)
      at fetch (maskedDirectoryPath/node_modules/next/dist/compiled/undici/index.js:2:25959)
      at Object.fetch (maskedDirectoryPath/node_modules/next/dist/compiled/undici/index.js:1:26638)
      at globalThis.fetch (maskedDirectoryPath/node_modules/next/dist/server/node-polyfill-fetch.js:30:26)
      at fetchIt (maskedDirectoryPath/node_modules/i18next-http-backend/cjs/request.js:52:3)
      at requestWithFetch (maskedDirectoryPath/node_modules/i18next-http-backend/cjs/request.js:78:5)
      at Object.request (maskedDirectoryPath/node_modules/i18next-http-backend/cjs/request.js:144:12)
      at Backend.loadUrl (maskedDirectoryPath/node_modules/i18next-http-backend/cjs/index.js:107:20)
      at maskedDirectoryPath/node_modules/i18next-http-backend/cjs/index.js:97:16
      at processTicksAndRejections (node:internal/process/task_queues:96:5) {
    input: 'maskedDirectoryPath/public/locales/en/ns1.json',
    code: 'ERR_INVALID_URL'
  }
}

At the same time, I have created 2 .json file within FE project, at path of projectRootDir/public/locales/en/

  • common.json
  • ns1.json

Inside both this file, I have also placed the key:value for welcome

common.json

{
"welcome": "COMMON EN"
}

ns1.json

{
    "welcome":"ns1 EN welcome"
}

To Reproduce

next-i18next.config.js

const I18NextHttpBackend = require('i18next-http-backend')
const HttpBackend = require('i18next-http-backend/cjs')

/** @type {import('next-i18next').UserConfig} */
module.exports = {
    i18n: {
      // Testing backend calling
      defaultLocale: 'en',
      locales: ['en'],
      defaultNS: 'ns1',
      serializeConfig: false,
      use: [HttpBackend],
      backend: {
        loadPath: 'http://localhost:8080/locales?lng=en&ns=ns1',
      },
      debug: true,
    }
  }

_app.js

import '../styles/globals.css'
import { appWithTranslation } from 'next-i18next'
import nextI18NextConfig from '../next-i18next.config'


const App = ({ Component, pageProps }) => {
  return <Component {...pageProps} />
}

// export default appWithTranslation(App);
export default appWithTranslation(App, nextI18NextConfig);

FE code

import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'

export default function Home(props) {
const { t:translate } = useTranslation(['ns1']);

return(
    <div>
        <h1>
            Translation the key "welcome"
            <br/> ---::: {translate("welcome")}
        </h1>
    </div>
)
}

export async function getServerSideProps({ locale }) {
    return {
      props: {
        ...(await serverSideTranslations(locale, ['ns1'])),
        // ...(await serverSideTranslations(locale, ['ns1','common'], nextI18NextConfig)),
      },
    }
}

Expected behavior

I'm expecting that the translation content read from API will be returned as the namespace ns1.

Your Environment

  • runtime version: node v16.20.0
  • i18next version: ^23.3.0
  • os: Mac
  • extra package.json info
"i18next": "^23.3.0",
    "i18next-chained-backend": "^4.4.0",
    "i18next-http-backend": "^2.2.1",
    "i18next-localstorage-backend": "^4.1.1",
    "i18next-multiload-backend-adapter": "^2.2.2",
    "next": "^13.4.12",
    "next-i18next": "^14.0.0",

More information on the debug

- wait compiling...
- event compiled client and server successfully in 344 ms (314 modules)
i18next: languageChanged en
i18next: initialized {
  debug: true,
  initImmediate: undefined,
  ns: [],
  defaultNS: 'ns1',
  fallbackLng: [ 'en' ],
  fallbackNS: false,
  supportedLngs: false,
  nonExplicitSupportedLngs: false,
  load: 'currentOnly',
  preload: [ 'en' ],
  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: false,
  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: 'en',
  defaultLocale: 'en',
  locales: [ 'en' ],
  backend: {
    addPath: 'maskedDirectoryPath/public/locales/{{lng}}/{{ns}}.missing.json',
    loadPath: 'maskedDirectoryPath/public/locales/{{lng}}/{{ns}}.json',
    allowMultiLoading: false,
    parse: [Function: parse],
    stringify: [Function: stringify],
    parsePayload: [Function: parsePayload],
    parseLoadPayload: [Function: parseLoadPayload],
    request: [Function: request],
    reloadInterval: 3600000,
    customHeaders: {},
    queryStringParams: {},
    crossDomain: false,
    withCredentials: false,
    overrideMimeType: false,
    requestOptions: { mode: 'cors', credentials: 'same-origin', cache: 'default' }
  },
  resources: undefined,
  ignoreJSONStructure: true
}

Make sure maskedDirectoryPath starts with http...

@adrai actually no, it's pointing to my local project path. Any chance u spotted something configured wrongly?

If it does not start with http, pn server side that will fail...
Beside that, a reproducible example would help to investigate.

@adrai I have created a repo here for reproduce purpose. Hope to hear from you soon!

So can this issue be closed?

Yes. Thank you for your prompt response!

May I understand why must the url leave it with http://localhost:8080/locales?lng={{lng}}&ns={{ns}} ? From where the value of lng and ns is coming?

it gets automatically interpolated with the requested language and namespace by the used backend plugin