Fix: Lingui Not Working — Messages Not Extracted, Translations Missing, or Macro Errors
Quick Answer
How to fix Lingui.js i18n issues — setup with React, message extraction, macro compilation, ICU format, lazy loading catalogs, and Next.js integration.
The Problem
The <Trans> component renders the message ID instead of translated text:
import { Trans } from '@lingui/react';
<Trans id="welcome">Welcome to our app</Trans>
// Renders: "welcome" instead of the translated textOr message extraction produces an empty catalog:
npx lingui extract
# Catalog for "en": 0 messages extractedOr the Lingui macro throws a build error:
SyntaxError: Using the macro requires configuring the Lingui compilerWhy This Happens
Lingui is a compile-time i18n framework that uses macros to extract translatable messages from source code:
- Lingui macros require a compiler plugin —
@lingui/macrouses babel or SWC macros that transform<Trans>andtcalls at build time. Without the compiler plugin, macros aren’t processed and throw syntax errors. - Messages must be extracted before translation —
lingui extractscans source files for macro usage and creates message catalogs (.poor.json). Without extraction, catalogs are empty. - Catalogs must be compiled —
lingui compiletransforms human-readable catalogs into optimized JavaScript. Without compilation, the runtime can’t find translations. - The
I18nProvidermust wrap the app — without the provider and active locale, all messages render as their IDs or source text.
Fix 1: Setup with React and Vite
npm install @lingui/core @lingui/react
npm install -D @lingui/cli @lingui/macro @lingui/vite-plugin// lingui.config.ts
import type { LinguiConfig } from '@lingui/conf';
const config: LinguiConfig = {
locales: ['en', 'ja', 'es', 'fr'],
sourceLocale: 'en',
catalogs: [
{
path: '<rootDir>/src/locales/{locale}/messages',
include: ['src'],
},
],
format: 'po', // 'po' | 'json' | 'minimal'
};
export default config;// vite.config.ts — add the Lingui plugin
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { lingui } from '@lingui/vite-plugin';
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['@lingui/babel-plugin-lingui-macro'],
},
}),
lingui(),
],
});
// Or with SWC (faster):
import react from '@vitejs/plugin-react-swc';
export default defineConfig({
plugins: [
react({
plugins: [['@lingui/swc-plugin', {}]],
}),
lingui(),
],
});// src/i18n.ts — initialize i18n
import { i18n } from '@lingui/core';
export async function loadCatalog(locale: string) {
const { messages } = await import(`./locales/${locale}/messages.ts`);
i18n.load(locale, messages);
i18n.activate(locale);
}
// Load default locale on startup
loadCatalog('en');
export { i18n };// src/main.tsx — wrap app with provider
import { I18nProvider } from '@lingui/react';
import { i18n } from './i18n';
function App() {
return (
<I18nProvider i18n={i18n}>
<MainContent />
</I18nProvider>
);
}Fix 2: Use Macros in Components
// Macros are compiled away at build time — zero runtime overhead
import { Trans, Plural, Select } from '@lingui/react/macro';
import { t, msg, plural } from '@lingui/core/macro';
function WelcomePage({ user }: { user: User }) {
return (
<div>
{/* Trans macro — JSX translation */}
<h1><Trans>Welcome to our app</Trans></h1>
{/* With variables */}
<p><Trans>Hello, {user.name}!</Trans></p>
{/* Pluralization */}
<p>
<Plural
value={user.messageCount}
zero="No messages"
one="You have # message"
other="You have # messages"
/>
</p>
{/* Gender/select */}
<p>
<Select
value={user.gender}
male={<Trans>{user.name} updated his profile</Trans>}
female={<Trans>{user.name} updated her profile</Trans>}
other={<Trans>{user.name} updated their profile</Trans>}
/>
</p>
{/* Rich text — HTML inside translations */}
<p>
<Trans>
Read our <a href="/terms">Terms of Service</a> and{' '}
<a href="/privacy">Privacy Policy</a>.
</Trans>
</p>
</div>
);
}
// t() macro — for non-JSX contexts (attributes, variables)
function SearchInput() {
return (
<input
placeholder={t`Search...`}
aria-label={t`Search the documentation`}
/>
);
}
// In plain functions
function getErrorMessage(code: string): string {
switch (code) {
case 'NOT_FOUND': return t`Resource not found`;
case 'UNAUTHORIZED': return t`You must be logged in`;
default: return t`Something went wrong`;
}
}
// msg() — define messages for later use
const messages = {
title: msg`Dashboard`,
subtitle: msg`Welcome back, ${name}`,
};
// Use later with i18n._(messages.title)Fix 3: Extract and Translate
# Step 1: Extract messages from source code
npx lingui extract
# Output:
# Catalog statistics for en:
# ┌──────────┬─────────────┬─────────┐
# │ Language │ Total count │ Missing │
# ├──────────┼─────────────┼─────────┤
# │ en │ 42 │ 0 │
# │ ja │ 42 │ 42 │
# │ es │ 42 │ 42 │
# └──────────┴─────────────┴─────────┘
# Step 2: Translate the catalogs
# Edit src/locales/ja/messages.po (or .json)
# Step 3: Compile catalogs to JavaScript
npx lingui compile
# This generates optimized .ts files from .po catalogs# src/locales/ja/messages.po
msgid "Welcome to our app"
msgstr "アプリへようこそ"
msgid "Hello, {name}!"
msgstr "こんにちは、{name}さん!"
msgid "{0, plural, zero {No messages} one {You have # message} other {You have # messages}}"
msgstr "{0, plural, other {{0}件のメッセージ}}"
msgid "Search..."
msgstr "検索..."// package.json — i18n scripts
{
"scripts": {
"i18n:extract": "lingui extract",
"i18n:compile": "lingui compile",
"i18n": "lingui extract && lingui compile",
"prebuild": "npm run i18n:compile"
}
}Fix 4: Language Switching
'use client';
import { useLingui } from '@lingui/react';
import { loadCatalog } from '@/i18n';
function LanguageSwitcher() {
const { i18n } = useLingui();
async function changeLanguage(locale: string) {
await loadCatalog(locale);
// i18n is already activated in loadCatalog
}
const locales = [
{ code: 'en', label: 'English' },
{ code: 'ja', label: '日本語' },
{ code: 'es', label: 'Español' },
];
return (
<select
value={i18n.locale}
onChange={(e) => changeLanguage(e.target.value)}
>
{locales.map(({ code, label }) => (
<option key={code} value={code}>{label}</option>
))}
</select>
);
}Fix 5: Next.js App Router
// next.config.mjs
const nextConfig = {
experimental: {
swcPlugins: [['@lingui/swc-plugin', {}]],
},
};
export default nextConfig;// app/[lang]/layout.tsx
import { I18nProvider } from '@lingui/react';
import { loadCatalog, i18n } from '@/i18n';
export default async function LangLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ lang: string }>;
}) {
const { lang } = await params;
await loadCatalog(lang);
return (
<I18nProvider i18n={i18n}>
{children}
</I18nProvider>
);
}
export function generateStaticParams() {
return [{ lang: 'en' }, { lang: 'ja' }, { lang: 'es' }];
}Fix 6: Integration with Translation Services
# Export for translation services (Crowdin, Lokalise, etc.)
npx lingui extract --format po
# .po files are the standard format for translation tools
# Or use JSON format
# lingui.config.ts: format: 'json'
npx lingui extract
# Generates .json catalogs compatible with most translation platforms
# After translators update the files:
npx lingui compileStill Not Working?
Macro throws “requires configuring the Lingui compiler” — the babel or SWC plugin isn’t configured. For Vite, add @lingui/babel-plugin-lingui-macro to the React plugin’s babel plugins. For Next.js, add @lingui/swc-plugin to experimental.swcPlugins.
Extraction finds 0 messages — the include path in lingui.config.ts doesn’t match your source files. Check that include: ['src'] covers where your components are. Also verify macros are imported from @lingui/react/macro or @lingui/core/macro (not @lingui/react).
Translations show source text instead of translated text — catalogs aren’t compiled. Run npx lingui compile after translating. Also check that loadCatalog() was called with the correct locale and that i18n.activate() was called.
Runtime error: “i18n instance not found” — I18nProvider is missing or doesn’t wrap the component using translations. Ensure the provider is in the layout above all translated components.
For related i18n issues, see Fix: i18next Not Working and Fix: next-intl Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: cmdk Not Working — Command Palette Not Opening, Items Not Filtering, or Keyboard Navigation Broken
How to fix cmdk command palette issues — Dialog setup, custom filtering, groups and separators, keyboard shortcuts, async search, nested pages, and integration with shadcn/ui and Tailwind.
Fix: Conform Not Working — Form Validation Not Triggering, Server Errors Missing, or Zod Schema Rejected
How to fix Conform form validation issues — useForm setup with Zod, server action integration, nested and array fields, file uploads, progressive enhancement, and Remix and Next.js usage.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.
Fix: Mantine Not Working — Styles Not Loading, Theme Not Applying, or Components Broken After Upgrade
How to fix Mantine UI issues — MantineProvider setup, PostCSS configuration, theme customization, dark mode, form validation with useForm, and Next.js App Router integration.