{"id":8703,"date":"2024-10-17T13:24:20","date_gmt":"2024-10-17T11:24:20","guid":{"rendered":"https:\/\/phraseapp.com\/blog\/?p=3164"},"modified":"2024-11-14T17:29:17","modified_gmt":"2024-11-14T16:29:17","slug":"laravel-i18n-frontend-best-practices","status":"publish","type":"post","link":"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/","title":{"rendered":"The Ultimate Guide to Laravel Localization"},"content":{"rendered":"<p>I remember using Laravel years ago to kick off two tech startups. I loved Laravel\u2019s elegance, developer experience, and down-to-earth community. This was in the days of Laravel 3. We\u2019re at version 11 of the framework as I write this, and to say that its ecosystem has matured would be an understatement. There is a quality Laravel package (often first-party) for almost any common dev problem, from authentication to deployment. The care and commitment the community continues to pour into Laravel is inspiring.<\/p>\n<p>When it comes to Laravel internationalization (i18n), a good built-in foundation is complemented by third-party packages. In this guide, we\u2019ll use these features to localize a Laravel app, covering translations, localized routing, formatting, localizing models, and more.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong> Internationalization (i18n) and localization (l10n) allow us to make our apps available in different languages and regions, often for more profit. If you\u2019re new to i18n and l10n, check out our guide to <a href=\"https:\/\/phrase.com\/blog\/posts\/i18n-a-simple-definition\/\">internationalization<\/a>.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_69_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Overview<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#our-demo\" title=\"Our demo\">Our demo<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#packages-and-versions-used\" title=\"Packages and versions used\">Packages and versions used<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#the-starter-app\" title=\"The starter app\">The starter app<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-localize-a-laravel-app\" title=\"How do I localize a Laravel app?\">How do I localize a Laravel app?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-translate-strings\" title=\"How do I translate strings?\">How do I translate strings?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#a-note-on-locales\" title=\"A note on locales\">A note on locales<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#missing-translations\" title=\"Missing translations\">Missing translations<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#translation-strings-as-keys\" title=\"Translation strings as keys\">Translation strings as keys<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-extract-translations-from-my-code\" title=\"How do I extract translations from my code?\">How do I extract translations from my code?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-localize-routes\" title=\"How do I localize routes?\">How do I localize routes?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#localized-links-and-urls\" title=\"Localized links and URLs\">Localized links and URLs<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-detect-the-users-locale\" title=\"How do I detect the user\u2019s locale?\">How do I detect the user\u2019s locale?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#new-user-visit\" title=\"New user visit\">New user visit<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#rolling-our-own-browser-detector\" title=\"Rolling our own browser detector\">Rolling our own browser detector<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-work-with-right-to-left-languages\" title=\"How do I work with right-to-left languages?\">How do I work with right-to-left languages?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-build-a-language-switcher\" title=\"How do I build a language switcher?\">How do I build a language switcher?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#a-note-on-storage-and-detectors\" title=\"A note on storage and detectors\">A note on storage and detectors<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-add-dynamic-values-to-translation-messages\" title=\"How do I add dynamic values to translation messages?\">How do I add dynamic values to translation messages?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-work-with-plurals\" title=\"How do I work with plurals?\">How do I work with plurals?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#adding-trans-choice-to-the-extraction-function-list\" title=\"Adding trans_choice to the extraction function list\">Adding trans_choice to the extraction function list<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-format-numbers\" title=\"How do I format numbers?\">How do I format numbers?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-format-dates\" title=\"How do I format dates?\">How do I format dates?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#some-notes-on-date-localization\" title=\"Some notes on date localization\">Some notes on date localization<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#how-do-i-localize-my-eloquent-models\" title=\"How do I localize my Eloquent models?\">How do I localize my Eloquent models?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/phrase.com\/blog\/posts\/laravel-i18n-frontend-best-practices\/#power-up-your-laravel-localization\" title=\"Power up your Laravel localization\">Power up your Laravel localization<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"our-demo\"><\/span>Our demo<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Our starting point will be the fictional <strong>bookavel<\/strong>, a used bookstore app.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92111\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-707x1024.png\" alt=\"The image is a screenshot of a demo web app showcasing a book listing. It includes a header with the app name &quot;bookavel&quot; and two navigation links: &quot;Books&quot; and &quot;About us.&quot; The section shown is titled &quot;Recently added&quot; and displays four books with their titles, authors, added dates, and prices in USD. The screenshot reflects the state of the app before localization is applied. The footer notes the app is a demo showcasing Laravel localization, created as a companion to a Phrase blog article.\" width=\"707\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-707x1024.png 707w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-207x300.png 207w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-768x1113.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-1060x1536.png 1060w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-1413x2048.png 1413w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization.png 1456w\" sizes=\"(max-width: 707px) 100vw, 707px\" \/><\/p>\n<p>We won\u2019t cover any e-commerce functionality here, since we want to focus on i18n.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"packages-and-versions-used\"><\/span>Packages and versions used<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We\u2019ll use the following technologies and packages when building this app.<\/p>\n<ul>\n<li>PHP v8.3.10 \u2014 our programming language<\/li>\n<li>Laravel v11.21.0 \u2014 the framework for web artisans<\/li>\n<li><code>codezero\/laravel-localized-routes<\/code> v4.0.1 \u2014 allows us to localize our routes quickly<\/li>\n<li><code>kkomelin\/laravel-translatable-string-exporter<\/code> v1.22.0 \u2014\u00a0provides translation extraction from our code into message files<\/li>\n<li><code>tailwindcss<\/code> v3.4.10 \u2014\u00a0for styling; largely optional for our purposes<\/li>\n<\/ul>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We\u2019ll cover the installation of <code>laravel-localized-routes<\/code> and <code>laravel-translatable-string-exporter<\/code> a bit later. All other packages are included with our starter app.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"the-starter-app\"><\/span>The starter app<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Normally, we build demo apps from scratch in our tutorials. However, we have lots to cover here, so we\u2019ll work from a pre-built starter for brevity. Here\u2019s how to install the starter app:<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Heads up \u00bb<\/strong> You\u2019ll need PHP 8+, <a href=\"https:\/\/getcomposer.org\/\">Composer<\/a>, and <a href=\"https:\/\/nodejs.org\/\">Node.js 20+<\/a> installed to continue.<\/p>\n<ol>\n<li>Clone the GitHub repo and switch to the <code>start<\/code> branch (alternatively, <a style=\"background-color: #ffffff;\" href=\"https:\/\/github.com\/PhraseApp-Blog\/laravel-i18n\/archive\/refs\/heads\/start.zip\">download the ZIP<\/a> of the <code>start<\/code> branch and unzip it)<\/li>\n<li>If you\u2019re using <a style=\"background-color: #ffffff;\" href=\"https:\/\/herd.laravel.com\/\">Laravel Herd<\/a>, make sure to place the project directory inside your <code>Herd<\/code> directory<\/li>\n<li>From the project root, run <code>composer install<\/code><\/li>\n<li>Run <code>npm install<\/code><\/li>\n<li>Duplicate the <code>.env.example<\/code> file and rename the copy to <code>.env<\/code><\/li>\n<li>Run <code>php artisan key:generate<\/code><\/li>\n<li>Run <code>php artisan migrate --seed<\/code> and select \u201cYes\u201d when asked to create the SQLite database<\/li>\n<li>Run <code>php artisan serve<\/code> to start the dev server (you don\u2019t need to do this if you\u2019re using Laravel Herd; just find the app under <strong>Sites<\/strong> and visit its URL)<\/li>\n<li>In a separate terminal tab, run <code>npm run dev<\/code> to start Vite<\/li>\n<\/ol>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If you\u2019re using <a href=\"https:\/\/laravel.com\/docs\/11.x\/sail\">Laravel Sail<\/a> with Docker, we\u2019ve provided a <code>docker-compose.yml<\/code> file in the repo. However, Sail\/Docker instructions are a bit outside the scope of this article.<\/p>\n<p>If all goes well, you should see the demo app in all its glory when you visit the local URL.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92111\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-707x1024.png\" alt=\"The image is a screenshot of a demo web app showcasing a book listing. It includes a header with the app name &quot;bookavel&quot; and two navigation links: &quot;Books&quot; and &quot;About us.&quot; The section shown is titled &quot;Recently added&quot; and displays four books with their titles, authors, added dates, and prices in USD. The screenshot reflects the state of the app before localization is applied. The footer notes the app is a demo showcasing Laravel localization, created as a companion to a Phrase blog article.\" width=\"707\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-707x1024.png 707w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-207x300.png 207w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-768x1113.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-1060x1536.png 1060w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization-1413x2048.png 1413w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-before-localization.png 1456w\" sizes=\"(max-width: 707px) 100vw, 707px\" \/><\/p>\n<p>The app is simple:<\/p>\n<ul>\n<li>A <code>Book<\/code> model is used by a <code>BookController<\/code> to expose a book <code>index<\/code> (under <code>\/books<\/code>) and a single book <code>show<\/code> (under <code>\/books\/{id}<\/code>).<\/li>\n<li>The root route (<code>\/<\/code>) exposes <code>BookController@index<\/code>, presenting the book listing above.<\/li>\n<li>An <code>\/about<\/code> route shows a simple text view.<\/li>\n<\/ul>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">BookController<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Route<\/span>;\n\nRoute::get(<span class=\"hljs-string\">'\/'<\/span>, &#91;BookController::class, <span class=\"hljs-string\">'index'<\/span>])-&gt;name(<span class=\"hljs-string\">'root'<\/span>);\nRoute::resource(<span class=\"hljs-string\">'books'<\/span>, BookController::class)-&gt;only(<span class=\"hljs-string\">'index'<\/span>, <span class=\"hljs-string\">'show'<\/span>);\n\nRoute::get(<span class=\"hljs-string\">'\/about'<\/span>, fn () =&gt; view(<span class=\"hljs-string\">'about'<\/span>))-&gt;name(<span class=\"hljs-string\">'about'<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_5b52bb881903a632183a46544921c1a1\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>This should be bread-and-butter Laravel for you. Let\u2019s localize!<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-a-laravel-app\"><\/span>How do I localize a Laravel app?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Localizing a Laravel app involves the following steps.<\/p>\n<ol>\n<li>Replacing hard-coded strings with translated strings using the Laravel <code>()<\/code> function.<\/li>\n<li>Extracting these translated strings from code into translation files.<\/li>\n<li>Localizing routes.<\/li>\n<li>Building a language switcher UI.<\/li>\n<li>Handling dynamic values in translations.<\/li>\n<li>Working with plurals in translations.<\/li>\n<li>Formatting localized numbers and dates.<\/li>\n<li>Localizing database models.<\/li>\n<\/ol>\n<p>We\u2019ll go through these steps in detail. Let\u2019s start with basic string translation.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-translate-strings\"><\/span>How do I translate strings?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Laravel has solid built-in localization features; we can use them for basic translations. First, let\u2019s configure some localization settings in our <code>.env<\/code> file.<\/p>\n<p>We\u2019ll support English &#8211; United States (<code>en_US<\/code>) and Arabic &#8211; Egypt (<code>ar_EG<\/code>) in this article. Let\u2019s start by updating our active and fallback locales to be <code>en_US<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># .env<\/span>\n\n  APP_NAME=bookavel\n  APP_ENV=local\n  APP_KEY=yourappkeyliveshere\n  APP_DEBUG=<span class=\"hljs-keyword\">true<\/span>\n  APP_TIMEZONE=UTC\n  APP_URL=http:<span class=\"hljs-comment\">\/\/localhost<\/span>\n\n- APP_LOCALE=en\n+ APP_LOCALE=en_US\n- APP_FALLBACK_LOCALE=en\n+ APP_FALLBACK_LOCALE=en_US\n  APP_FAKER_LOCALE=en\n\n  APP_MAINTENANCE_DRIVER=file\n  <span class=\"hljs-comment\"># APP_MAINTENANCE_STORE=database<\/span>\n\n  BCRYPT_ROUNDS=<span class=\"hljs-number\">12<\/span>\n\n  <span class=\"hljs-comment\"># ...<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_688194c66b6ea0d130473fca5fcabd4f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The\u00a0<code>APP_LOCALE<\/code> environment variable determines the active locale. When a translation message is missing, the <code>APP_FALLBACK_LOCALE<\/code> is used. We\u2019ll see how both these variables work momentarily.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"a-note-on-locales\"><\/span>A note on locales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A locale defines a language, a region, and sometimes more. Locales typically use <a href=\"https:\/\/en.wikipedia.org\/wiki\/IETF_language_tag\">IETF BCP 47 language tags<\/a>, like <code>en<\/code> for English, <code>fr<\/code> for French, and <code>es<\/code> for Spanish. Adding a region with the ISO Alpha-2 code (e.g., <code>BH<\/code> for Bahrain, <code>CN<\/code> for China, <code>US<\/code> for the United States) is recommended for accurate date and number localization. So a complete locale might look like <code>en_US<\/code> for American English or <code>zh_CN<\/code> for Chinese as used in China.<\/p>\n<p>\ud83d\udd17 Explore more language tags on <a href=\"https:\/\/en.wikipedia.org\/wiki\/List_of_ISO_639-1_codes\">Wikipedia<\/a> and find country codes through the ISO&#8217;s <a href=\"https:\/\/www.iso.org\/obp\/ui\/#search\">search tool<\/a>.<\/p>\n<p>Now to add some translation strings. By default, Laravel expects translation files to sit in the <code>lang<\/code> directory under the project root. Let\u2019s create this directory, along with a subdirectory for each locale.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">.\n\u2514\u2500\u2500 lang\n    \u251c\u2500\u2500 ar_EG\n    \u2502   \u2514\u2500\u2500 <span class=\"hljs-comment\"># We'll place Arabic translation files here.<\/span>\n    \u2514\u2500\u2500 en_US\n        \u2514\u2500\u2500 <span class=\"hljs-comment\"># We'll place English translation files here.<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_0d29b5a8c26031eac553255571c19514\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can have as many locale subdirectories under <code>lang<\/code> as we want; Laravel will automatically pick them up. Under each locale subdirectory, we can have one or more translation files. Let\u2019s create our first.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ lang\/en_US\/global.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> &#91;\n  <span class=\"hljs-string\">'app_name'<\/span> =&gt; <span class=\"hljs-string\">'bookavel'<\/span>,\n];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_f36dc8e6531b1478c1e0ce3a1a745581\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ lang\/ar_EG\/global.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> &#91;\n  <span class=\"hljs-string\">'app_name'<\/span> =&gt; <span class=\"hljs-string\">'\u0628\u0648\u0643\u0627\u06a4\u0650\u0644'<\/span>,\n];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_4fe9a34403be49891b43bc50c6107aeb\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Translation files return PHP arrays with simple key\/value pairs. To use them, we can call the <code>__()<\/code> translation function in our code. Let\u2019s translate the app name in our header component to demonstrate.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">{{-- resources\/views\/components\/layout\/header.blade.php --}}\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.logo<\/span> \/&gt;<\/span>\n    \n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"{{ route('root') }}\"<\/span>&gt;<\/span>\n-       bookavel\n+       {{ __('global.app_name') }}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-comment\">&lt;!-- ... --&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_4c755de1bf597eddfb2226b9f2b5e298\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The <code>__()<\/code> function takes a translation key in the form <code>file.key<\/code>.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong> We omit CSS styles here for brevity, unless they relate directly to i18n.<\/p>\n<p>Since we set the\u00a0<code>APP_LOCALE<\/code> value to <code>en_US<\/code> in our <code>.env<\/code> file, the <code>lang\/en_US\/global.php<\/code> file will be loaded and used. If we refresh our app now, it should look the same.<\/p>\n<p>Now let\u2019s set our <code>APP_LOCALE<\/code> to Arabic and reload the app.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># .env<\/span>\n\n  <span class=\"hljs-comment\"># ...<\/span>\n  APP_TIMEZONE=UTC\n  APP_URL=http:<span class=\"hljs-comment\">\/\/localhost<\/span>\n\n- APP_LOCALE=en_US\n+ APP_LOCALE=ar_EG\n  APP_FALLBACK_LOCALE=en_US\n  APP_FAKER_LOCALE=en_US\n\n  <span class=\"hljs-comment\"># ...<\/span>\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_ab9231f81b0bfa6cf8920c120e04ee30\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<figure id=\"attachment_92117\" aria-describedby=\"caption-attachment-92117\" style=\"width: 546px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-92117 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/app-name-ar.png\" alt=\"The navigation bar of our app with the brand name shown in Arabic.\" width=\"546\" height=\"132\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/app-name-ar.png 546w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/app-name-ar-300x73.png 300w\" sizes=\"(max-width: 546px) 100vw, 546px\" \/><figcaption id=\"caption-attachment-92117\" class=\"wp-caption-text\">Et voila! Our app name in Arabic!<\/figcaption><\/figure>\n<p>That\u2019s basic string translation in a nutshell. We can use as many files as we want under our locale directories. Let\u2019s add a couple for navigation links.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ lang\/en_US\/nav.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> &#91;\n  <span class=\"hljs-string\">'books'<\/span> =&gt; <span class=\"hljs-string\">'Books'<\/span>,\n  <span class=\"hljs-string\">'about'<\/span> =&gt; <span class=\"hljs-string\">'About us'<\/span>,\n];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b1d7e260ff95620c3adc6873cb85d708\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n\n<div id=\"acf\/text-block_f36dc8e6531b1478c1e0ce3a1a745581\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ lang\/ar_EG\/nav.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> &#91;\n  <span class=\"hljs-string\">'books'<\/span> =&gt; <span class=\"hljs-string\">'\u0627\u0644\u0643\u062a\u0628'<\/span>,\n  <span class=\"hljs-string\">'about'<\/span> =&gt; <span class=\"hljs-string\">'\u0646\u0628\u0630\u0629 \u0639\u0646\u0627'<\/span>,\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_4810e511d6867f87103b1dd801868578\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>In Blade templates, the <code>@lang('foo.bar')<\/code> directive is a shortcut for <code>{{ ('foo.bar) }}<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">{{-- resources\/views\/components\/layout\/header.blade.php --}}\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.logo<\/span> \/&gt;<\/span>\n    \n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"{{ route('root') }}\"<\/span>&gt;<\/span>\n        {{ __('global.app_name') }}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.nav-link<\/span> <span class=\"hljs-attr\">:href<\/span>=<span class=\"hljs-string\">\"route('books.index')\"<\/span>&gt;<\/span>\n-           Books\n+           @lang('nav.books')\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">x-layout.nav-link<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.nav-link<\/span> <span class=\"hljs-attr\">:href<\/span>=<span class=\"hljs-string\">\"route('about')\"<\/span>&gt;<\/span>\n-           About us\n+           @lang('nav.about')\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">x-layout.nav-link<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_fd886bb9dd4b11d7475296974d37bd31\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92123\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/nav-links-ar.png\" alt=\"Our navigation links shown in Arabic.\" width=\"496\" height=\"136\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/nav-links-ar.png 496w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/nav-links-ar-300x82.png 300w\" sizes=\"(max-width: 496px) 100vw, 496px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"missing-translations\"><\/span>Missing translations<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>If a translation is missing from our Arabic file, Laravel will fall back to the locale specified in <code>APP_FALLBACK_LOCALE<\/code>. Let&#8217;s test this by removing the Arabic translation for &#8220;about.&#8221;<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ lang\/ar_EG\/nav.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-string\">'books'<\/span> =&gt; <span class=\"hljs-string\">'\u0627\u0644\u0643\u062a\u0628'<\/span>,\n-   <span class=\"hljs-string\">'about'<\/span> =&gt; <span class=\"hljs-string\">'\u0646\u0628\u0630\u0629 \u0639\u0646\u0627'<\/span>,\n  ];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_90880eb347af48a05dcf6bacb62419eb\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92129\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/fallback-en.png\" alt=\"Our navigation links shown in Arabic, except the &quot;About us&quot; links, which is in English.\" width=\"582\" height=\"138\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/fallback-en.png 582w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/fallback-en-300x71.png 300w\" sizes=\"(max-width: 582px) 100vw, 582px\" \/><\/p>\n<p>The <code>en_US<\/code> value is used as a fallback for our missing translation.<\/p>\n<p>What if the <code>en_US<\/code> value itself is missing? In that case, Laravel shows us the translation key.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ lang\/en_US\/nav.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-string\">'books'<\/span> =&gt; <span class=\"hljs-string\">'Books'<\/span>,\n-   <span class=\"hljs-string\">'about'<\/span> =&gt; <span class=\"hljs-string\">'About us'<\/span>,\n  ];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e890eaa1fe21f8df36531c92b4ba7302\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92135\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/missing-translation.png\" alt=\"Our navigation links shown in Arabic, except the &quot;About us&quot; link, which reads &quot;nav.about.\" width=\"580\" height=\"130\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/missing-translation.png 580w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/missing-translation-300x67.png 300w\" sizes=\"(max-width: 580px) 100vw, 580px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"translation-strings-as-keys\"><\/span>Translation strings as keys<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Using translation strings as keys can clarify our views. To do this, we need to change how we define our translations. First, we\u2019ll pass English <strong>translation strings (verbatim)<\/strong> to <code>__()<\/code> and <code>@lang()<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">{{-- resources\/views\/components\/layout\/header.blade.php --}}\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.logo<\/span> \/&gt;<\/span>\n    \n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"{{ route('root') }}\"<\/span>&gt;<\/span>\n-       {{ __('global.app_name') }}\n+       {{ __('bookavel') }}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.nav-link<\/span> <span class=\"hljs-attr\">:href<\/span>=<span class=\"hljs-string\">\"route('books.index')\"<\/span>&gt;<\/span>\n-           @lang('nav.books')\n+           @lang('Books')\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">x-layout.nav-link<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.nav-link<\/span> <span class=\"hljs-attr\">:href<\/span>=<span class=\"hljs-string\">\"route('about')\"<\/span>&gt;<\/span>\n-           @lang('nav.about')\n+           @lang('About')\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">x-layout.nav-link<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7f391fc80d03a8507fdd979259fe2f04\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Instead of PHP files under locale directories, we use a single JSON translation file per locale.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ lang\/ar_EG.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"bookavel\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0648\u0643\u0627\u06a4\u0650\u0644\"<\/span>,\n  <span class=\"hljs-attr\">\"Books\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0643\u062a\u0628\"<\/span>,\n  <span class=\"hljs-attr\">\"About us\"<\/span>: <span class=\"hljs-string\">\"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_ca9db3877352efab5c90bd7f8c2f3361\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We don\u2019t need to add an <code>en_US.json<\/code> file. Laravel\u2019s fallback logic will ensure <code>enUS<\/code> translations are shown when <code>en_US<\/code> is the active locale.<\/p>\n<p>When not given a file prefix like <code>global.<\/code> or <code>nav.<\/code>, the <code>()<\/code> function will pull translations from the <code>{locale}.json<\/code> file (where <code>{locale}<\/code> is the active locale). If the file or translation isn\u2019t found, Laravel will default to the given key itself.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92141\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/translation-values-as-keys.png\" alt=\"The image shows a comparison between two versions of the same web app header, one for the &#96;en-US&#96; locale and the other for &#96;ar-EG&#96;. The &#96;en-US&#96; version displays the app name &quot;bookavel&quot; with navigation links &quot;Books&quot; and &quot;About us.&quot; The &#96;ar-EG&#96; version shows the same content in Arabic, with the app name and navigation links translated. The text explains that the &#96;en-US&#96; version uses keys provided to the translation function &#96;__()&#96;, while the &#96;ar-EG&#96; version uses corresponding values from an &#96;ar-EG.json&#96; file.\" width=\"1000\" height=\"483\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/translation-values-as-keys.png 1000w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/translation-values-as-keys-300x145.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/translation-values-as-keys-768x371.png 768w\" sizes=\"(max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We\u2019ll use the translation values as keys (JSON) method moving forward. However, JSON files can be used along with PHP files. If you call <code>('foo.bar')<\/code>, Laravel will look for a <code>lang\/{locale}\/foo.php<\/code> file with a <code>bar<\/code> key inside. A call like <code>('Baz')<\/code> will cause Laravel to look for a <code>lang\/{locale}.json<\/code> file with a <code>Baz<\/code> key inside. Both methods can work side-by-side.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Read more about <a href=\"https:\/\/laravel.com\/docs\/11.x\/localization#defining-translation-strings\">Defining Translation Strings<\/a> in the Laravel docs.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-extract-translations-from-my-code\"><\/span>How do I extract translations from my code?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Instead of manually adding each translation to each locale file (and we might have many), we can use a CLI tool to do it automatically.<\/p>\n<p><a href=\"https:\/\/github.com\/kkomelin\">Konstantin Komelin<\/a>\u2019s <a href=\"https:\/\/github.com\/kkomelin\/laravel-translatable-string-exporter\">laravel-translatable-string-exporter<\/a> is a popular choice for string extraction. Using this package, a simple Artisan command will scan our code and place translated strings into locale files. Let\u2019s install it as a dev dependency.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">composer <span class=\"hljs-built_in\">require<\/span> kkomelin\/laravel-translatable-string-exporter --dev\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_740bccde86d38d44092f3bfe2dad6d52\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now let\u2019s hop into our book index view and mark a string for translation to see the new CLI tool in action.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">{{-- resources\/views\/books\/index.blade.php --}}\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.main<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Books\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n-     Recently added   \n+     {{ __('Recently added') }}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      @foreach ($books as $book)\n        {{-- ... --}}\n      @endforeach\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">x-layout.main<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a76523dd709af3a02faab7ca5173c7d7\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now to run the Artisan command.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">php artisan translatable:<span class=\"hljs-keyword\">export<\/span> ar_EG<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_9352b4cde6aa1aa0cfbd247b9fb19f38\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>As you\u2019ve probably guessed, the <code>ar_EG<\/code> argument tells <code>translatable:export<\/code> to dump new translations into our <code>lang\/ar_EG.json<\/code> file.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We can have as many comma-separated locales as we want here. For example, <code>php artisan translatable:export ar_EG,de_DE,hi_IN<\/code> will export new translations to <code>ar_EG.json<\/code>, <code>de_DE.json<\/code> and <code>hiI_N.json<\/code>, respectively.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"> <span class=\"hljs-comment\">\/\/ lang\/ar_EG.json<\/span>\n \n  {\n+     <span class=\"hljs-string\">\"Recently added\"<\/span>: <span class=\"hljs-string\">\"Recently added\"<\/span>,\n      <span class=\"hljs-string\">\"Books\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0643\u062a\u0628\"<\/span>,\n      <span class=\"hljs-string\">\"About us\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0630\u0629 \u0639\u0646\u0627\"<\/span>,\n      <span class=\"hljs-string\">\"bookavel\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0648\u0643\u0627\u06a4\u0650\u0644\"<\/span>\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_00714661ae21e671a7367873ab38c5f4\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The extracted string duplicates the key as its value. We can replace this with the appropriate translation.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"> <span class=\"hljs-comment\">\/\/ lang\/ar_EG.json<\/span>\n \n  {\n-   <span class=\"hljs-string\">\"Recently added\"<\/span>: <span class=\"hljs-string\">\"Recently added\"<\/span>,\n+   <span class=\"hljs-string\">\"Recently added\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u062c\u062f\u064a\u062f \u0639\u0646\u062f\u0646\u0627\"<\/span>,\n    <span class=\"hljs-string\">\"Books\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0643\u062a\u0628\"<\/span>,\n    <span class=\"hljs-string\">\"About us\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0630\u0629 \u0639\u0646\u0627\"<\/span>,\n    <span class=\"hljs-string\">\"bookavel\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0648\u0643\u0627\u06a4\u0650\u0644\"<\/span>\n }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2ae8f08708b03732695b68fe2ac50315\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0For larger projects, we can upload our translation files to a software localization platform like <a href=\"https:\/\/phrase.com\/platform\/strings\/\">Phrase Strings<\/a> so that translators can work on them.<\/p>\n<p>By default, <code>translatable:export<\/code> will scan all PHP and JS files under the <code>app<\/code> and <code>resources<\/code> directories for any calls to <code>__()<\/code>, <code>_t()<\/code>, or <code>@lang()<\/code>, pulling any keys out of them. You can configure this behavior by <a href=\"https:\/\/github.com\/kkomelin\/laravel-translatable-string-exporter?tab=readme-ov-file#configuration\">publishing the package\u2019s config file<\/a>. We\u2019ll do this later.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Heads up \u00bb<\/strong> We haven\u2019t mentioned the <code>trans()<\/code> function. <code>()<\/code> is an alias of <code>trans()<\/code>. If you\u2019re using <code>trans()<\/code> note that <code>translatable:export<\/code> doesn\u2019t scan for it by default.<\/p>\n<p>Moving translation strings into as many locale files as our app needs is now significantly streamlined.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/github.com\/kkomelin\/laravel-translatable-string-exporter?tab=readme-ov-file#translatable-string-exporter-for-laravel\">Check out the laravel-translatable-string-exporter documentation<\/a> for the details on how the package works.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-routes\"><\/span>How do I localize routes?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>One of the most common ways to set the app\u2019s active locale is via routes. This could be a locale route param: <code>\/en_US\/foo<\/code> and <code>\/ar_EG\/foo<\/code>, where the first route segment sets the active locale. Visiting <code>en_US\/foo<\/code> shows the <code>foo<\/code> page in English; visiting <code>\/ar_EG\/foo<\/code> shows it in Arabic. We\u2019ll use this localized routing strategy in this guide.<\/p>\n<p>We\u2019ll also grab a package to carry the heavy lifting for localized routing. We could roll our own, but <a href=\"https:\/\/github.com\/codezero-be\/laravel-localized-routes\">CodeZero\u2019s Laravel Localized Routes<\/a> does a great job of it and saves us some time.<\/p>\n<p>First, let\u2019s install the package from the command line:<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">composer <span class=\"hljs-built_in\">require<\/span> codezero\/laravel-localized-routes<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7183a7e791bb796e686bd8fba8f83ea4\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0When asked to trust the package to execute code, enter <code>y<\/code> for \u201cyes\u201d.<\/p>\n<p>Next, let\u2019s publish the package\u2019s config file so we can modify it.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">php artisan vendor:publish --provider=<span class=\"hljs-string\">\"CodeZero\\LocalizedRoutes\\LocalizedRoutesServiceProvider\"<\/span> --tag=<span class=\"hljs-string\">\"config\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_f36dc8e6531b1478c1e0ce3a1a745581\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/localized-routes.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n\n    <span class=\"hljs-comment\">\/**\n     * The locales you wish to support.\n     *\/<\/span>\n-   <span class=\"hljs-string\">'supported_locales'<\/span> =&gt; &#91;],\n+   <span class=\"hljs-string\">'supported_locales'<\/span> =&gt; &#91;<span class=\"hljs-string\">'en_US'<\/span>, <span class=\"hljs-string\">'ar_EG'<\/span>],\n  \n    <span class=\"hljs-comment\">\/**\n     * The fallback locale to use when generating a route URL\n     * and the provided locale is not supported.\n     *\/<\/span>\n-   <span class=\"hljs-string\">'fallback_locale'<\/span> =&gt; <span class=\"hljs-keyword\">null<\/span>,\n+   <span class=\"hljs-string\">'fallback_locale'<\/span> =&gt; <span class=\"hljs-string\">'en_US'<\/span>,\n\n    <span class=\"hljs-comment\">\/**\n     * If you have a main locale, and you want to omit\n     * its slug from the URL, specify it here.\n     *\/<\/span>\n    <span class=\"hljs-string\">'omitted_locale'<\/span> =&gt; <span class=\"hljs-keyword\">null<\/span>,\n\n    <span class=\"hljs-comment\">\/**\n     * Set this option to true if you want to redirect URLs\n     * without a locale slug to their localized version.\n     * You need to register the fallback route for this to work.\n     *\/<\/span>\n-   <span class=\"hljs-string\">'redirect_to_localized_urls'<\/span> =&gt; <span class=\"hljs-keyword\">false<\/span>,\n+   <span class=\"hljs-string\">'redirect_to_localized_urls'<\/span> =&gt; <span class=\"hljs-keyword\">true<\/span>,\n \n    <span class=\"hljs-string\">'redirect_status_code'<\/span> =&gt; <span class=\"hljs-number\">301<\/span>,\n\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  ];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c85db4a5ff53e2dc62ca889a910e7ee6\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Any locale not present in <code>supported_locales<\/code> will trigger a 404 Not Found if its slug is used.<\/p>\n<p>In the above, <code> redirect_to_localized_urls<\/code> and <code>fallback_locale<\/code> work together; if we visit <code>\/about<\/code>, we\u2019re automatically redirected to <code>\/en_US\/about<\/code>.<\/p>\n<p>But for any of this to work we need two more pieces: middleware and route config. Let\u2019s add the middleware first:<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ bootstrap\/app.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Application<\/span>;\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Configuration<\/span>\\<span class=\"hljs-title\">Exceptions<\/span>;\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Configuration<\/span>\\<span class=\"hljs-title\">Middleware<\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> Application::configure(basePath: dirname(<span class=\"hljs-keyword\">__DIR__<\/span>))\n    -&gt;withRouting(\n      web: <span class=\"hljs-keyword\">__DIR__<\/span> . <span class=\"hljs-string\">'\/..\/routes\/web.php'<\/span>,\n      commands: <span class=\"hljs-keyword\">__DIR__<\/span> . <span class=\"hljs-string\">'\/..\/routes\/console.php'<\/span>,\n      health: <span class=\"hljs-string\">'\/up'<\/span>,\n    )\n    -&gt;withMiddleware(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Middleware $middleware)<\/span> <\/span>{\n+     $middleware-&gt;web(remove: &#91;\n+       \\Illuminate\\Routing\\Middleware\\SubstituteBindings::class,\n+     ]);\n+     $middleware-&gt;web(append: &#91;\n+       \\CodeZero\\LocalizedRoutes\\Middleware\\SetLocale::class,\n+       \\Illuminate\\Routing\\Middleware\\SubstituteBindings::class,\n+     ]);\n    })\n    -&gt;withExceptions(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Exceptions $exceptions)<\/span> <\/span>{\n      <span class=\"hljs-comment\">\/\/<\/span>\n    })-&gt;create();\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_64e3937762bcb7be747caf9fc75d8182\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The above ensures that the locale is set from the route <strong>before<\/strong> Laravel\u2019s <a href=\"https:\/\/laravel.com\/docs\/11.x\/routing#route-model-binding\">route model binding<\/a> kicks in. This is important if you want to, say, translate article slugs. We won\u2019t do this in this guide, but it\u2019s good to know.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Read the <a href=\"https:\/\/github.com\/codezero-be\/laravel-localized-routes?tab=readme-ov-file#-translate-parameters-with-route-model-binding\">Translate Parameters with Route Model Binding<\/a> and <a href=\"https:\/\/github.com\/codezero-be\/laravel-localized-routes?tab=readme-ov-file#-translate-hard-coded-uri-slugs\">Translate Hard-Coded URI Slugs<\/a> sections of the docs for more info.<\/p>\n<p>OK, now let\u2019s update our routes to add localization:<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">BookController<\/span>;\n+ <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">CodeZero<\/span>\\<span class=\"hljs-title\">LocalizedRoutes<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">FallbackController<\/span>;\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Route<\/span>;\n\n+ Route::localized(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    Route::get(<span class=\"hljs-string\">'\/'<\/span>, &#91;BookController::class, <span class=\"hljs-string\">'index'<\/span>])-&gt;name(<span class=\"hljs-string\">'root'<\/span>);\n    Route::resource(<span class=\"hljs-string\">'books'<\/span>, BookController::class)-&gt;only(<span class=\"hljs-string\">'index'<\/span>, <span class=\"hljs-string\">'show'<\/span>);\n\n    Route::get(<span class=\"hljs-string\">'\/about'<\/span>, fn () =&gt; view(<span class=\"hljs-string\">'about'<\/span>))-&gt;name(<span class=\"hljs-string\">'about'<\/span>);\n+ });\n\n+ Route::fallback(FallbackController::class);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c242728897fe52e35b1060d1e57a417d\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Wrapping our routes with <code>Route::localized<\/code> adds our locale slugs (<code>en_US<\/code>, <code>ar_EG<\/code>) before each route. We also specify the <code>FallbackController::class<\/code> to handle Laravel\u2019s catch-all <code>fallback<\/code> route: This redirects <code>\/<\/code> to <code>\/en_US<\/code>.<\/p>\n<p>If we visit the root (<code>\/<\/code>) route of our app now, we should indeed be redirected to <code>\/en_US<\/code>.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Heads up \u00bb<\/strong> The Localized Routes package performs locale auto-detection based on the user\u2019s browser settings. If your browser languages list a locale supported by your app, you might get redirected to that locale URI instead. We\u2019ll tackle locale auto-detection in the next section.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92148\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/en-root-route-1024x187.png\" alt=\"Our app's home page shown with English nav links; the browser's address bar reads &quot;localhost\/en_US&quot;\" width=\"1024\" height=\"187\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/en-root-route-1024x187.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/en-root-route-300x55.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/en-root-route-768x140.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/en-root-route.png 1334w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>We can manually visit <code>\/ar_EG<\/code>\u00a0to see our home page translated into Arabic. Note how the Localized Link package sets the active app locale to match the URL slug behind the scenes. This means our <code>ar_EG.json<\/code> strings are used when we hit a route prefixed with <code>ar_EG<\/code>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92154\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/ar-root-route-1024x198.png\" alt=\"Our app's home page shown with Arabic nav links; the browser's address bar reads &quot;localhost\/ar_EG&quot;\" width=\"1024\" height=\"198\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/ar-root-route-1024x198.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/ar-root-route-300x58.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/ar-root-route-768x149.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/ar-root-route.png 1334w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0You can re-map the locale URI slugs to <a href=\"https:\/\/github.com\/codezero-be\/laravel-localized-routes\/tree\/master?tab=readme-ov-file#custom-slugs\">Custom Slugs<\/a> if you prefer.<\/p>\n<p>We can also run the <code>route:list<\/code> artisan command to see our new localized routes.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92160\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/localized-route-list-1024x294.png\" alt=\"The image is a screenshot of console output from running the &#96;php artisan route:list&#96; command in a Laravel application. It displays a list of 10 routes for two locales: &#96;ar_EG&#96; and &#96;en_US&#96;. The routes include paths like &#96;\/about&#96;, &#96;\/books&#96;, and &#96;\/books\/{book}&#96;, mapped to the &#96;BookController&#96; methods (&#96;index&#96;, &#96;show&#96;). There is also a fallback route &#96;{fallbackPlaceholder}&#96; mapped to the &#96;FallbackController&#96;. Each route is prefixed by either &#96;GET|HEAD&#96;, indicating the HTTP methods supported, and is followed by the corresponding localized route name (e.g., &#96;ar_EG.root&#96;, &#96;en_US.books.show&#96;).\" width=\"1024\" height=\"294\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/localized-route-list-1024x294.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/localized-route-list-300x86.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/localized-route-list-768x221.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/localized-route-list-1536x442.png 1536w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/localized-route-list.png 1586w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"localized-links-and-urls\"><\/span>Localized links and URLs<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The Localized Routes package works with named routes out of the box: If we\u2019re currently on an <code>en_US<\/code>\u00a0route then <code>route('about')<\/code> outputs <code>'\/en_US\/about'<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-comment\">\/\/ ...<\/span>\n\nRoute::localized(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n  <span class=\"hljs-comment\">\/\/ Explicit naming with `name()`<\/span>\n  Route::get(<span class=\"hljs-string\">'\/'<\/span>, &#91;BookController::class, <span class=\"hljs-string\">'index'<\/span>])-&gt;name(<span class=\"hljs-string\">'root'<\/span>);\n\n  <span class=\"hljs-comment\">\/\/ Implicit naming with `resource()` generates names:<\/span>\n  <span class=\"hljs-comment\">\/\/ `books.index` and `books.show`<\/span>\n  Route::resource(<span class=\"hljs-string\">'books'<\/span>, BookController::class)-&gt;only(<span class=\"hljs-string\">'index'<\/span>, <span class=\"hljs-string\">'show'<\/span>);\n\n  Route::get(<span class=\"hljs-string\">'\/about'<\/span>, fn () =&gt; view(<span class=\"hljs-string\">'about'<\/span>))-&gt;name(<span class=\"hljs-string\">'about'<\/span>);\n});\n\n<span class=\"hljs-comment\">\/\/ ...<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_946ab467017df81feabcf4ff2da1500c\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Let\u2019s take the books resource as an example:<\/p>\n<ul>\n<li>If we&#8217;re on <code>\/en_US<\/code> and call <code>route('books.index')<\/code> in one of our views, it will output <code>`\/en_US\/books`<\/code>.<\/li>\n<li>If we&#8217;re on <code>\/ar_EG<\/code>\u00a0and call <code>route('books.index')<\/code> in one of our views, it will output <code>`\/ar_EG\/books`<\/code>.<\/li>\n<li>If we&#8217;re on <code>\/en_US<\/code> and call <code>route('books.show', 1)<\/code> in one of our views, it will output <code>`\/en_US\/books\/1`<\/code>.<\/li>\n<li>If we&#8217;re on <code>\/ar_EG<\/code>\u00a0and call <code>route('books.show', 1)<\/code> in one of our views, it will output <code>`\/ar_EG\/books\/1`<\/code>.<\/li>\n<\/ul>\n<p>If you want to force a route, you can call <code>route('about', [], true, 'ar_EG')<\/code>: This outputs <code>'\/ar_EG\/about'<\/code> even if we\u2019re on an <code>en_US<\/code> page.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0See the <a href=\"https:\/\/github.com\/codezero-be\/laravel-localized-routes\/tree\/master?tab=readme-ov-file#-generate-urls-for-a-specific-locale\">Generate URLs for a Specific Locale<\/a> section of the docs.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0URL slugs are one strategy for localized routes. Another is custom domains e.g. <code>en.example.com<\/code>, <code>ar.example.com<\/code>. The Localized Routes package supports the domain strategy: See the <a href=\"https:\/\/github.com\/codezero-be\/laravel-localized-routes\/tree\/master?tab=readme-ov-file#custom-domains\">Custom Domains<\/a> section of the docs for more info.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-detect-the-users-locale\"><\/span>How do I detect the user\u2019s locale?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>CodeZero\u2019s Localized Routes package has an array of locale detectors, and locale stores, set up for us by default.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/localized-routes.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> &#91;\n\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  <span class=\"hljs-comment\">\/**\n   * The detectors to use to find a matching locale.\n   * These will be executed in the order that they are added to the array!\n   *\/<\/span>\n  <span class=\"hljs-string\">'detectors'<\/span> =&gt; &#91;\n    <span class=\"hljs-comment\">\/\/ required for scoped config<\/span>\n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\RouteActionDetector::class, \n    <span class=\"hljs-comment\">\/\/ required<\/span>\n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\UrlDetector::class, \n    <span class=\"hljs-comment\">\/\/ required for omitted locale<\/span>\n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\OmittedLocaleDetector::class,\n    \n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\UserDetector::class,\n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\SessionDetector::class,\n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\CookieDetector::class,\n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\BrowserDetector::class,\n    \n    <span class=\"hljs-comment\">\/\/ required<\/span>\n    CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\AppDetector::class, \n  ],\n\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  <span class=\"hljs-comment\">\/**\n   * The stores to store the first matching locale in.\n   *\/<\/span>\n   <span class=\"hljs-string\">'stores'<\/span> =&gt; &#91;\n      CodeZero\\LocalizedRoutes\\Middleware\\Stores\\SessionStore::class,\n      CodeZero\\LocalizedRoutes\\Middleware\\Stores\\CookieStore::class,\n      \n      <span class=\"hljs-comment\">\/\/ required<\/span>\n      CodeZero\\LocalizedRoutes\\Middleware\\Stores\\AppStore::class,\n   ],\n];\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_bff557ae210351a1ef1e699ae58de81d\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The detectors will run in order, and the first detector to find a locale will determine the active locale for the request, bypassing the rest of the detectors. Stores save a matched locale for a user\u2019s subsequent site visits.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0The <a href=\"https:\/\/github.com\/codezero-be\/laravel-localized-routes\/tree\/master?tab=readme-ov-file#detectors\">Detectors<\/a> section of the docs also has a table that further explains how they all work.<\/p>\n<p>Before we proceed, let\u2019s remove the detectors and stores we\u2019re not using in this app.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/localized-routes.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n    <span class=\"hljs-comment\">\/**\n     * The detectors to use to find a matching locale.\n     * These will be executed in the order that they are added to the array!\n     *\/<\/span>\n    <span class=\"hljs-string\">'detectors'<\/span> =&gt; &#91;\n      <span class=\"hljs-comment\">\/\/ required for scoped config<\/span>\n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\RouteActionDetector::class, \n      <span class=\"hljs-comment\">\/\/ required<\/span>\n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\UrlDetector::class, \n-     <span class=\"hljs-comment\">\/\/ required for omitted locale<\/span>\n-     CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\OmittedLocaleDetector::class,\n    \n-     CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\UserDetector::class,\n-     CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\SessionDetector::class,\n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\CookieDetector::class,\n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\BrowserDetector::class,\n    \n      <span class=\"hljs-comment\">\/\/ required<\/span>\n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\AppDetector::class, \n    ],\n\n   <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n    <span class=\"hljs-comment\">\/**\n     * The stores to store the first matching locale in.\n     *\/<\/span>\n     <span class=\"hljs-string\">'stores'<\/span> =&gt; &#91;\n-       CodeZero\\LocalizedRoutes\\Middleware\\Stores\\SessionStore::class,\n        CodeZero\\LocalizedRoutes\\Middleware\\Stores\\CookieStore::class,\n      \n        <span class=\"hljs-comment\">\/\/ required<\/span>\n        CodeZero\\LocalizedRoutes\\Middleware\\Stores\\AppStore::class,\n     ],\n  ];\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_946cbb6446cf5e3d856b7af2bb1a59be\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We\u2019re not using an omitted locale in this app. An omitted locale would be used for URLs without a locale slug. Since we\u2019re forcing locale slugs in our app, we can safely remove the <code>OmittedLocaleDetector<\/code> here.<\/p>\n<p>Since we don\u2019t have authentication, and we\u2019re not using Laravel\u2019s <code>User<\/code> model, we can remove the <code>UserDetector<\/code>.<\/p>\n<p>The <code>SessionDetector<\/code> is redundant since we\u2019ll utilize the <code>CookieDetector<\/code> and <code>CookieStore<\/code> to save the user\u2019s locale preference. We can safely remove the <code>SessionDetector<\/code> and <code>SessionStore<\/code> here.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"new-user-visit\"><\/span>New user visit<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Now let\u2019s go through the scenario of a fresh visit to our site, illustrating how the detector cascade would work with our current configuration.<\/p>\n<ol>\n<li>A new user visits our site\u2019s root route (<code>\/<\/code>).<\/li>\n<li>No locale slug is on the <code>\/<\/code> route, so the <code>RouteActionDetector<\/code> and <code>UrlDetector<\/code> have nothing to detect; they cascade down.<\/li>\n<li>This is the user\u2019s first visit, so we haven\u2019t stored their locale preference with the <code>CookieStore<\/code> yet. The <code>CookieDetector<\/code> has nothing to detect; it cascades down.<\/li>\n<li>The <code>BrowserDetector<\/code> kicks in and tries to match one of the user\u2019s configured browser locales with one of our supported locales. If it succeeds, the matched locale becomes the active locale for the request. If it fails, it cascades down.<\/li>\n<li>The <code>AppDetector<\/code> uses the <code>locale<\/code> configured in <code>config\/app.php<\/code> as the final fallback.<\/li>\n<\/ol>\n<p>Of particular interest to us is the <code>BrowserDetector<\/code>: It detects the locale based on the request\u2019s <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Accept-Language\">Accept-Language<\/a> HTTP header, which corresponds to the user\u2019s configured language list in the browser.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Our guide, <a href=\"https:\/\/phrase.com\/blog\/posts\/detecting-a-users-locale\/\">Detecting a User\u2019s Locale in a Web App<\/a>, goes into Accept-Language and browser locale detection in depth.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"rolling-our-own-browser-detector\"><\/span>Rolling our own browser detector<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>CodeZero\u2019s built-in <code>BrowserDetector<\/code> works reasonably well but doesn\u2019t normalize locales during detection. For example, it doesn\u2019t match <code>ar-eg<\/code> in an Accept-Language header to our configured <code>ar_EG<\/code> locale. The problem is that <a href=\"https:\/\/laravel.com\/docs\/11.x\/localization#using-short-keys\">Laravel\u2019s documentation instructs us to use ISO 15897 locale naming<\/a> with underscores (<code>ar_EG<\/code>). It\u2019s common for browsers to use a dash to separate locales and regions, e.g. <code>ar-eg<\/code>. So we\u2019re between a rock and a hard place here.<\/p>\n<p>The built-in <code>BrowserDetector<\/code> doesn\u2019t attempt a best match, either. So it won\u2019t match a user\u2019s configured <code>en-gb<\/code> to our <code>en_US<\/code> locale, even though British people can read American English just fine.<\/p>\n<p>Thankfully, it\u2019s easy enough to roll our own detector that solves these problems. We\u2019ll begin by installing the popular <code>http-accept-language<\/code> PHP package by <a href=\"https:\/\/github.com\/BaguettePHP\">Baguette HQ<\/a>. This package will parse the Accept-Language header and provide it as a PHP array.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">composer <span class=\"hljs-built_in\">require<\/span> zonuexe\/http-accept-language<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_f3ac1f80ad945953fcab4a8fb9cb2d28\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can use this package to write our own browser detector that performs a best match against the Accept-Language header.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Middleware\/LocaleDetectors\/AcceptLanguageDetector.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Middleware<\/span>\\<span class=\"hljs-title\">LocaleDetectors<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">CodeZero<\/span>\\<span class=\"hljs-title\">LocalizedRoutes<\/span>\\<span class=\"hljs-title\">Middleware<\/span>\\<span class=\"hljs-title\">Detectors<\/span>\\<span class=\"hljs-title\">Detector<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Arr<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Teto<\/span>\\<span class=\"hljs-title\">HTTP<\/span>\\<span class=\"hljs-title\">AcceptLanguage<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AcceptLanguageDetector<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">Detector<\/span>\n<\/span>{\n  <span class=\"hljs-comment\">\/**\n   * Detect the locale.\n   *\n   * <span class=\"hljs-doctag\">@return<\/span> string|array|null\n   *\/<\/span>\n  <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">detect<\/span><span class=\"hljs-params\">()<\/span>\n  <\/span>{\n    <span class=\"hljs-comment\">\/\/ Get locales from the Accept-Language header<\/span>\n    $accept_locales = AcceptLanguage::get();\n    \n    <span class=\"hljs-comment\">\/\/ Get the languages parts only<\/span>\n    $languages = array_unique(Arr::pluck(\n      $accept_locales, <span class=\"hljs-string\">'language'<\/span>));\n\n    $supported_locales =\n      config(<span class=\"hljs-string\">'localized-routes.supported_locales'<\/span>);\n\n    <span class=\"hljs-keyword\">foreach<\/span> ($languages <span class=\"hljs-keyword\">as<\/span> $language) {\n      <span class=\"hljs-keyword\">foreach<\/span> ($supported_locales <span class=\"hljs-keyword\">as<\/span> $locale) {\n        <span class=\"hljs-keyword\">if<\/span> (str_starts_with(\n          $locale,\n          strtolower($language))) {\n          <span class=\"hljs-keyword\">return<\/span> $locale;\n        }\n      }\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">null<\/span>;\n  }\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7c4c199b76bffe5e1d93fab32ca5122e\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We must implement CodeZero\u2019s <code>Detector<\/code> interface, which defines a <code>detect<\/code> method for our custom detector to work with the Localized Routes package. In our <code>detect()<\/code>, we first grab the user\u2019s configured locales using the <code>http-accept-language<\/code> parser package and do a best match against our app\u2019s supported locales. Our detector will match the first case-insensitive <strong>language<\/strong> <strong>code<\/strong> regardless of region. If a user has set <code>ar-SA<\/code> (Arabic Saudi-Arabia) in their browser, they will be matched with our supported <code>ar_EG<\/code> and shown our site in Arabic.<\/p>\n<p>Let\u2019s wire up our new detector to see it in action.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/localized-routes.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n \n    <span class=\"hljs-string\">'detectors'<\/span> =&gt; &#91;\n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\RouteActionDetector::class, \n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\UrlDetector::class, \n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\CookieDetector::class,\n-     CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\BrowserDetector::class\n+     App\\Http\\Middleware\\LocaleDetectors\\AcceptLanguageDetector::class,\n      CodeZero\\LocalizedRoutes\\Middleware\\Detectors\\AppDetector::class,\n    ],\n\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n    ],\n\n  ];\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_60a0b404d269842e93ce6dec64b7aa8f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We get our desired behavior by swapping out the built-in <code>BrowserDetector<\/code> with our own <code>AcceptLanuageDetector<\/code>. If a user has <code>en<\/code>, <code>en-us<\/code>, or <code>en_GB<\/code> as a preferred locale in her browser settings, she will be redirected from <code>\/<\/code> to <code>\/en_US<\/code> during her first site visit. If another user has <code>ar<\/code>, <code>ar-ma<\/code>, or <code>ar_SA<\/code> as a preferred locale, he will be redirected from <code>\/<\/code> to <code>\/ar_EG<\/code>.<\/p>\n<p>Of course, we need a language switcher UI to let the user override the locale we\u2019ve chosen for them. We also need to cover how the locale is stored in a cookie. We\u2019ll do this shortly.<\/p>\n<p>But first, a quick detour to take care of locale direction.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-work-with-right-to-left-languages\"><\/span>How do I work with right-to-left languages?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Before we get to the language switcher, let\u2019s deal with right-to-left (RTL) languages. Our Arabic content flows left-to-right, yet Arabic is read right-to-left.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0We dive into locale direction, writing modes, fonts, and more in our guide to <a href=\"https:\/\/phrase.com\/blog\/posts\/how-do-i-use-a-css-file-for-site-localization\/\">CSS Localization<\/a>.<\/p>\n<p>We can deal with this by creating a helper function that returns the active locale\u2019s direction, and use this direction in our pages\u2019 <code>&lt;html dir&gt;<\/code> attribute. Let&#8217;s start by creating a config file to store our custom i18n settings, such as locale directions.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/i18n.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">return<\/span> &#91;\n  <span class=\"hljs-string\">'rtl_locales'<\/span> =&gt; &#91;\n      <span class=\"hljs-string\">'ar_EG'<\/span>,\n  ],\n];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_17e406cf4236e3d1aec20a24a4ca634e\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Most locales are LTR (left-to-right), so we can list the RTL locales and default to LTR. Currently, we only have one RTL locale, <code>ar_EG<\/code>.<\/p>\n<p>We can use this new config in a simple <code>locale_dir()<\/code> function, which we\u2019ll place in a new <code>i18n-functions<\/code> file.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/i18n-functions.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">locale_dir<\/span><span class=\"hljs-params\">()<\/span> : <span class=\"hljs-title\">string<\/span>\n<\/span>{\n  $rtl_locales = config(<span class=\"hljs-string\">'i18n.rtl_locales'<\/span>);\n  $locale = app()-&gt;getLocale();\n\n  <span class=\"hljs-keyword\">return<\/span> in_array($locale, $rtl_locales)\n    ? <span class=\"hljs-string\">\"rtl\"<\/span>\n    : <span class=\"hljs-string\">\"ltr\"<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_d1ffe8ed32e145e6023a79115a4edf95\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Simple enough. We need to remember to register our new file for autoloading, of course.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ composer.json<\/span>\n\n{\n  <span class=\"hljs-string\">\"name\"<\/span>: <span class=\"hljs-string\">\"laravel\/laravel\"<\/span>,\n\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  <span class=\"hljs-string\">\"autoload\"<\/span>: {\n    <span class=\"hljs-string\">\"psr-4\"<\/span>: {\n      <span class=\"hljs-string\">\"App\\\\\"<\/span>: <span class=\"hljs-string\">\"app\/\"<\/span>,\n      <span class=\"hljs-string\">\"Database\\\\Factories\\\\\"<\/span>: <span class=\"hljs-string\">\"database\/factories\/\"<\/span>,\n      <span class=\"hljs-string\">\"Database\\\\Seeders\\\\\"<\/span>: <span class=\"hljs-string\">\"database\/seeders\/\"<\/span>\n    },\n+   <span class=\"hljs-string\">\"files\"<\/span>: &#91;\n+     <span class=\"hljs-string\">\"app\/i18n-functions.php\"<\/span>\n+   ]\n  },\n  <span class=\"hljs-string\">\"autoload-dev\"<\/span>: {\n    <span class=\"hljs-string\">\"psr-4\"<\/span>: {\n      <span class=\"hljs-string\">\"Tests\\\\\"<\/span>: <span class=\"hljs-string\">\"tests\/\"<\/span>\n    }\n  },\n\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2b747f52ea34c2b1b1a7c119944be428\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>For Composer to pick this up we must regenerate our autoload files.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">composer dumpautoload<\/code><\/span><\/pre>\n\n\n<div id=\"acf\/text-block_1bd4768f79d78d989f8b370d37abfab8\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>With this setup in place, we can call <code>locale_dir()<\/code> in our main layout.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">\/\/  resources\/views\/components\/layout\/main.blade.php\n\n  <span class=\"hljs-meta\">&lt;!DOCTYPE <span class=\"hljs-meta-keyword\">html<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span>\n    <span class=\"hljs-attr\">lang<\/span>=<span class=\"hljs-string\">\"{{ str_replace(\"<\/span><span class=\"hljs-attr\">_<\/span>\", \"<span class=\"hljs-attr\">-<\/span>\", <span class=\"hljs-attr\">app<\/span>()<span class=\"hljs-attr\">-<\/span>&gt;<\/span>getLocale()) }}\"\n+   dir=\"{{ locale_dir() }}\"\n  &gt;\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n      <span class=\"hljs-comment\">&lt;!-- ... --&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-comment\">&lt;!-- ... --&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_69779642ec2094b991c327599567dfb4\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<figure id=\"attachment_92166\" aria-describedby=\"caption-attachment-92166\" style=\"width: 1024px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-92166 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/rtl-1024x881.png\" alt=\"Our home page shown with Arabic content, laid out right-to-left.\" width=\"1024\" height=\"881\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/rtl-1024x881.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/rtl-300x258.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/rtl-768x661.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/rtl.png 1334w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption id=\"caption-attachment-92166\" class=\"wp-caption-text\">Our Arabic routes now correctly show content laid out right-to-left.<\/figcaption><\/figure>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0We often must ensure our CSS works in both directions, which we cover in <a href=\"https:\/\/phrase.com\/blog\/posts\/how-do-i-use-a-css-file-for-site-localization\/\">CSS Localization<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-build-a-language-switcher\"><\/span>How do I build a language switcher?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>OK, let\u2019s return to setting the active locale. We\u2019re attempting to detect the user\u2019s locale from their browser settings. However, the user should be able to override this selection manually. This is often achieved with a locale switcher UI. Let\u2019s build ours.<\/p>\n<p>First, let\u2019s add an array to our <code>i18n.php<\/code> config file that lists our supported locale codes with human-readable text for each.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/i18n.php<\/span>\n\n <span class=\"hljs-meta\">&lt;?php<\/span>\n\n <span class=\"hljs-keyword\">return<\/span> &#91;\n+  <span class=\"hljs-string\">'supported_locales'<\/span> =&gt; &#91;\n+    <span class=\"hljs-string\">'en_US'<\/span> =&gt; <span class=\"hljs-string\">'English'<\/span>,\n+    <span class=\"hljs-string\">'ar_EG'<\/span> =&gt; <span class=\"hljs-string\">'\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabic)'<\/span>\n+  ],\n   <span class=\"hljs-string\">'rtl_locales'<\/span> =&gt; &#91;\n       <span class=\"hljs-string\">'ar_EG'<\/span>,\n   ]\n ];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_f7e11c0231406b29e1d9f8dd88e665e0\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can now use this config in our new <code>locale-switcher.blade.php<\/code> component.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">\/\/ resources\/views\/components\/layout\/locale-switcher.blade.php\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> {{ $<span class=\"hljs-attr\">attributes-<\/span>&gt;<\/span>merge(&#91;\"class\" =&gt; \"relative\"]) }}&gt;\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">select<\/span>\n    <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>\n    <span class=\"hljs-attr\">autocomplete<\/span>=<span class=\"hljs-string\">\"off\"<\/span>\n    <span class=\"hljs-attr\">onchange<\/span>=<span class=\"hljs-string\">\"window.location = this.options&#91;this.selectedIndex].getAttribute('data-url')\"<\/span>\n  &gt;<\/span>\n    @foreach (config(\"i18n.supported_locales\") as $locale =&gt; $name)\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span>\n        <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"{{ $locale }}\"<\/span>\n        <span class=\"hljs-attr\">data-url<\/span>=<span class=\"hljs-string\">\"{{ Route::localizedUrl($locale) }}\"<\/span>\n        {{ $<span class=\"hljs-attr\">locale<\/span> == <span class=\"hljs-string\">app()-<\/span>&gt;<\/span>getLocale() ? \"selected\" : \"\" }}\n      &gt;\n        {{ $name }}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n    @endforeach\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">select<\/span>&gt;<\/span>\n  \n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.icon-chevron-down<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e8b9694ab1deadef05d4e7941a898770\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0You can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/laravel-i18n\">get the complete demo code from our GitHub repo<\/a>.<\/p>\n<p>Our locale selector is a simple <code>&lt;select&gt;<\/code> element. When a user selects a new locale, it redirects them to the current page in the selected language. This is done using the <code>Route::localizedUrl()<\/code> macro from the Localized Routes package.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0We set <code>autocomplete=\"off\"<\/code> to work around an issue with Firefox; it doesn\u2019t always select the correct option. See the Stack Overflow question, <a href=\"https:\/\/stackoverflow.com\/questions\/4831848\/firefox-ignores-option-selected-selected\">Firefox ignores option selected=&#8221;selected\u201d<\/a>, for more info.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92172\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/locale-switcher.gif\" alt=\"Animation showing a user clicking on the locale switcher dropdown to switch between English and Arabic versions of a page.\" width=\"600\" height=\"171\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"a-note-on-storage-and-detectors\"><\/span>A note on storage and detectors<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Remember how we set up <code>CookieDetector<\/code> and <code>CookieStore<\/code> in <code>config\/localized-routes.php<\/code>? These are important for our locale switcher. When a locale is resolved, <code>CookieStore<\/code> saves it in the user&#8217;s browser. Since <code>CookieDetector<\/code> is listed before <code>AcceptLanguageDetector<\/code>, it resolves the locale from the user&#8217;s cookie and stops the cascade.<\/p>\n<p>When a user selects a new locale from the switcher, the URL updates, changing from something like <code>\/en_US\/about<\/code> to <code>\/ar-EG\/about<\/code>. This prompts the <code>UrlDetector<\/code> to recognize the new locale, and the <code>CookieStore<\/code> to save it. The next time the user visits, the <code>CookieDetector<\/code> will automatically load the locale the user chose last.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-add-dynamic-values-to-translation-messages\"><\/span>How do I add dynamic values to translation messages?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Let\u2019s direct our attention to translation strings and formatting. Laravel&#8217;s translation functions let you insert dynamic values into your messages using the <code>:variable<\/code> format. For example, <code>('Hello, :username', ['username' =&gt; 'Adam'])<\/code> will interpolate &#8216;Adam&#8217; at runtime, rendering &#8216;Hello, Adam&#8217; when the code runs. Let&#8217;s add a simple user notification and translate it to see how that works.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">\/\/ resources\/views\/books\/index.blade.php\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.main<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Books\"<\/span>&gt;<\/span>\n+   <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n+       {{\n+         __(\"\ud83d\udc4b Hey :username, :book_title just came in :)\", &#91;\n+           \"username\" =&gt; \"Nunu\",\n+           \"book_title\" =&gt; \"A Brief of History of Time\",\n+         ])\n+       }}\n+   <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>{{ __(\"Recently added\") }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n  \n    <span class=\"hljs-comment\">&lt;!-- ... --&gt;<\/span>  \n  \n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">x-layout.main<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_16e5d56faee77ba4c73fc7dc5f8535d9\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>When we view our home page in English, we see the <code>username<\/code> and <code>book_title<\/code> variables interpolated correctly.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92179\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-en-1024x327.png\" alt=\"The upper part of the English home page, showing an alert that reads &quot;Hey Nunu, A Brief History of Time just arrived :)&quot;\" width=\"1024\" height=\"327\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-en-1024x327.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-en-300x96.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-en-768x245.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-en.png 1266w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>We can extract this string into our <code>ar_EG.json<\/code> file as usual (or copy\/paste it manually if preferred).<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">php artisan translatable:<span class=\"hljs-keyword\">export<\/span> ar_EG<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_6d2cd7a4fca18434ac51b0f209657940\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can then translate the new string, positioning <code>:username<\/code> and <code>:book_title<\/code> where appropriate in the translation message.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  {\n    <span class=\"hljs-string\">\"About us\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0630\u0629 \u0639\u0646\u0627\"<\/span>,\n    <span class=\"hljs-string\">\"bookavel\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0648\u0643\u0627\u06a4\u0650\u0644\"<\/span>,\n    <span class=\"hljs-string\">\"Books\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0643\u062a\u0628\"<\/span>,\n    <span class=\"hljs-string\">\"Recently added\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u062c\u062f\u064a\u062f \u0639\u0646\u062f\u0646\u0627\"<\/span>,\n+   <span class=\"hljs-string\">\"\ud83d\udc4b Hey :username, :book_title just came in :)\"<\/span>: <span class=\"hljs-string\">\"\ud83d\udc4b \u0623\u0647\u0644\u0627\u064b :username\u060c \u0644\u0642\u062f \u0648\u0635\u0644 \u0644\u0644\u062a\u0648 :book_title :)\"<\/span>\n  }\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_f1cc477abdb60796e27cbe4389177e1f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92185\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-ar-1024x325.png\" alt=\"The upper part of the Arabic home page, showing an alert that reads, in Arabic, &quot;Hey Nunu, A Brief History of Time just arrived :)&quot;\" width=\"1024\" height=\"325\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-ar-1024x325.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-ar-300x95.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-ar-768x243.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/interpolation-ar.png 1262w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0See the Laravel docs section, <a href=\"https:\/\/laravel.com\/docs\/11.x\/localization#replacing-parameters-in-translation-strings\">Replacing Parameters in Translation Strings<\/a>, for more info.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-work-with-plurals\"><\/span>How do I work with plurals?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Plurals often need special treatment in translation messages. It\u2019s not just \u201cone\u201d and \u201cother\u201d forms like in English. Arabic has six plural forms, for example. Some languages only have one. So we need a way to provide different plural forms for each locale. Luckily, Laravel allows for this.<\/p>\n<p>Let\u2019s start simple. We\u2019ll add a book counter next to our \u201cRecently added\u201d title.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-40\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">\/\/ resources\/views\/books\/index.blade.php\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">x-layout.main<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Books\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        {{ __(\"\ud83d\udc4b Hey :username, :book_title just came in :)\", &#91;\"username\" =&gt; \"Nunu\", \"book_title\" =&gt; \"A Brief of History of Time\"]) }}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>{{ __(\"Recently added\") }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n+   <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n+     {{ trans_choice(\"One book|:count books\", $books-&gt;count()) }}\n+   <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    \n    <span class=\"hljs-comment\">&lt;!-- ... --&gt;<\/span>\n    \n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">x-layout.main<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-40\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2279bc9abb83cc310f730e3d5130ebf4\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><code>trans_choice<\/code> is Laravel\u2019s function for handling plural translations, taking a message and an integer. The message is split into plural forms using the pipe character (<code>|<\/code>). In English, when <code>$books-&gt;count()<\/code> equals 1, it will render &#8220;One book.&#8221; Any other value, like 3, will show &#8220;3 books.&#8221;<\/p>\n<p>We can control this behavior further by specifying ranges.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-41\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">{{\n    trans_choice(\n      <span class=\"hljs-string\">\"{0} No books|{1} One book|&#91;2,*] :count books\"<\/span>,\n      $books-&gt;count(),\n    )\n}}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-41\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_fe60276757f800af692a8bbf80cf8bc6\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now we\u2019ll get these renders:<\/p>\n<ul>\n<li><code>$books-&gt;count() == 0<\/code> renders <code>'No books'<\/code><\/li>\n<li><code>$books-&gt;count() == 1<\/code> renders <code>'One book'<\/code><\/li>\n<li><code>$books-&gt;count() == 38<\/code> renders <code>'38 books'<\/code><\/li>\n<\/ul>\n<p>Let\u2019s tackle the Arabic translation. First, another quick detour.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"adding-trans-choice-to-the-extraction-function-list\"><\/span>Adding trans_choice to the extraction function list<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The Translatable String Exporter package doesn\u2019t scan for <code>trans_choice<\/code> out of the box, but we can tell it to. Let\u2019s publish the package\u2019s config to do that.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-42\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">php artisan vendor:publish --provider=<span class=\"hljs-string\">\"KKomelin\\TranslatableStringExporter\\Providers\\ExporterServiceProvider\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-42\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_fa7a642eae551bb724d821fd05f41b50\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The command should have created a <code>laravel-translatable-string-exporter.php<\/code> file in our <code>config<\/code> directory. We can add <code>trans_choice<\/code> under <code>functions<\/code> to tell the exporter to track it.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/laravel-translatable-string-exporter.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-string\">'directories'<\/span> =&gt; &#91;<span class=\"hljs-string\">'app'<\/span>, <span class=\"hljs-string\">'resources'<\/span>],\n\n    <span class=\"hljs-string\">'excluded-directories'<\/span> =&gt; &#91;],\n\n    <span class=\"hljs-string\">'patterns'<\/span> =&gt; &#91;\n      <span class=\"hljs-string\">'*.php'<\/span>,\n      <span class=\"hljs-string\">'*.js'<\/span>,\n    ],\n  \n    <span class=\"hljs-string\">'allow-newlines'<\/span> =&gt; <span class=\"hljs-keyword\">false<\/span>,\n\n    <span class=\"hljs-string\">'functions'<\/span> =&gt; &#91;\n      <span class=\"hljs-string\">'__'<\/span>,\n      <span class=\"hljs-string\">'_t'<\/span>,\n      <span class=\"hljs-string\">'@lang'<\/span>,\n+     <span class=\"hljs-string\">'trans_choice'<\/span>,\n    ],\n\n    <span class=\"hljs-string\">'sort-keys'<\/span> =&gt; <span class=\"hljs-keyword\">true<\/span>,\n  \n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n  \n  ];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_250c0ed9d5a13214f27b44464e61b972\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now let\u2019s run our translation export command as usual.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">php artisan translatable:<span class=\"hljs-keyword\">export<\/span> ar_EG<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c1b4391460455d70da1c87631322391d\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Our plural string should get added to <code>lang\/ar_EG.json<\/code>. As mentioned earlier, Arabic has six translation forms, and we can add them separated by the <code>|<\/code> character.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  {\n    <span class=\"hljs-string\">\"About us\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0630\u0629 \u0639\u0646\u0627\"<\/span>,\n    <span class=\"hljs-string\">\"bookavel\"<\/span>: <span class=\"hljs-string\">\"\u0628\u0648\u0643\u0627\u06a4\u0650\u0644\"<\/span>,\n    <span class=\"hljs-string\">\"Books\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0643\u062a\u0628\"<\/span>,\n    <span class=\"hljs-string\">\"Recently added\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u062c\u062f\u064a\u062f \u0639\u0646\u062f\u0646\u0627\"<\/span>,\n+   <span class=\"hljs-string\">\"{0} No books|{1} One book|&#91;2,*] :count books\"<\/span>: <span class=\"hljs-string\">\"\u0644\u0627 \u062a\u0648\u062c\u062f \u0643\u062a\u0628|\u0643\u062a\u0627\u0628 \u0648\u0627\u062d\u062f|\u0643\u062a\u0627\u0628\u064a\u0646|:count \u0643\u062a\u0628|:count \u0643\u062a\u0627\u0628|:count \u0643\u062a\u0627\u0628\"<\/span>,\n    <span class=\"hljs-string\">\"\ud83d\udc4b Hey :username, :book_title just came in :)\"<\/span>: <span class=\"hljs-string\">\"\ud83d\udc4b \u0623\u0647\u0644\u0627\u064b :username\u060c \u0644\u0642\u062f \u0648\u0635\u0644 \u0644\u0644\u062a\u0648 :book_title :)\"<\/span>\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a9b654801919ca502bde06ee37c2fe6f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0The canonical source for languages\u2019 plural forms is the <a href=\"https:\/\/www.unicode.org\/cldr\/charts\/42\/supplemental\/language_plural_rules.html\">CLDR Language Plural Rules<\/a> listing.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Even though it\u2019s not documented, Laravel\u2019s <code>trans_choice<\/code> supports CLDR plural rules, so specifying six plural forms for Arabic works out of the box.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92191\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/plurals-1024x757.png\" alt=\"The image compares pluralization rules in English (en-US) and Arabic (ar-EG). On the left, the English rules show two forms: &quot;one&quot; for singular (&quot;One book&quot;) and &quot;other&quot; for plural (&quot;2 books&quot;). On the right, the Arabic rules display multiple plural forms: &quot;zero&quot; for no books, &quot;one&quot; for one book, &quot;two&quot; for two books, &quot;few&quot; for numbers like 3, &quot;many&quot; for numbers like 11, and &quot;other&quot; for larger numbers like 100.\" width=\"1024\" height=\"757\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/plurals-1024x757.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/plurals-300x222.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/plurals-768x568.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/plurals.png 1183w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Read our <a href=\"https:\/\/phrase.com\/blog\/posts\/pluralization\/\">Guide to Localizing Plurals<\/a> for a deeper dive.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-format-numbers\"><\/span>How do I format numbers?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Laravel has built-in <a href=\"https:\/\/laravel.com\/docs\/11.x\/helpers#numbers-method-list\">number-formatting helpers<\/a> that are locale-aware.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ In our views<\/span>\n\n{{ Number::format(<span class=\"hljs-number\">1000<\/span>) }}\n<span class=\"hljs-comment\">\/\/ =&gt; '1,000'<\/span>\n\n{{ Number::currency(<span class=\"hljs-number\">1234.56<\/span>) }}\n<span class=\"hljs-comment\">\/\/ =&gt; '$1,234.56'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_219345017503c8d2786f5b5f0c50351b\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can add a <code>locale<\/code> param when calling these functions.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-47\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ In our views<\/span>\n\n{{ Number::format(<span class=\"hljs-number\">1000<\/span>, locale: <span class=\"hljs-string\">'ar_EG'<\/span>) }}\n<span class=\"hljs-comment\">\/\/ =&gt; '\u0661\u066c\u0660\u0660\u0660'<\/span>\n\n{{ Number::currency(<span class=\"hljs-number\">1234.56<\/span>, locale: <span class=\"hljs-string\">'ar_EG'<\/span>) }}\n<span class=\"hljs-comment\">\/\/ =&gt; '\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666 US$'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-47\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e60fdb833e4ea618d889010cfc10a71a\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Numbers and dates are often formatted differently in each <strong>region<\/strong>, so it\u2019s a good idea to use region qualifiers when setting our locales e.g. using <code>en_US<\/code> instead of <code>en<\/code>.<\/p>\n<p>However, instead of manually adding the active locale, we can write a custom middleware that sets the locale used by all number formatters.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-48\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">php<\/span> <span class=\"hljs-selector-tag\">artisan<\/span> <span class=\"hljs-selector-tag\">make<\/span><span class=\"hljs-selector-pseudo\">:middleware<\/span> <span class=\"hljs-selector-tag\">FormatSetLocale<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-48\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b1d7e260ff95620c3adc6873cb85d708\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-49\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Middleware\/FormatSetLocale.php<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Middleware<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Closure<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Number<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Symfony<\/span>\\<span class=\"hljs-title\">Component<\/span>\\<span class=\"hljs-title\">HttpFoundation<\/span>\\<span class=\"hljs-title\">Response<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">FormatSetLocale<\/span>\n<\/span>{\n  <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handle<\/span><span class=\"hljs-params\">(Request $request, Closure $next)<\/span> : <span class=\"hljs-title\">Response<\/span>\n  <\/span>{\n    <span class=\"hljs-comment\">\/\/ Set the locale to use for all number formatters<\/span>\n    <span class=\"hljs-comment\">\/\/ as the active locale.<\/span>\n    Number::useLocale(app()-&gt;getLocale());\n    \n    <span class=\"hljs-keyword\">return<\/span> $next($request);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-49\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a3edea8055d4013940e3e12bbb67e230\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We need to register this middleware <strong>after<\/strong> the <code>SetLocale<\/code> middleware to ensure the Localized Routes package has set the app locale before we use it.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-50\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ bootstrap\/app.php<\/span>\n\n  <span class=\"hljs-meta\">&lt;?php<\/span>\n\n+ <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Middleware<\/span>\\<span class=\"hljs-title\">FormatSetLocale<\/span>;\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Application<\/span>;\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Configuration<\/span>\\<span class=\"hljs-title\">Exceptions<\/span>;\n  <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Configuration<\/span>\\<span class=\"hljs-title\">Middleware<\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> Application::configure(basePath: dirname(<span class=\"hljs-keyword\">__DIR__<\/span>))\n    -&gt;withRouting(\n      web: <span class=\"hljs-keyword\">__DIR__<\/span> . <span class=\"hljs-string\">'\/..\/routes\/web.php'<\/span>,\n      commands: <span class=\"hljs-keyword\">__DIR__<\/span> . <span class=\"hljs-string\">'\/..\/routes\/console.php'<\/span>,\n      health: <span class=\"hljs-string\">'\/up'<\/span>,\n    )\n    -&gt;withMiddleware(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Middleware $middleware)<\/span> <\/span>{\n      $middleware-&gt;web(remove: &#91;\n        \\Illuminate\\Routing\\Middleware\\SubstituteBindings::class,\n      ]);\n      $middleware-&gt;web(append: &#91;\n        \\CodeZero\\LocalizedRoutes\\Middleware\\SetLocale::class,\n+       FormatSetLocale::class,\n        \\Illuminate\\Routing\\Middleware\\SubstituteBindings::class,\n      ]);\n    })\n    -&gt;withExceptions(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Exceptions $exceptions)<\/span> <\/span>{\n      <span class=\"hljs-comment\">\/\/<\/span>\n    })-&gt;create();\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-50\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7cb69b474a44519611e87a98f71e3e37\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now we can call the <code>Number<\/code> functions without an explicit locale, and they will use the app\u2019s active locale.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-92104\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/number-formats-1024x363.png\" alt=\"The image shows a table comparing number and currency formatting between the &#96;en_US&#96; and &#96;ar_EG&#96; locales. For &#96;en_US&#96;, formatting 1000 results in &quot;1,000&quot;, while currency formatting for 1234.56 renders as &quot;$1,234.56&quot;. In the &#96;ar_EG&#96; locale, formatting 1000 shows &quot;\u0661\u0660\u0660\u0660&quot;, and currency formatting for 1234.56 renders as &quot;\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666 US$&quot;. The table illustrates differences in how numbers and currencies are displayed based on locale settings.\" width=\"1024\" height=\"363\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/number-formats-1024x363.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/number-formats-300x106.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/number-formats-768x273.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/number-formats.png 1172w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Our <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/\">Concise Guide to Number Localization<\/a> goes into numeral systems, separators, currency, and more.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-format-dates\"><\/span>How do I format dates?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Under the hood, Laravel uses the PHP <a href=\"https:\/\/carbon.nesbot.com\/docs\/#api-localization\">Carbon<\/a> library to represent and format datetimes. And, unlike numbers, Laravel\u2019s Carbon instances will automatically use the app\u2019s active locale.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-51\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ In our views<\/span>\n<span class=\"hljs-meta\">&lt;?php<\/span> $datetime = Illuminate\\Support\\Carbon::create(<span class=\"hljs-number\">2024<\/span>, <span class=\"hljs-number\">9<\/span>, <span class=\"hljs-number\">24<\/span>, <span class=\"hljs-number\">14<\/span>, <span class=\"hljs-number\">37<\/span>, <span class=\"hljs-number\">6<\/span>); <span class=\"hljs-meta\">?&gt;<\/span>\n\n&lt;p&gt;{{ $datetime-&gt;isoFormat(<span class=\"hljs-string\">\"MMMM Do YYYY, h:mm:ss a\"<\/span>) }}&lt;\/p&gt;\n&lt;p&gt;{{ $datetime-&gt;isoFormat(<span class=\"hljs-string\">\"LL\"<\/span>) }}&lt;\/p&gt;\n&lt;p&gt;{{ $datetime-&gt;diffForHumans() }}&lt;\/p&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-51\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_6fae874fb30b71c064e95b178d3fb797\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>When the active locale is <code>en_US<\/code>, the above will output:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92197\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/dates-en.png\" alt=\"The image shows three date and time formats in English. The first is &quot;September 24th 2024, 2:37:06 pm&quot; displaying the full date with time. The second is &quot;September 24, 2024&quot; showing the date only. The third is &quot;52 minutes from now&quot; expressing time in a relative format.\" width=\"644\" height=\"184\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/dates-en.png 644w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/dates-en-300x86.png 300w\" sizes=\"(max-width: 644px) 100vw, 644px\" \/><\/p>\n<p>And when it\u2019s <code>ar_EG<\/code>, we get:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92203\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/dates-ar.png\" alt=\"The image shows three date and time formats in Arabic. The first is &quot;\u0662:\u0663\u0667:\u0660\u0666 \u0645, \u0662\u0664 \u0633\u0628\u062a\u0645\u0628\u0631 \u0662\u0660\u0662\u0664&quot; displaying the full date with time. The second is &quot;\u0662\u0664 \u0633\u0628\u062a\u0645\u0628\u0631 \u0662\u0660\u0662\u0664&quot; showing the date only. The third is &quot;\u0665\u0662 \u062f\u0642\u064a\u0642\u0629 \u0645\u0646 \u0627\u0644\u0622\u0646&quot; expressing time in a relative format.\" width=\"504\" height=\"186\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/dates-ar.png 504w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/dates-ar-300x111.png 300w\" sizes=\"(max-width: 504px) 100vw, 504px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"some-notes-on-date-localization\"><\/span>Some notes on date localization<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li>The <code>isoFormat()<\/code> function is compatible with <a href=\"https:\/\/momentjs.com\/\">Moment.js formats<\/a>.<\/li>\n<li>See the <a href=\"https:\/\/github.com\/briannesbitt\/Carbon\/tree\/master\/src\/Carbon\/Lang\">Lang directory in the Carbon source code<\/a> for all supported locales. Note that Carbon will fall back ie. if it can\u2019t find <code>ar_XX<\/code> it will use <code>ar<\/code>.<\/li>\n<li>The default timezone that Laravel sets for our app is UTC, <a href=\"https:\/\/carbon.nesbot.com\/laravel\/\">which is often what we want<\/a>. You can change this in <code>config\/app.php<\/code>, however.<\/li>\n<li>Laravel\u2019s Eloquent models\u2019 datetimes, e.g. <code>timestamps<\/code>, return Carbon instances.<\/li>\n<\/ul>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Our <a href=\"https:\/\/phrase.com\/blog\/posts\/date-time-localization\/\">Guide to Date and Time Localization<\/a> dives into formatting, time zones, calendars, and more.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-my-eloquent-models\"><\/span>How do I localize my Eloquent models?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>This guide is getting a bit long, and model\/database localization could take up its own article. So while we won\u2019t get into the details of model localization in this tutorial, we\u2019ve created a separate branch in our GitHub repo that should help. Here\u2019s how to use it:<\/p>\n<p>1. Clone or download the <a href=\"https:\/\/github.com\/PhraseApp-Blog\/laravel-i18n\/tree\/model-l10n\">model-l10n branch<\/a> from our GitHub repo. (Alternatively, check out the branch if you\u2019ve already cloned the repo).<br \/>\n2. Run <code>composer install<\/code><br \/>\n3. Run <code>php artisan migrate:refresh --seed<\/code><br \/>\n4. Run your dev server as usual<\/p>\n<p>We\u2019re using the <a href=\"https:\/\/github.com\/spatie\/laravel-translatable\">spatie\/laravel-translatable<\/a> package, which is great for localizing Eloquent models in apps with a few locales. The laravel-translatable package requires refactors to both our migrations and our model classes.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Peruse <a href=\"https:\/\/github.com\/PhraseApp-Blog\/laravel-i18n\/compare\/main...model-l10n\">the main \u2192 model-l10n diff<\/a> to see this refactor.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0See the <a href=\"https:\/\/spatie.be\/docs\/laravel-translatable\/v6\/introduction\">official laravel-translatable docs<\/a> for more info.<\/p>\n<p>And with that, our demo is complete.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-92209\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/09\/demo-final.gif\" alt=\"Animation showing a few pages of our fully localized app in English, and the same pages in Arabic. \" width=\"600\" height=\"598\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Remember, you can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/laravel-i18n\">get the entire demo code from our GitHub repo<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"power-up-your-laravel-localization\"><\/span>Power up your Laravel localization<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We hope you enjoyed this journey through Laravel localization. When you\u2019re all set to start the translation process, rely on the Phrase Localization Platform to handle the heavy lifting. A specialized software localization platform, the Phrase Localization Platform comes with dozens of tools designed to automate your translation process and native integrations with platforms like GitHub, GitLab, and Bitbucket.<\/p>\n<p>With its user-friendly strings editor, translators can effortlessly access your content and transfer it into your target languages. Once your translations are set, easily integrate them back into your project with a single command or automated sync.<\/p>\n<p>This way, you can stay focused on what you love\u2014your code. <a href=\"https:\/\/eu.phrase.com\/idm-ui\/signup?uiLang=en-US\">Sign up for a free trial<\/a> and see for yourself why developers appreciate using The Phrase Localization Platform for software localization.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>We dive deep into Laravel localization, covering routing, strings, and more.<\/p>\n","protected":false},"author":41,"featured_media":2612,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":false,"_modified_date":"","_searchwp_excluded":"","footnotes":""},"categories":[40],"class_list":["post-8703","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-localization"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/8703"}],"collection":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/comments?post=8703"}],"version-history":[{"count":8,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/8703\/revisions"}],"predecessor-version":[{"id":94836,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/8703\/revisions\/94836"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media?parent=8703"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/categories?post=8703"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}