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


"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.