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
452 stars 70 forks source link

Failed to load path from API to get translation content #117

Closed TommyLeong closed 1 year ago

TommyLeong commented 1 year ago

🐛 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/

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

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
}
adrai commented 1 year ago

Make sure maskedDirectoryPath starts with http...

TommyLeong commented 1 year ago

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

adrai commented 1 year ago

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

TommyLeong commented 1 year ago

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

adrai commented 1 year ago

@TommyLeong => https://github.com/TommyLeong/reproduce-inext18/pull/1/files

adrai commented 1 year ago

So can this issue be closed?

TommyLeong commented 1 year ago

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?

adrai commented 1 year ago

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