DEV Community

Cover image for Stop Managing Laravel Translations the Hard Way — Meet ilsawn
Yaⵣid ⵅ🇲🇦
Yaⵣid ⵅ🇲🇦

Posted on

Stop Managing Laravel Translations the Hard Way — Meet ilsawn

If you’ve ever maintained a multilingual Laravel app, you know the pain. You start with two languages, maybe English and French. That’s two JSON files, manageable. Then Arabic gets added. Then a client asks for Spanish. Suddenly, you’re copying keys across four files, hunting for the one translation you forgot, and wondering why your Moroccan users are seeing raw dashboard.title strings in production.

I’ve lived this. Working across FinTech projects serving French, English, and Arabic-speaking users, I got tired of the same broken workflow: scattered files, no single source of truth, and zero visibility into what’s missing. So I built laravel-ilsawn. a package that puts every translation in one CSV file and gives you a full management UI, Artisan tooling, and frontend adapters for React, Vue, Svelte, and Alpine.js.

The name comes from *ⵉⵍⵙⴰⵡⵏ *(ilsawn), the Amazigh plural of ils (ⵉⵍⵙ), meaning languages or tongues. It felt right for a tool whose entire purpose is bridging languages.

The Problem with Laravel’s Default Translation Setup

Laravel’s localization system is solid. The __() helper, JSON files, placeholder substitution, all well-designed. But the workflow around it breaks down as soon as you scale beyond two locales or work with a team.

Here’s what typically goes wrong. You add a key to en.json and forget to add it to ar.json. A translator edits fr.json directly and introduces a syntax error that breaks the entire locale. You have no way to see, at a glance, which translations are missing across all locales. And if you're using Inertia.js with React or Vue, you now need to figure out how to get those translations to the frontend, a problem Laravel doesn't solve out of the box.

What you actually want is a spreadsheet-like experience: one row per key, one column per language, and the ability to spot gaps immediately. That’s exactly what a CSV gives you.

Getting Started

composer require yazidkhaldi/laravel-ilsawn
php artisan ilsawn:install
Enter fullscreen mode Exit fullscreen mode

The install command publishes the config file, creates your CSV stub, sets up the Gate provider, and, if Inertia is detected, publishes the JS adapters with setup instructions.

You can configure your locales, scan paths, route prefix, middleware, and backup settings in config/ilsawn.php. The defaults are sensible for most projects.

*One CSV to Rule Them All
*

At the core of ilsawn is a single CSV file that lives at lang/ilsawn.csv:

key;en;fr;ar
dashboard.title;Dashboard;Tableau de bord;لوحة القيادة
welcome;Welcome :name;Bienvenue :name;مرحبا :name
Already have an account?;Already have an account?;Vous avez déjà un compte ?;هل لديك حساب بالفعل؟
Enter fullscreen mode Exit fullscreen mode

Every translation key is a row. Every locale is a column. If a cell is empty, you know something’s missing. You can open this file in Excel, Google Sheets, VS Code … whatever your team prefers. Non-technical translators can edit it without touching your codebase.

The semicolon delimiter is intentional. Commas appear constantly in natural language text, especially in French. Semicolons avoid that collision entirely.

When you’re ready to deploy, one Artisan command generates the standard JSON files Laravel expects:

php artisan ilsawn:generate

Enter fullscreen mode Exit fullscreen mode

That’s it. Your existing __('dashboard.title') calls work without any changes. ilsawn doesn't replace Laravel's translation engine; it feeds it.

A UI That Actually Helps

Artisan commands are great for developers, but they don’t help when a project manager asks “which translations are we missing for Arabic?” That’s why ilsawn ships with a Livewire-powered management UI.

Visit /ilsawn in your browser and you get a searchable, filterable table of every translation key. You can toggle "Missing only" to surface gaps instantly. Click Edit on any row to update translations inline, no file editing, no git conflicts.

There’s even an = button that copies the key itself into a locale field, which is handy for brand names or technical terms that shouldn't be translated.

The UI is gated behind a Laravel Gate, so you have full control over who can access it. The published service provider gives you a simple closure to customize:

Gate::define('viewIlsawn', function ($user) {
    return $user->hasRole('admin');
});
Enter fullscreen mode Exit fullscreen mode

Scanning Your Codebase for Missing Keys

One of the features I’m most proud of is the --scan flag. Run:

php artisan ilsawn:generate --scan

Enter fullscreen mode Exit fullscreen mode

ilsawn walks through your configured directories, finds every __() call, and adds any keys that aren't in the CSV yet. No more forgetting to register a translation key after adding it to a Blade template. You can also trigger this scan directly from the UI; no terminal required.

The --cleanup flag does the opposite: it finds keys in the CSV that are no longer referenced anywhere in your code and prompts you to remove them. Over time, translation files accumulate dead keys. This keeps things lean.

Frontend Adapters: React, Vue 3, Svelte, and Alpine.js

This is where ilsawn goes beyond what most translation packages offer. If you’re using Inertia.js, add the SharesTranslationstrait to your HandleInertiaRequestsmiddleware:

use SharesTranslations;

public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'translations' => $this->translations($request),
    ];
}
Enter fullscreen mode Exit fullscreen mode

Then on the frontend, each framework gets a thin adapter that exposes the same familiar __() function:

React:

import { useLang } from '@/vendor/ilsawn/adapters/react';

export default function Page() {
    const { __ } = useLang();
    return <h1>{__('welcome', { name: 'Ali' })}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Vue 3:

<script setup>
import { useLang } from '@/vendor/ilsawn/adapters/vue';
const { __ } = useLang();
</script>

<template>
    <h1>{{ __('welcome', { name: 'Ali' }) }}</h1>
</template>
Enter fullscreen mode Exit fullscreen mode

Svelte:

<script>
import { useLang } from '@/vendor/ilsawn/adapters/svelte';
const { __ } = useLang();
</script>

<h1>{__('welcome', { name: 'Ali' })}</h1>
Enter fullscreen mode Exit fullscreen mode

If you're using Blade with Alpine.js (no Inertia), drop the @ilsawnTranslations directive in your layout head, import the blade adapter, and window.__ is available globally in your Alpine expressions.

The API is identical across all adapters. :placeholder substitution works exactly like it does in PHP. Your backend developers and frontend developers use the same keys, the same syntax, and the same file as the source of truth.

AI Auto-Translation (Optional)

If you have laravel/ai installed, an AI button appears automatically in the edit UI. Click it, and the source text gets translated into the target locale using your configured AI provider. It's a one-click shortcut for first drafts, you'll still want a human to review, but it eliminates the blank-page problem for new locales.

No extra configuration is needed inside ilsawn. If laravel/ai is present, the feature activates.

What the Fallback Chain Looks Like

ilsawn follows a strict fallback chain that ensures your app never breaks because of a missing translation:

  1. Look up the key in the active locale
  2. If empty, fall back to the default locale (typically English)
  3. If that's also empty, return the raw key

This means the worst-case scenario for an untranslated key is that users see dashboard.title instead of a blank screen or an exception. Graceful degradation, not catastrophic failure.

Who Is This For?

ilsawn is built for Laravel developers who are managing two or more locales and want a workflow that doesn't fight them. It's especially useful if you're building for markets where RTL support matters (the Arabic column in the CSV is just another column, no special handling needed), if your team includes non-developers who need to edit translations, or if you're using Inertia.js and need translations on both sides of the stack.

It's MIT-licensed and available on GitHub. I'd love feedback, issues, and PRs.

GitHub: github.com/yazidkhaldi/laravel-ilsawn

Top comments (0)