React integration for Laravel Localizer - seamlessly use Laravel translations in your React/Inertia.js applications with full TypeScript support.
useLocalizer() hook for easy translation accessnpm install @devwizard/laravel-localizer-react
First, install and configure the Laravel Localizer package:
composer require devwizardhq/laravel-localizer
php artisan localizer:install
See the Laravel Localizer documentation for complete backend setup.
First, generate TypeScript translation files from your Laravel app:
php artisan localizer:generate --all
This creates files like resources/js/lang/en.ts, resources/js/lang/fr.ts, etc.
Add the Vite plugin to auto-regenerate translations when language files change.
File: vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import laravel from 'laravel-vite-plugin';
import { laravelLocalizer } from '@devwizard/laravel-localizer-react/vite';
export default defineConfig({
plugins: [
laravel({
input: ['resources/js/app.tsx'],
refresh: true,
}),
react(),
laravelLocalizer({
// Watch patterns for language file changes
patterns: ['lang/**', 'resources/lang/**'],
// Command to run when files change
command: 'php artisan localizer:generate --all',
// Enable debug logging (optional)
debug: false,
}),
],
});
What it does:
lang/** and resources/lang/**php artisan localizer:generate --all when files changeSet up the global window.localizer object in your app entry point.
File: resources/js/app.tsx
import './bootstrap';
import '../css/app.css';
import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
// Import all generated translation files
import * as translations from './lang';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.tsx`,
import.meta.glob('./Pages/**/*.tsx')
),
setup({ el, App, props }) {
// Initialize window.localizer with translations
if (typeof window !== 'undefined') {
window.localizer = {
translations,
};
}
createRoot(el).render(<App {...props} />);
},
progress: {
color: '#4B5563',
},
});
TypeScript Declaration
To ensure type safety when accessing window.localizer, add this global declaration to your project:
declare global {
interface Window {
localizer: {
translations: typeof translations;
};
}
}
Alternative: Create a separate file
File: resources/js/lang/index.ts
// Export all generated translations
export * from './en';
export * from './fr';
export * from './ar';
// ... add other locales as needed
File: resources/js/app.tsx
import * as translations from './lang';
// ... in setup()
window.localizer = { translations };
Add types to your tsconfig.json:
{
"compilerOptions": {
"types": ["@devwizard/laravel-localizer-react"]
}
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function WelcomeComponent() {
const { __ } = useLocalizer();
return (
<div>
<h1>{__('welcome')}</h1>
<p>{__('validation.required')}</p>
</div>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function GreetingComponent() {
const { __ } = useLocalizer();
return (
<div>
{/* Supports :placeholder format */}
<p>{__('greeting', { name: 'John' })}</p>
{/* "Hello :name!" β "Hello John!" */}
{/* Also supports {placeholder} format */}
<p>{__('items', { count: 5 })}</p>
{/* "You have {count} items" β "You have 5 items" */}
</div>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function ItemCounter({ count }: { count: number }) {
const { choice } = useLocalizer();
return (
<div>
{/* Define in your translation file: */}
{/* "apples": "no apples|one apple|many apples" */}
<p>{choice('apples', count)}</p>
{/* count = 0: "no apples" */}
{/* count = 1: "one apple" */}
{/* count = 5: "many apples" */}
{/* With replacements */}
<p>{choice('apples', count, { count })}</p>
{/* "You have {count} apples" β "You have 5 apples" */}
</div>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function ConditionalTranslation() {
const { __, has } = useLocalizer();
return (
<div>
{has('welcome') && <h1>{__('welcome')}</h1>}
{has('custom.message') ? <p>{__('custom.message')}</p> : <p>Default message</p>}
</div>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function SafeTranslation() {
const { __ } = useLocalizer();
return (
<div>
{/* Use fallback for missing keys */}
<p>{__('might.not.exist', {}, 'Default Text')}</p>
</div>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function LocaleInfo() {
const { locale, dir, availableLocales } = useLocalizer();
return (
<div dir={dir}>
<p>Current Locale: {locale}</p>
<p>Text Direction: {dir}</p>
<select value={locale}>
{Object.entries(availableLocales).map(([code, meta]) => (
<option key={code} value={code}>
{meta.flag} {meta.label}
</option>
))}
</select>
</div>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function RTLAwareComponent() {
const { __, dir } = useLocalizer();
return (
<div dir={dir} className={dir === 'rtl' ? 'text-right' : 'text-left'}>
<h1>{__('welcome')}</h1>
<p>{__('description')}</p>
</div>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function TranslationDebugger() {
const { translations } = useLocalizer();
return (
<div>
<h2>All Translations:</h2>
<pre>{JSON.stringify(translations, null, 2)}</pre>
</div>
);
}
useLocalizer()Returns an object with the following properties and methods:
| Property | Type | Description |
|---|---|---|
__ |
(key: string, replacements?: Replacements, fallback?: string) => string |
Main translation function |
trans |
(key: string, replacements?: Replacements, fallback?: string) => string |
Alias for __() |
lang |
(key: string, replacements?: Replacements, fallback?: string) => string |
Alias for __() |
has |
(key: string) => boolean |
Check if translation key exists |
choice |
(key: string, count: number, replacements?: Replacements) => string |
Pluralization support |
locale |
string |
Current locale code (e.g., βenβ) |
dir |
'ltr' \| 'rtl' |
Text direction |
availableLocales |
Record<string, LocaleMeta> |
Available locales with metadata |
translations |
Record<string, string> |
All translations for current locale |
interface LocalizerOptions {
// Watch patterns for language file changes
patterns?: string[]; // default: ['lang/**', 'resources/lang/**']
// Command to run when files change
command?: string; // default: 'php artisan localizer:generate --all'
// Enable debug logging
debug?: boolean; // default: false
}
The package is written in TypeScript and provides full type definitions:
import {
useLocalizer,
UseLocalizerReturn,
Replacements,
LocaleData,
PageProps,
} from '@devwizard/laravel-localizer-react';
// All types are available for import
The package includes comprehensive tests using Jest and React Testing Library:
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Generate coverage report
npm run test:coverage
import { router } from '@inertiajs/react';
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function LanguageSwitcher() {
const { locale, availableLocales } = useLocalizer();
const changeLocale = (newLocale: string) => {
router.visit(route('locale.switch', { locale: newLocale }), {
preserveScroll: true,
preserveState: true,
});
};
return (
<select value={locale} onChange={(e) => changeLocale(e.target.value)}>
{Object.entries(availableLocales).map(([code, meta]) => (
<option key={code} value={code}>
{meta.flag} {meta.label}
</option>
))}
</select>
);
}
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function LoginForm() {
const { __ } = useLocalizer();
return (
<form>
<div>
<label>{__('auth.email')}</label>
<input type="email" required />
<span className="error">{__('validation.required')}</span>
</div>
<div>
<label>{__('auth.password')}</label>
<input type="password" required />
</div>
<button type="submit">{__('auth.login')}</button>
</form>
);
}
Hereβs a full example of a multilingual user dashboard:
Backend: lang/en.json
{
"welcome": "Welcome",
"dashboard": "Dashboard",
"greeting": "Hello, :name!",
"notifications": "You have :count notifications"
}
Backend: lang/en/dashboard.php
<?php
return [
'title' => 'User Dashboard',
'stats' => [
'users' => '{0} No users|{1} One user|[2,*] :count users',
'posts' => 'You have :count posts',
],
];
Generate translations:
php artisan localizer:generate --all
Frontend: resources/js/Pages/Dashboard.tsx
import { Head } from '@inertiajs/react';
import { useLocalizer } from '@devwizard/laravel-localizer-react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { PageProps } from '@/types';
interface DashboardProps extends PageProps {
stats: {
users: number;
posts: number;
notifications: number;
};
}
export default function Dashboard({ auth, stats }: DashboardProps) {
const { __, choice, locale, dir } = useLocalizer();
return (
<AuthenticatedLayout
user={auth.user}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
{__('dashboard.title')}
</h2>
}
>
<Head title={__('dashboard')} />
<div className="py-12" dir={dir}>
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div className="p-6 text-gray-900">
{/* Greeting with replacement */}
<h1 className="text-2xl font-bold mb-4">
{__('greeting', { name: auth.user.name })}
</h1>
{/* Notification count */}
<p className="mb-4">{__('notifications', { count: stats.notifications })}</p>
{/* Statistics with pluralization */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-blue-50 rounded">
<h3 className="font-semibold">Users</h3>
<p>{choice('dashboard.stats.users', stats.users, { count: stats.users })}</p>
</div>
<div className="p-4 bg-green-50 rounded">
<h3 className="font-semibold">Posts</h3>
<p>{__('dashboard.stats.posts', { count: stats.posts })}</p>
</div>
</div>
{/* Locale info */}
<div className="mt-4 text-sm text-gray-500">
<p>Current locale: {locale}</p>
<p>Text direction: {dir}</p>
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run build
# Run linter
npm run lint
# Format code
npm run format
Contributions are welcome! Please see CONTRIBUTING for details.
Please see CHANGELOG for recent changes.
The MIT License (MIT). Please see License File for more information.