React integration for Laravel Localizer - seamlessly use Laravel translations in your React/Inertia.js applications with full TypeScript support.
- 🎣 React Hook -
useLocalizer()hook for easy translation access - 🔌 Vite Plugin - Auto-regenerates TypeScript translations on file changes
- 🎯 TypeScript - Full type safety with TypeScript support
- ⚡ Inertia.js - Native integration with Inertia.js page props
- 🌐 Pluralization - Built-in pluralization support
- 🔄 Replacements - Dynamic placeholder replacement
- 🌍 RTL Support - Automatic text direction detection
- 📦 Tree-shakeable - Modern ESM build
- React 18 or 19
- Inertia.js v1 or v2
- Laravel Localizer backend package
npm install @devwizard/laravel-localizer-reactFirst, install and configure the Laravel Localizer package:
composer require devwizardhq/laravel-localizer php artisan localizer:installSee the Laravel Localizer documentation for complete backend setup.
First, generate TypeScript translation files from your Laravel app:
php artisan localizer:generate --allThis 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:
- Watches for changes in
lang/**andresources/lang/** - Automatically runs
php artisan localizer:generate --allwhen files change - Triggers HMR to reload your frontend with updated translations
Set 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 neededFile: 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> ); }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 importThe 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:coverageimport { 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 --allFrontend: 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 formatContributions are welcome! Please see CONTRIBUTING for details.
Please see CHANGELOG for recent changes.
The MIT License (MIT). Please see License File for more information.
- Laravel Localizer - Backend package
- @devwizard/laravel-localizer-vue - Vue integration