<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Yaⵣid ⵅ🇲🇦</title>
    <description>The latest articles on Forem by Yaⵣid ⵅ🇲🇦 (@yazid_kha).</description>
    <link>https://forem.com/yazid_kha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1335258%2Ffacb902b-ac99-4ccc-819c-282fc1c54a79.jpg</url>
      <title>Forem: Yaⵣid ⵅ🇲🇦</title>
      <link>https://forem.com/yazid_kha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/yazid_kha"/>
    <language>en</language>
    <item>
      <title>Stop Managing Laravel Translations the Hard Way — Meet ilsawn</title>
      <dc:creator>Yaⵣid ⵅ🇲🇦</dc:creator>
      <pubDate>Wed, 11 Mar 2026 00:34:38 +0000</pubDate>
      <link>https://forem.com/yazid_kha/stop-managing-laravel-translations-the-hard-way-meet-ilsawn-52db</link>
      <guid>https://forem.com/yazid_kha/stop-managing-laravel-translations-the-hard-way-meet-ilsawn-52db</guid>
      <description>&lt;p&gt;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 &lt;code&gt;dashboard.title&lt;/code&gt; strings in production.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;laravel-ilsawn&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bticmlmqy56fp9z41p7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bticmlmqy56fp9z41p7.png" alt=" " width="800" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Problem with Laravel’s Default Translation Setup
&lt;/h2&gt;

&lt;p&gt;Laravel’s localization system is solid. The &lt;code&gt;__()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Here’s what typically goes wrong. You add a key to &lt;code&gt;en.json&lt;/code&gt; and forget to add it to &lt;code&gt;ar.json&lt;/code&gt;. A translator edits &lt;code&gt;fr.json&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require yazidkhaldi/laravel-ilsawn
php artisan ilsawn:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

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

&lt;p&gt;*&lt;em&gt;One CSV to Rule Them All&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
At the core of ilsawn is a single CSV file that lives at &lt;code&gt;lang/ilsawn.csv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="k"&gt;en&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="k"&gt;fr&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="k"&gt;ar&lt;/span&gt;
&lt;span class="k"&gt;dashboard&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;title&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="k"&gt;Dashboard&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="k"&gt;Tableau&lt;/span&gt; &lt;span class="k"&gt;de&lt;/span&gt; &lt;span class="k"&gt;bord&lt;/span&gt;&lt;span class="err"&gt;;لوحة&lt;/span&gt; &lt;span class="err"&gt;القيادة&lt;/span&gt;
&lt;span class="k"&gt;welcome&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="k"&gt;Welcome&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="k"&gt;Bienvenue&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;&lt;span class="err"&gt;;مرحبا&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;Already&lt;/span&gt; &lt;span class="k"&gt;have&lt;/span&gt; &lt;span class="k"&gt;an&lt;/span&gt; &lt;span class="k"&gt;account&lt;/span&gt;&lt;span class="err"&gt;?;&lt;/span&gt;&lt;span class="k"&gt;Already&lt;/span&gt; &lt;span class="k"&gt;have&lt;/span&gt; &lt;span class="k"&gt;an&lt;/span&gt; &lt;span class="k"&gt;account&lt;/span&gt;&lt;span class="err"&gt;?;&lt;/span&gt;&lt;span class="k"&gt;Vous&lt;/span&gt; &lt;span class="k"&gt;avez&lt;/span&gt; &lt;span class="k"&gt;d&lt;/span&gt;&lt;span class="err"&gt;é&lt;/span&gt;&lt;span class="k"&gt;j&lt;/span&gt;&lt;span class="err"&gt;à&lt;/span&gt; &lt;span class="k"&gt;un&lt;/span&gt; &lt;span class="k"&gt;compte&lt;/span&gt; &lt;span class="err"&gt;?;هل&lt;/span&gt; &lt;span class="err"&gt;لديك&lt;/span&gt; &lt;span class="err"&gt;حساب&lt;/span&gt; &lt;span class="err"&gt;بالفعل؟&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The semicolon delimiter is intentional. Commas appear constantly in natural language text, especially in French. Semicolons avoid that collision entirely.&lt;/p&gt;

&lt;p&gt;When you’re ready to deploy, one Artisan command generates the standard JSON files Laravel expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan ilsawn:generate

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  A UI That Actually Helps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcht3t7gbwwwdk5hie9v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcht3t7gbwwwdk5hie9v1.png" alt=" " width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Visit &lt;code&gt;/ilsawn&lt;/code&gt; 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.&lt;/p&gt;

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

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Gate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'viewIlsawn'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scanning Your Codebase for Missing Keys
&lt;/h2&gt;

&lt;p&gt;One of the features I’m most proud of is the &lt;code&gt;--scan&lt;/code&gt; flag. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan ilsawn:generate &lt;span class="nt"&gt;--scan&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ilsawn walks through your configured directories, finds every &lt;code&gt;__()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--cleanup&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend Adapters: React, Vue 3, Svelte, and Alpine.js
&lt;/h2&gt;

&lt;p&gt;This is where ilsawn goes beyond what most translation packages offer. If you’re using Inertia.js, add the &lt;code&gt;SharesTranslations&lt;/code&gt;trait to your &lt;code&gt;HandleInertiaRequests&lt;/code&gt;middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SharesTranslations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;share&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'translations'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then on the frontend, each framework gets a thin adapter that exposes the same familiar &lt;code&gt;__()&lt;/code&gt; function:&lt;/p&gt;

&lt;p&gt;React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLang&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/vendor/ilsawn/adapters/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;__&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLang&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ali&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vue 3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLang&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/vendor/ilsawn/adapters/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;__&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLang&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ali&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Svelte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLang&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/vendor/ilsawn/adapters/svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;__&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLang&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ali&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The API is identical across all adapters. &lt;code&gt;:placeholder&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Auto-Translation (Optional)
&lt;/h2&gt;

&lt;p&gt;If you have &lt;code&gt;laravel/ai&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0q0gjwgh2un5b28py05.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0q0gjwgh2un5b28py05.gif" alt=" " width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No extra configuration is needed inside ilsawn. If &lt;code&gt;laravel/ai&lt;/code&gt; is present, the feature activates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Fallback Chain Looks Like
&lt;/h2&gt;

&lt;p&gt;ilsawn follows a strict fallback chain that ensures your app never breaks because of a missing translation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look up the key in the active locale&lt;/li&gt;
&lt;li&gt;If empty, fall back to the default locale (typically English)&lt;/li&gt;
&lt;li&gt;If that's also empty, return the raw key&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h2&gt;
  
  
  Who Is This For?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;It's MIT-licensed and available on GitHub. I'd love feedback, issues, and PRs.&lt;/p&gt;

&lt;p&gt;GitHub: github.com/yazidkhaldi/laravel-ilsawn&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>localization</category>
      <category>ai</category>
      <category>inertia</category>
    </item>
  </channel>
</rss>
