next-translate icon indicating copy to clipboard operation
next-translate copied to clipboard

[Question] DynamicNamespaces problems

Open william-hotmart opened this issue 3 years ago • 4 comments

I'm doing an application where the user language is defined after login, so after get the user information I set correct language

  useEffect(() => {
    const changeUserLanguage = async (locale: string) => setLanguage(locale)
    ...
    if (userLocale) {
      changeUserLanguage(userLocale)
    }
  }, [...])

The texts that are not dynamic updates to the correct language immediately, but the content inside DynamicNamespaces do not update. I print the locale to see if they're changing and it was, but the property lang returned by dynamic doesn't.

  const getDynamic = async (lang = 'pt-BR', nameSpace: string) => {
    try {
      const widgetName = getWidgetName(widget.name)
      const language = await import(
        `packages/widgets/${widgetName}/locales/${lang}/${nameSpace}.json`
      )

      return language.default
    } catch (err) {
      console.error(err)

      return null
    }
  }

return (
  <DynamicNamespaces namespaces={['dynamic']} dynamic={getDynamic}>
    {appProps && (
      <WidgetComponent
        widget={widget}
        appProps={appProps}
        prefetchData={getPrefetchData(prefetchUrl)}
      />
    )}
  </DynamicNamespaces>
)

Here you can see that after the language changes, they not call getDynamic again

Captura de tela de 2021-04-22 15-26-13

Is this expected or is a bug? I really need to make this work urgently

william-hotmart avatar Apr 22 '21 18:04 william-hotmart

@william-hotmart I think that <DynamicNamespace> isn't updating since its props aren't. Try to wrap getDynamic with useCallback and add current lang as its dependency. Thus changing language will change getDynamic and trigger updating of <DynamicNamespace>. Not sure tho, just passing by 😄

vlad-elagin avatar Apr 23 '21 09:04 vlad-elagin

@william-hotmart I think that <DynamicNamespace> isn't updating since its props aren't. Try to wrap getDynamic with useCallback and add current lang as its dependency. Thus changing language will change getDynamic and trigger updating of <DynamicNamespace>. Not sure tho, just passing by

Hi @vlad-elagin, thanks for the answer. Teorically the getDynamic function is created in every render. What seems not to change is lang property. Or at least is the dynamic property is not being called again. I'll rewrite the DynamicNamespace component locally and it works. So maybe this can be a bug.

Here is my version of DynamicNamespace

import { useEffect, useMemo, useState } from 'react'

import { DynamicNamespacesProps, I18nDictionary } from 'next-translate'
import I18nProvider from 'next-translate/I18nProvider'
import { useRouter } from 'next/router'

const DynamicNamespaces = ({
  dynamic,
  namespaces = [],
  fallback,
  children
}: DynamicNamespacesProps) => {
  const { locale } = useRouter()

  const [loaded, setLoaded] = useState<boolean>(false)
  const [pageNs, setPageNs] = useState<I18nDictionary[]>([])

  useEffect(() => {
    const loadNamespaces = async () => {
      if (dynamic) {
        const pageNamespaces = await Promise.all(namespaces.map((ns) => dynamic(locale, ns)))
        setPageNs(pageNamespaces)
        setLoaded(true)
      }
    }

    loadNamespaces()
  }, [dynamic, locale, namespaces])
  
  const structuredNamespaces = useMemo(() => {
    return namespaces.reduce((obj: Record<string, I18nDictionary>, ns, i) => {
      obj[ns] = pageNs[i]

      return obj
    }, {})
  }, [namespaces, pageNs])

  if (loaded) {
    return (
      <I18nProvider lang={locale} namespaces={ns}>
        {children}
      </I18nProvider>
    )
  }

  if (fallback) {
    return <>{fallback}</>
  }

  return null
}

export default DynamicNamespaces

william-hotmart avatar Apr 23 '21 13:04 william-hotmart

I proved this code but the problem is:

DynamicNamespaces don't refresh namespaces when change locale and I18NProvider only refresh when locale change so... the first render when you are requesting namespaces I18nProvider don't have namespaces yet. When you will have namespaces, the provider not update namespaces, only when change language soo..

I added a state: const [locale2, setLocale2] = useState(locale === 'en' ? 'es' : 'en'); (it's necesary locale2 exist in your page languages)

and upodate locale2 when you have namespaces: ->setLocal2(locale)

and the new lang param of i18nProvider will be locale2.

like this:

import { useEffect, useMemo, useState } from 'react'

import { DynamicNamespacesProps, I18nDictionary } from 'next-translate'
import I18nProvider from 'next-translate/I18nProvider'
import { useRouter } from 'next/router'

const DynamicNamespaces = ({
  dynamic,
  namespaces = [],
  fallback,
  children
}: DynamicNamespacesProps) => {
  const { locale } = useRouter()

  const [loaded, setLoaded] = useState<boolean>(false)
  const [locale2, setLocale2] = useState(locale === 'en' ? 'es' : 'en');
  const [pageNs, setPageNs] = useState<I18nDictionary[]>([])

  useEffect(() => {
    const loadNamespaces = async () => {
      if (dynamic) {
        const pageNamespaces = await Promise.all(namespaces.map((ns) => dynamic(locale, ns)))
        setPageNs(pageNamespaces)
        setLocale2(locale)
        setLoaded(true)
      }
    }

    loadNamespaces()
  }, [dynamic, locale, namespaces])
  
  const structuredNamespaces = useMemo(() => {
    return namespaces.reduce((obj: Record<string, I18nDictionary>, ns, i) => {
      obj[ns] = pageNs[i]

      return obj
    }, {})
  }, [namespaces, pageNs])

  if (loaded) {
    return (
      <I18nProvider lang={locale2} namespaces={pageNs}>
        {children}
      </I18nProvider>
    )
  }

  if (fallback) {
    return <>{fallback}</>
  }

  return null
}

export default DynamicNamespaces

piiok avatar May 06 '21 16:05 piiok

And is it really necessary to use DynamicNamespace for this case? After login you can redirect to the same path with the lang changed and everything becomes much simpler.

aralroca avatar May 07 '21 08:05 aralroca