
A Laravel package to manage multilingual translations through a single CSV file. Comes with a Livewire management UI, an Inertia.js integration, and JS adapters for React, Vue 3, and Svelte.
ilsawn (ⵉⵍⵙⴰⵡⵏ) — from the Amazigh word ⵉⵍⵙ (ils), meaning language or tongue. The plural ⵉⵍⵙⴰⵡⵏ carries both senses: the organ of speech, and a people's idiom.

SharesTranslations trait pushes the active locale to the frontend automatically, with production caching built in__() helperlaravel/ai (optional)composer require yazidkhaldi/laravel-ilsawn
php artisan ilsawn:install
The install command:
config/ilsawn.phplang/ilsawn.csv with a header row for your configured localesapp/Providers/IlsawnServiceProvider.php where you control who can access the UIresources/js/vendor/ilsawn/ and prints setup instructionsThen register the provider in bootstrap/providers.php:
App\Providers\IlsawnServiceProvider::class,
All assets are published automatically by ilsawn:install. The tags below are only needed if you want to re-publish or customise individual pieces after installation.
| Tag | What gets published | Destination |
|---|---|---|
ilsawn-config |
Package config file | config/ilsawn.php |
ilsawn-views |
Livewire UI views | resources/views/vendor/ilsawn/ |
ilsawn-js |
JS adapters (React, Vue, Svelte, Blade) | resources/js/vendor/ilsawn/ |
php artisan vendor:publish --tag=ilsawn-config
php artisan vendor:publish --tag=ilsawn-views
php artisan vendor:publish --tag=ilsawn-js
Add --force to overwrite files that were already published.
config/ilsawn.php:
return [
// Locales your app supports — each becomes a column in the CSV
'locales' => ['en', 'fr', 'ar'],
// Primary fallback locale when a translation is missing
'default_locale' => 'en',
// Path to the CSV file, relative to base_path()
'csv_path' => 'lang/ilsawn.csv',
// Automatic CSV backup before every generate run
'backup' => true,
'backup_limit' => 5, // 0 = keep all backups
// Column delimiter — semicolon avoids conflicts with commas in text
'delimiter' => ';',
// Directories scanned for translation keys (relative to base_path())
'scan_paths' => ['app', 'resources'],
// Paths excluded from scanning
'scan_exclude' => ['resources/js/vendor/ilsawn'],
// URL prefix for the management UI → /ilsawn
'route_prefix' => 'ilsawn',
// Middleware applied to UI routes
'middleware' => ['web'],
];
key;en;fr;ar
dashboard.title;Dashboard;Tableau de bord;لوحة القيادة
welcome;Welcome :name;Bienvenue :name;مرحبا :name
config('ilsawn.locales'):placeholder — use :name syntax for variable substitution in both PHP and JSEdit this file directly, or use the Livewire UI at /ilsawn.
When you run php artisan lang:publish, Laravel creates lang/en/ with four files:
lang/en/auth.php
lang/en/pagination.php
lang/en/passwords.php
lang/en/validation.php
For each additional locale, the best practice is to clone those files into the matching folder and translate them:
lang/fr/auth.php
lang/fr/pagination.php
lang/fr/passwords.php
lang/fr/validation.php
Pre-translated versions for dozens of languages are available at: github.com/Laravel-Lang/lang
Copy the files for your locales directly from that repository.
Some starter kits (e.g. Laravel Breeze) add strings that you may also want to translate — dashboard labels, auth messages, etc.
Laravel's native translator does NOT auto-load arbitrary JSON files from lang/{locale}/.
For Blade and Livewire, only these sources are loaded automatically:
lang/{locale}/auth.php, pagination.php, passwords.php, validation.php (PHP namespace files)lang/{locale}.json — the root-level JSON, which is exactly what ilsawn generatesSo the correct workflow for those strings is: add them to the CSV.
ilsawn will generate lang/fr.json, lang/ar.json, etc., and Laravel will pick them up natively.
Inertia.js note: If you use the
SharesTranslationstrait, it also reads JSON files placed inlang/{locale}/(e.g.lang/fr/breeze.json) and merges them into the sharedtranslationsprop. This is Inertia-only — it does not affect Blade.
lang/fr.json ← generated by ilsawn:generate — do not edit manually
ilsawn's scanner automatically skips any key it finds in Laravel's standard lang files:
lang/{locale}/auth.php, pagination.php, passwords.php, validation.phplang/{locale}/*.json (read by Inertia's SharesTranslations)The CSV therefore stays focused on your project's own strings — no noise from the framework.
ilsawn:generateGenerates one JSON file per locale in lang/ (e.g. lang/en.json, lang/fr.json).
php artisan ilsawn:generate
Run this whenever you edit the CSV to make changes available to the application. It creates a timestamped CSV backup and clears the application cache automatically.
Options:
| Flag | Description |
|---|---|
--scan |
Scan scan_paths for __() calls and add missing keys to the CSV |
--cleanup |
Prompt to remove keys that are no longer referenced in code |
--remove-duplicates |
Prompt to remove keys that already exist in Laravel's own lang/ PHP files |
--dry-run |
Preview all changes without modifying any file |
ilsawn:installPublish config, CSV stub, Gate provider, and JS adapters.
Safe to re-run with --force to overwrite already-published files.
Use Laravel's native __() helper — no change to your workflow:
{{ __('dashboard.title') }} {{ __('welcome', ['name' => $user->name]) }}
For projects that use Blade without Inertia, add the @ilsawnTranslations directive to your main layout. It outputs a <script> tag that inlines the current locale's translations into the page:
<head>
@ilsawnTranslations
</head>
Then import the adapter once in your JS entry file:
import "@/vendor/ilsawn/adapters/blade";
That's it. window.__ is now available globally, so Alpine.js inline expressions work without any extra setup:
<span x-text="__('dashboard.title')"></span>
<span x-text="__('welcome', { name: 'Ali' })"></span>
Or import the function explicitly in any JS file:
import { __ } from "@/vendor/ilsawn/adapters/blade";
__("dashboard.title"); // 'Dashboard'
__("welcome", { name: "Ali" }); // 'Welcome Ali'
Open app/Http/Middleware/HandleInertiaRequests.php and make two additions:
// 1. Add the use statement inside the class
use ilsawn\LaravelIlsawn\SharesTranslations;
class HandleInertiaRequests extends Middleware
{
use SharesTranslations; // ← add this
public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => ['user' => $request->user()],
// ... your existing shared props ...
'translations' => $this->translations($request), // ← add this
];
}
}
That's it. The trait provides the translations() method — no other changes needed.
In production the result is cached forever under ilsawn_translations_{locale} and invalidated automatically when ilsawn:generate runs.
import { useLang } from "@/vendor/ilsawn/adapters/react";
export default function Page() {
const { __ } = useLang();
return <h1>{__("dashboard.title")}</h1>;
}
<script setup>
import { useLang } from "@/vendor/ilsawn/adapters/vue";
const { __ } = useLang();
</script>
<template>
<h1>{{ __("dashboard.title") }}</h1>
</template>
<script>
import { useLang } from '@/vendor/ilsawn/adapters/svelte';
const { __ } = useLang();
</script>
<h1>{__('dashboard.title')}</h1>
All adapters support :placeholder replacements, consistent with Laravel's PHP syntax:
__("welcome", { name: "Ali" }); // 'Welcome Ali'

Visit /ilsawn (or your configured route_prefix) to access the translation manager.
| Feature | Description |
|---|---|
| Search | Filter keys in real time |
| Missing only | Toggle to show only keys that have at least one empty locale — great for spotting gaps quickly |
| Inline edit | Click Edit on any row to update values directly in the browser |
= button |
Copy the key itself into a locale field — useful for brand names or terms that need no translation |
| AI button | Auto-translate the source text into the target locale (requires laravel/ai) |
| Scan | Detect new keys from your codebase and add them to the CSV without leaving the UI |
| Generate JSON | Regenerate the JSON locale files; a red dot on the button signals the CSV has unsaved changes |
| Pending keys banner | Shown on page load when keys exist in code that are not yet in the CSV |
ilsawn:install publishes app/Providers/IlsawnServiceProvider.php:
Gate::define('viewIlsawn', function ($user) {
return in_array($user->email, [
'[email protected]',
]);
});
Replace the closure body with any logic you need — role checks, environment guards, etc. The UI returns 403 for anyone the gate denies.
Install laravel/ai:
composer require laravel/ai

An AI button appears automatically in the edit row for each non-source locale. It translates the source text into the target language using your configured AI provider. No additional configuration inside ilsawn is needed.
When a translation is requested:
default_locale valueThe app never throws — a missing translation degrades gracefully to the key string.
MIT — see LICENSE for details.