js-lingui: Memoized string-only translation does not update on `useLingui` re-render

Describe the bug In v4, to ensure that messages tagged with the t macro get updated when the locale changes, we must subscribe components to the useLingui hook. This works reliably in the component’s return-to-render. However, if parts of the render are memoized using a hook such as useMemo, the value will not always be updated. Always, because this seems to only happen after switching the locale at least twice. The first change is registered. Yet, when i18n.locale is added to the hook’s dependencies, values are reliably re-computed.

To Reproduce

// LocaleProvider.tsx

  // When the language changes, load and activate its message catalog.
  useEffect(() => {
    async function activate(language: SupportedLanguage): Promise<void> {
      const { messages } = await import(`../../assets/locales/${language}/messages`);

      i18n.load(language, messages);
      i18n.activate(language);

      setCachedLanguages((cachedLanguages) => {
        return { ...cachedLanguages, [language]: true };
      });

      setShowLoadingSpinner(false);
    }

    activate(activeLanguage);
  }, [activeLanguage]);

[...]

  return (
    <LocaleContext.Provider value={context}>
      <I18nProvider i18n={i18n} {...props} />
    </LocaleContext.Provider>
  );

Does not work reliably:

const translatedMessage = useMemo(() => i18n._(msg`Unverified`), [i18n]);
console.log(translatedMessage); // Unverified

Works reliably:

const translatedMessage = useMemo(() => i18n._(msg`Unverified`), [i18n, i18n.locale]);
console.log(translatedMessage); // Sin verificar

Additional context Add any other context about the problem here.

  • jsLingui version: 4.2.0
  • Babel version: @babel/core@7.21.5 (via @lingui/cli@4.2.0)
  • Create React App: 5.0.1

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 15

Most upvoted comments

I’m away for a few days now but believe there is a way to make this less verbose: we can expose the ‘t’ function from the context in such a way that it will change reference as we need. Then, the memo would depend on just ‘t’ and not ‘i18n’. I will take a look at this in two weeks or so. There might be some downsides that I haven’t considered.

Both versions below do indeed work, though I wish there was a shorter way to do a memoized string-only translation:

import { t } from '@lingui/macro';
import { useLingui } from '@lingui/react';

const Component = () => {
  const lingui = useLingui();

  // Works
  const version1 = useMemo(() => {
    return t(lingui.i18n)`welcomeMessage`;
  }, [lingui]);

  // Works
  const version2 = useMemo(() => {
    return lingui.i18n.t("welcomeMessage");
  }, [lingui]);

  [...]
};

@vonovak For me depending on the i18n in the context will not trigger the lazy translation to be changed once the locale is changed. It only works if I add i18n.locale to the dependency array as stated in this reply. But indeed it breaks eslint rules: “exhaustive-deps”.