Problems developers face when localizing Remix apps—and how IntlPull helps.
Multiple files to configure: server, client, root. Initial setup has learning curve.
Remix evolves quickly. Library updates follow. Check compatibility with your Remix version.
Careful setup needed to avoid translating on both server and client unnecessarily.
Why remix-i18next is a great choice for Remix localization.
Translations load in Remix loaders. Server-rendered content. No flash of untranslated content.
vs client-only loading
Full i18next feature set. Namespaces, interpolation, plugins. Familiar API.
vs learning new API
Works with Remix's nested routing. Load translations per route. Efficient bundle splitting.
vs loading all routes
Store language preference in cookies or sessions. Persist across visits.
vs localStorage only
Server-rendered translations. Proper lang attribute. Search engines see localized content.
vs client-rendered i18n
TypeScript support with type-safe namespaces and keys. Catch errors at build time.
vs runtime key errors
Get remix-i18next running in your Remix project.
Add remix-i18next and i18next.
npm install remix-i18next i18next react-i18next i18next-browser-languagedetector i18next-fs-backendSet up shared i18n config.
// app/i18n.ts
export default {
supportedLngs: ['en', 'es', 'de'],
fallbackLng: 'en',
defaultNS: 'common',
react: { useSuspense: false },
};Set up i18next for server-side rendering.
// app/i18next.server.ts
import Backend from 'i18next-fs-backend';
import { resolve } from 'node:path';
import { RemixI18Next } from 'remix-i18next/server';
import i18n from './i18n';
const i18next = new RemixI18Next({
detection: {
supportedLanguages: i18n.supportedLngs,
fallbackLanguage: i18n.fallbackLng,
},
i18next: {
...i18n,
backend: {
loadPath: resolve('./public/locales/{{lng}}/{{ns}}.json'),
},
},
plugins: [Backend],
});
export default i18next;Load locale in root loader.
// app/root.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import i18next from './i18next.server';
export async function loader({ request }: LoaderFunctionArgs) {
const locale = await i18next.getLocale(request);
return json({ locale });
}
export default function App() {
const { locale } = useLoaderData<typeof loader>();
const { i18n } = useTranslation();
useChangeLanguage(locale);
return (
<html lang={locale} dir={i18n.dir()}>
{/* ... */}
</html>
);
}Use the useTranslation hook.
import { useTranslation } from 'react-i18next';
export default function Welcome() {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome.title')}</h1>
<p>{t('welcome.description')}</p>
</div>
);
}Connect IntlPull to manage translations professionally.
Add the CLI for translation management.
npm install -D @intlpull/cliSet up the project configuration.
{
"projectId": "your-project-id",
"format": "json",
"sourceLocale": "en",
"localesDir": "./public/locales",
"filePattern": "{locale}/{namespace}.json"
}Download translations from IntlPull.
npx intlpull pullAutomate translation sync.
# .github/workflows/i18n.yml
name: Sync Translations
on:
push:
branches: [main]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx intlpull pull
- run: npm run buildComplete guide to React localization with react-i18next. Setup, lazy loading, namespaces, SSR, and IntlPull integration for production i18n.
Complete guide to Next.js App Router localization with next-intl. Server components, routing, metadata, and IntlPull integration.