Blog

How to Use i18next.t() Outside React Components (And Avoid Common Pitfalls)

How to Use i18next.t() Outside React Components (And Avoid Common Pitfalls)

March 18, 2025
Blog Hero Image

"Why Doesn't My Translation Work Outside of React?"

You're building an internationalized React app, and everything works fine inside your components using the useTranslation hook, like shown here, here or here. But then you need to translate text outside of React—maybe in a service, a utility function, or even inside an object.

You try this:‍

import { useTranslation } from 'react-i18next';

const t = useTranslation().t;
console.log(t('welcome_message'));

‍And then—boom. ❌ "Invalid Hook Call." Or maybe t() returns just the key string instead of the translation. So, what's going on?

Let’s break it down and explore the right way to use t() outside React components.

🔥 The Problem: React Hooks Can't Be Used Outside Components

The useTranslation() hook is a React hook, which means it only works inside functional components. If you try to use it in a regular JavaScript module, you’ll get an error. That's because React hooks rely on React's rendering cycle and can't function properly in plain JavaScript files.

But what about this?

import i18n from './i18n';
console.log(i18n.t('welcome_message'));

This might work... or it might not. If i18next isn't fully initialized yet, t() could return the key itself instead of the translation.

So how do we fix this?

✅ Solution 1: Ensure i18next is Initialized Before Calling t()

Since i18next may load translations asynchronously, you must ensure it’s ready before calling t(). One possible way to do this is to await the initialization promise:

1️⃣ Set Up Initialization in i18n.js

// i18n.js
import i18next from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';

const initPromise = i18next
  .use(Backend)
  .use(LanguageDetector)
  .init({
    fallbackLng: 'en',
    debug: true,
    resources: {
      en: { translation: { welcome_message: 'Hello, world!' } },
      de: { translation: { welcome_message: 'Hallo, Welt!' } },
    },
  });

export { initPromise };
export default i18next;

2️⃣ Wait for Initialization Before Using t()

// utils/translate.js
import i18n, { initPromise } from '../i18n';

export const translate = async (key) => {
  await initPromise; // Wait for i18next to be ready
  return i18n.t(key);
};

translate('welcome_message').then(console.log); // "Hello, world!" (or "Hallo, Welt!")

This ensures that translations will always work, even if they’re requested before i18next is fully loaded.

✅ Solution 2: Pass t() as a Function to Non-React Code

Another approach is to pass the t() function from a React component to the place where it's needed.

1️⃣ Inside Your React Component:

import React from 'react';
import { useTranslation } from 'react-i18next';
import { processMessage } from './utils/processMessage';

const MyComponent = () => {
  const { t } = useTranslation();

  React.useEffect(() => {
    processMessage(t);
  }, [t]);

  return <div>{t('welcome_message')}</div>;
};

export default MyComponent;

2️⃣ In Your Utility Function:

// utils/processMessage.js
export const processMessage = (t) => {
  console.log(t('welcome_message')); // Uses the passed `t` function
};

By passing t down from a React component, you avoid the issue of calling t() before initialization, keeping everything in sync with React's lifecycle.

✅ Solution 3: Translating an Object Outside React

What if you need to store translated values inside an object, like for error messages or API responses? You can update the object dynamically when the language changes.

1️⃣ Define a Translatable Object

// utils/messages.js
import i18n from '../i18n'; // the i18next instance

const messages = {};

function updateMessages() {
  messages.error = i18n.t('error_message');
  messages.success = i18n.t('success_message');
}

// If i18next is ready, initialize translations immediately
if (i18n.isInitialized) {
  updateMessages();
} else {
  // wait for the initialized event
  i18n.on('initialized', () => {
    updateMessages();
  });
}

// Update translations on language change
i18n.on('languageChanged', () => {
  updateMessages();
});

// if you lazy loaded some translation
i18n.on('loaded', () => {
  updateMessages();
});

export default messages;

Now, whenever i18next loads or the language changes, the messages object updates automatically.

🚀 Conclusion: The Right Solution Depends on Your Use Case

If you need to use i18next outside of React, pick the right approach:

✔ Ensure initialization before calling t() (await initPromise).
✔ Pass t() down from React if you want to keep it reactive.
✔ Store translations in an object and update them when the language changes, etc.

No more t() returning keys instead of translations! 🎉

Happy coding — and happy translating! 🚀

‍

Do you want to unleash the full power of i18next? Check out this video.