tolgee / tolgee-js

Tolgee JavaScript libraries monorepo
https://tolgee.io
MIT License
219 stars 24 forks source link

early updateOptions makes it missbehave (react and i18next backend) #3283

Closed Megamannen closed 1 month ago

Megamannen commented 6 months ago

First wrote #3281 But I don't know, I think this is the same, but keeping the old one for history in case I'm wrong

Attached project to reproduce it tolgee-updateOptions.zip

Import language_export into a Tolgee project (has one namespace: 'keys')

populate .env.local with your project setting

VITE_TOLGEE_KEY=tgpak_
VITE_TOLGEE_API=https://
VITE_TOLGEE_PROJECT=1

run

pnpm install
pnpm run dev

observe how AltLeft isn't translated, but you can Alt + Click it and it has the correct values

it all comes down to a very early updateOptions. This example code doesn't make so much sense. But it's a condensed version of something that makes more sense :P

language-provider.tsx

useEffect(() => {
      tolgee.updateOptions({
        apiKey: import.meta.env.VITE_TOLGEE_KEY,
      });      
  }, []);
stepan662 commented 6 months ago

Hey, unfortunately this is not solvable easily. The problem comes from friction between i18next and Tolgee. As Tolgee serves as simple backend, it loads data when i18next requests them. The issue is that the change in options causes tolgee to cancel the pending request, but i18next won't request data again.

What would be an ideal solution is to postpone i18next initialization until the tolgee is fully initialized. Something like this:

const tolgee = Tolgee()
  .use(DevTools())
  .use(I18nextPlugin())
  .use(BackendFetch())
  .init({
    apiUrl: process.env.REACT_APP_TOLGEE_API_URL,
    apiKey: process.env.REACT_APP_TOLGEE_API_KEY,
  });

export const App = () => {
  const currentRoute = window.location.pathname;

  const [ready, setReady] = useState(false);

  useEffect(() => {
    tolgee.updateOptions({
      apiKey: process.env.REACT_APP_TOLGEE_API_KEY,
    });
    withTolgee(i18n, tolgee)
      .use(ICU)
      .use(initReactI18next)
      .init({
        lng: 'en',
        supportedLngs: ['cs', 'en', 'fr', 'de'],
      });
    setReady(true);
  }, []);

  return ready ? (
    <Suspense fallback={<div>Loading...</div>}>
      {currentRoute === '/translation-methods' ? (
        <TranslationMethods />
      ) : (
        <Todos />
      )}
    </Suspense>
  ) : (
    <div>Loading...</div>
  );
};

I know it's a bit ugly, but I don't see a better solution now.

Megamannen commented 6 months ago

How do you know that tolgee is fully initialized in your case?

Is it just because of the "delay" between importing the file and the execution of the component?

stepan662 commented 6 months ago

It's not about Tolgee initialization, but about i18next initialization. Because you ask i18next to load translations, which will use TolgeeBackend to load them - Tolgee starts, but then in the middle of it, you update Tolgee config. Tolgee itself is ready for this as it will cancel the previous requests (or ignore their outcome) and starts new requests to load for data.

However in this case it is dependant on i18next flow and you'd need to force i18next to refetch the data. Other issue is however, that even if you call i18next.refetch (or how it's called). It won't trigger anything as long there is existing ongoing fetch pending, so it won't help unless you artifically wait for some time and then refresh.

Could you maybe explain what is your usecase for this, I might be able to improve Tolgee behavior, to cover your case.