[Complete Practical Guide] Laravel Multilingualization and Localization: Translation Files, Language Switching, URL Design, Validation, Dates and Currency, and Accessible Multilingual UI
What you will learn in this article
- Basic design for starting multilingual support in Laravel
- How to use the
langdirectory, PHP array translations, and JSON translations - Managing text with
__(),@lang, andtrans_choice() - Implementation patterns for language switching, URL design, middleware, and session storage
- Localization of validation messages, attribute names, dates, times, currencies, and numbers
- Common pitfalls in multilingual sites: SEO, caching, testing, and operations
- Accessible language-switching UI, screen reader support,
langattributes, text direction, and clear error display
Target readers
- Beginner to intermediate Laravel engineers who want to expand a Japanese-only app into English or other languages
- SaaS, e-commerce, and media operators who want to support overseas and multilingual users
- Tech leads who want to standardize translation files, URLs, caching, and testing across a team
- Designers, writers, and accessibility specialists who want language switching and multilingual text to be clear for everyone
Accessibility level: ★★★★★
Multilingualization is not just translating text. It requires a design that lets users operate with confidence, including the page’s lang attribute, clear language-switching links, correct screen reader language detection, date and currency formatting, and natural error messages. This article organizes accessible multilingual support from both implementation and UI perspectives.
1. Introduction: Multilingualization Is Not “Translation,” but “Experience Design”
When starting multilingual support in Laravel, many people first think about replacing Japanese text with English. Translation is, of course, important, but it is not enough. Multilingual support affects URLs, forms, validation, dates, times, currencies, emails, SEO, caching, and accessibility.
For example, even if the visible text on a screen is in English, users will be confused if error messages remain in Japanese. A date such as 2026/05/13 may be interpreted differently depending on the country or region. Currency, decimal separators, and thousands separators also vary by locale. In addition, screen readers refer to the page’s lang attribute when reading content aloud, so inaccurate language settings can affect pronunciation and understanding.
In other words, multilingualization is not “replacing strings.” It is “designing information delivery so it matches the user’s language, culture, and operating environment.” Laravel provides localization features, so the safest approach is to use the standard features correctly first, then add operational rules as needed.
2. Decide First: Supported Languages and URL Design
Before beginning multilingualization, the first things to decide are supported languages and URL design. If this is unclear, routing, SEO, caching, and link generation become difficult later.
There are three common URL design patterns.
2.1 Include the Language in the Path
/ja/products
/en/products
/fr/products
This is the clearest approach and works well for SEO and sharing. Because the language of the page is visible from the URL, it also works well with caching and search engines. In real projects, this is often the first option to consider.
2.2 Separate by Subdomain
ja.example.com
en.example.com
This is suitable when you want to clearly separate brands or regions. However, certificates, cookies, sessions, routing, and infrastructure settings become slightly more complex.
2.3 Switch Only with Sessions or Cookies
/products
In this method, the URL stays the same, and the display language changes based on the language saved in the session or cookie. It is convenient for small admin screens, but weak for SEO and shared URLs. If someone views a URL in English and sends it to someone else, the recipient may see the page in Japanese.
In practice, public sites and services where SEO matters are easier to manage when the language is included in the URL, such as /ja/... or /en/.... For admin screens or logged-in SaaS products, using sessions or user settings is also realistic.
3. Laravel Basic Settings: locale and fallback_locale
In Laravel, application language settings are managed in config/app.php.
'locale' => env('APP_LOCALE', 'ja'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
In .env, you can configure them as follows.
APP_LOCALE=ja
APP_FALLBACK_LOCALE=en
locale is the default language, and fallback_locale is the backup language used when a translation is not found. For example, if Japanese is the default language but some translations are missing, you can fall back to English.
However, having a fallback does not mean it is acceptable to leave missing translations unresolved. If part of the screen is in Japanese and another part is in English, users may become confused. It is safer to prepare a mechanism that makes missing translations easy to find during development and testing.
4. Translation File Location: Prepare the lang Directory
In newer Laravel applications, the lang directory may not exist by default. In that case, you can publish it with the following command.
php artisan lang:publish
A typical structure looks like this.
lang/
├─ ja/
│ ├─ messages.php
│ ├─ validation.php
│ └─ auth.php
├─ en/
│ ├─ messages.php
│ ├─ validation.php
│ └─ auth.php
└─ ja.json
Laravel supports both PHP array translation files and JSON translation files. Both are useful, but separating their use cases makes maintenance easier.
5. How to Use PHP Array Translations and JSON Translations
5.1 PHP Array Translations
lang/ja/messages.php
return [
'welcome' => 'ようこそ、:nameさん',
'profile_updated' => 'プロフィールを更新しました。',
'items_count' => ':count件の項目があります。',
];
Usage:
{{ __('messages.welcome', ['name' => $user->name]) }}
PHP array translations are useful because they let you organize text by category.
For example, you can separate authentication into auth.php, forms into forms.php, and admin screens into admin.php.
5.2 JSON Translations
lang/ja.json
{
"Welcome": "ようこそ",
"Save": "保存する",
"Cancel": "キャンセル"
}
Usage:
{{ __('Save') }}
JSON translations are suitable for short common text.
However, they are not well suited for context-specific management or hierarchy, so in large applications it is often easier to maintain PHP array translations as the main approach.
5.3 Practical Recommendation
- Screen-specific and business-specific text: PHP arrays
- Common buttons and short labels: JSON
- Validation:
validation.php - Authentication:
auth.php - Emails:
mail.phporemails.php
You do not need to split files too finely from the beginning, but deciding as a team where to place text helps keep translation files organized.
6. Translation Basics: __(), @lang, and trans_choice()
The basic way to retrieve translated strings in Laravel is __().
<h1>{{ __('messages.welcome', ['name' => $user->name]) }}</h1>
In Blade, you can also use @lang.
@lang('messages.profile_updated')
When the wording changes depending on a count, use trans_choice().
echo trans_choice('messages.comments', $count, ['count' => $count]);
Translation file:
return [
'comments' => '{0} コメントはありません|{1} :count件のコメントがあります|[2,*] :count件のコメントがあります',
];
Japanese has fewer singular/plural changes than English, but when expanding to English or other languages, it is safer to handle count-based expressions with trans_choice() from the start.
7. Implementing Language Switching: Determine the Current Language with Middleware
When the language is included in the URL, you might use routes such as /ja/products.
First, define supported languages in a configuration file.
// config/locales.php
return [
'supported' => ['ja', 'en'],
'default' => 'ja',
];
Create middleware.
php artisan make:middleware SetLocale
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class SetLocale
{
public function handle(Request $request, Closure $next)
{
$locale = $request->route('locale');
if (! in_array($locale, config('locales.supported'), true)) {
abort(404);
}
App::setLocale($locale);
return $next($request);
}
}
Define routes like this.
Route::prefix('{locale}')
->middleware(['set.locale'])
->group(function () {
Route::get('/products', [ProductController::class, 'index'])
->name('products.index');
});
When generating links, pass the current language.
<a href="{{ route('products.index', ['locale' => app()->getLocale()]) }}">
{{ __('messages.products') }}
</a>
With this approach, the language is clear from the URL. It is easy to share and understandable for search engines, making it especially practical for public websites.
8. Saving Language in Sessions or User Settings
For logged-in admin screens or SaaS products, you may store a language setting for each user.
For example, add a locale column to the users table.
Schema::table('users', function (Blueprint $table) {
$table->string('locale', 10)->default('ja');
});
Set it in middleware after login.
public function handle(Request $request, Closure $next)
{
if ($request->user()?->locale) {
App::setLocale($request->user()->locale);
}
return $next($request);
}
Language-switching form:
<form method="POST" action="{{ route('settings.locale.update') }}">
@csrf
@method('PATCH')
<label for="locale">{{ __('settings.language') }}</label>
<select id="locale" name="locale">
<option value="ja" @selected(app()->getLocale() === 'ja')>日本語</option>
<option value="en" @selected(app()->getLocale() === 'en')>English</option>
</select>
<button type="submit">{{ __('messages.save') }}</button>
</form>
Saving the language as a user setting makes it easier to show the same language even when the user changes devices.
On the other hand, for public pages, the language does not appear in the URL, so you need to be careful with SEO and shared links.
9. Language-Switching UI: Design for Accessibility from the Start
Language switching is not as simple as placing a flag icon. Flags represent countries, not languages, and they do not always accurately represent a language. For example, English is not used only in the United States, and Spanish is used in many countries.
A recommended approach is to display each language name in that language itself.
<nav aria-label="{{ __('messages.language_switcher') }}">
<ul>
<li>
<a href="{{ route('home', ['locale' => 'ja']) }}" lang="ja" hreflang="ja">
日本語
</a>
</li>
<li>
<a href="{{ route('home', ['locale' => 'en']) }}" lang="en" hreflang="en">
English
</a>
</li>
</ul>
</nav>
Adding aria-current to the currently selected language makes it easier to understand.
<a
href="{{ route('home', ['locale' => 'ja']) }}"
lang="ja"
hreflang="ja"
@if(app()->getLocale() === 'ja') aria-current="true" @endif
>
日本語
</a>
For language-switching UI, keep the following in mind.
- Do not rely only on flags
- Display language names as text
- Clearly indicate the currently selected language
- Make it operable by keyboard only
- After switching, return focus to the top of the page or a heading if necessary
Multilingualization must communicate correctly not only to people who understand the language, but also to people using assistive technologies.
10. HTML lang Attribute: Directly Affects Screen Reader Quality
One easily forgotten part of multilingual support is the HTML lang attribute.
Reflect the current language in the layout file.
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<title>{{ $title ?? config('app.name') }}</title>
</head>
<body>
{{ $slot ?? '' }}
</body>
</html>
If the whole page is Japanese, use lang="ja". If it is English, use lang="en".
If another language appears within the page, add lang to that specific part.
<p>
サービス名は <span lang="en">Accessible Laravel</span> です。
</p>
Screen readers may switch pronunciation based on the lang attribute.
Therefore, if lang is incorrect, English may be read with Japanese pronunciation, or Japanese may be read unnaturally.
It is hard to notice visually, but it is very important for accessibility.
11. Multilingual Validation Messages
Validation messages are especially important in form localization.
Laravel uses lang/{locale}/validation.php to manage messages.
Example:
return [
'required' => ':attribute は必須です。',
'email' => ':attribute はメールアドレスの形式で入力してください。',
'max' => [
'string' => ':attribute は :max 文字以内で入力してください。',
],
'attributes' => [
'name' => 'お名前',
'email' => 'メールアドレス',
'password' => 'パスワード',
],
];
You can also use attributes() in a FormRequest.
public function attributes(): array
{
return [
'name' => __('forms.name'),
'email' => __('forms.email'),
];
}
However, if translation calls become too complex here, readability suffers, so it is best to decide a team policy.
In general, common fields are easier to manage in the attributes section of validation.php, while screen-specific fields can be added in the FormRequest.
12. Error Display: Keep the Structure Clear After Translation
When multilingualizing an app, text length changes depending on the language. A sentence that is short in English may become long in German or French. Even when translating from Japanese to English, button widths and error-message line counts may change.
Use a standard structure like this for error display.
@if ($errors->any())
<div id="error-summary" role="alert" tabindex="-1" class="border p-3 mb-4">
<h2>{{ __('forms.error_summary_title') }}</h2>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
Input field:
<label for="email">{{ __('forms.email') }}</label>
<input
id="email"
name="email"
type="email"
value="{{ old('email') }}"
aria-invalid="{{ $errors->has('email') ? 'true' : 'false' }}"
aria-describedby="{{ $errors->has('email') ? 'email-error' : 'email-help' }}"
>
<p id="email-help">{{ __('forms.email_help') }}</p>
@error('email')
<p id="email-error">{{ $message }}</p>
@enderror
Design spacing and wrapping so that the structure does not break even when translated text becomes longer.
For multilingual UI, it is safer to avoid fixed-width buttons and labels that assume a single line.
13. Dates, Times, and Time Zones: Consider Not Only Language but Region
Dates and times are also important in multilingualization.
Laravel often uses Carbon, but if display formats are fixed strings, it becomes difficult to support regional differences.
Bad example:
{{ $post->published_at->format('Y/m/d H:i') }}
This format is easy to read in Japan, but may not feel natural in English-speaking regions.
In practice, creating a display formatter keeps things organized.
class DateFormatter
{
public function date($value): string
{
return match (app()->getLocale()) {
'ja' => $value->format('Y年n月j日'),
'en' => $value->format('F j, Y'),
default => $value->toDateString(),
};
}
}
Blade:
{{ app(DateFormatter::class)->date($post->published_at) }}
When handling time, time zones are also important.
If each user has a timezone, convert the time at display.
$post->published_at
->timezone($user->timezone ?? config('app.timezone'))
->format('Y-m-d H:i');
From an accessibility perspective, avoiding ambiguous dates is also important.
For example, instead of only saying “tomorrow” or “next week,” adding a specific date when necessary reduces misunderstanding.
14. Numbers, Currency, and Units: Align Meaning, Not Just Appearance
Currency and number formats also vary by region.
For Japanese yen, you might display ¥1,000; for U.S. dollars, $1,000.00. Decimal places and symbols vary by currency.
Simple example:
class MoneyFormatter
{
public function format(int $amount, string $currency = 'JPY'): string
{
return match ($currency) {
'JPY' => '¥' . number_format($amount),
'USD' => '$' . number_format($amount / 100, 2),
default => number_format($amount),
};
}
}
However, serious multi-currency support requires design decisions such as storing amounts in the smallest currency unit, saving currency codes, and standardizing rounding rules.
Even if the display is translated, billing and payment amounts must not be ambiguous.
The same applies to units.
- km / miles
- kg / lb
- Celsius / Fahrenheit
If required units differ by target region, review not only the display layer but also the data design.
15. Multilingual Emails: Clearly Decide the Language at Send Time
Unlike web screens, emails cannot change display language after being sent.
Therefore, you must clearly decide which language to use at send time.
The basic approach is to use the user’s locale.
Mail::to($user->email)
->locale($user->locale)
->send(new WelcomeMail($user));
Use translations in the Mailable view as well.
<h1>{{ __('emails.welcome_title') }}</h1>
<p>{{ __('emails.welcome_body', ['name' => $user->name]) }}</p>
For emails, pay special attention to the following.
- Translate the subject line too
- Prepare both HTML and plain-text versions
- Make link text specific
- Break long text into short paragraphs
- Do not communicate information only through images
Email environments vary widely, so a simple and clear structure is even more important than on web screens.
16. Route Names and Translated URLs: Decide How Far to Translate
For multilingual sites, whether to translate the URL path itself is also an important decision.
Example without translated paths:
/ja/products
/en/products
Example with translated paths:
/ja/shohin
/en/products
Translated URL paths can feel more natural to users.
On the other hand, routing, link generation, redirects, SEO, and maintenance become more complex.
In practice, it is safer to start with common paths such as /ja/products, then consider translated URLs later if the need becomes strong.
For admin screens in particular, translating URLs is usually unnecessary.
It is also natural to use different policies for public sites and admin screens.
17. SEO: Organize hreflang and Canonical URLs
When multilingualizing a public site, SEO design is also necessary.
If language-specific pages exist, use hreflang to show the relationships between them.
<link rel="alternate" hreflang="ja" href="https://example.com/ja/products">
<link rel="alternate" hreflang="en" href="https://example.com/en/products">
<link rel="alternate" hreflang="x-default" href="https://example.com/">
Canonical URLs should also be set appropriately for each language.
If all language pages point to the same canonical URL, search engines may not handle separate language pages correctly.
Page titles, meta descriptions, and OGP content should also be included in translation targets.
It is surprisingly common for only the body text to be translated while meta information remains in Japanese, so include this in your checklist.
18. Caching: Separate Keys by Language
Multilingualization and caching work well together, but if cache keys are designed incorrectly, languages can get mixed.
For example, if the Japanese homepage is cached and the same cache is returned to English users, it becomes a serious problem.
Always include the language in cache keys.
$key = 'home:' . app()->getLocale();
$posts = Cache::remember($key, 300, function () {
return Post::published()->latest()->take(10)->get();
});
If tenants or user settings are also involved, include them as well.
$key = sprintf(
'dashboard:%s:tenant:%d:user:%d',
app()->getLocale(),
tenant()->id,
auth()->id()
);
In multilingualization, remember this assumption: even if the page looks the same, different languages require separate caches.
19. Translation Operations: Review Translation Files as “Code”
Translation files are not just text collections.
They directly affect screen meaning, error communication, button actions, and user confidence.
Therefore, translation files should be reviewed like code.
Operational rules to decide include the following.
- Standardize writing style
- Decide rules for button text
- Include the cause and next action in error messages
- Decide naming rules for translation keys
- Regularly clean up unused keys
- Detect missing translations in tests or CI
For example, button text such as “保存” may be clearer as “保存する” because it describes an action.
In English too, context-specific text such as Save, Update profile, and Send invitation is important.
Shorter is not always better. Prioritize whether users understand what the button does.
20. Testing: Prevent Missing Translations and Broken UI
In multilingualization, language-specific display tests are helpful in addition to normal feature tests.
20.1 Japanese Display Test
public function test_homepage_is_displayed_in_japanese()
{
$response = $this->get('/ja/products');
$response->assertOk()
->assertSee('商品');
}
20.2 English Display Test
public function test_homepage_is_displayed_in_english()
{
$response = $this->get('/en/products');
$response->assertOk()
->assertSee('Products');
}
20.3 Unsupported Languages Return 404
public function test_unsupported_locale_returns_404()
{
$this->get('/xx/products')
->assertNotFound();
}
20.4 Validation Message Test
public function test_validation_message_is_localized()
{
$response = $this->post('/ja/contact', [
'email' => '',
]);
$response->assertSessionHasErrors('email');
}
With browser tests such as Dusk, you can also check whether long translations break buttons and whether language switching works with a keyboard.
21. Common Pitfalls and How to Avoid Them
21.1 Translating only screen text while validation remains in Japanese
This makes the form experience unnatural.
Translate validation.php and attribute names too.
21.2 Not updating the lang attribute
This affects screen reader pronunciation.
Reflect app()->getLocale() in the layout.
21.3 Not including language in cache keys
This causes pages in different languages to get mixed.
Always include the locale in cache keys.
21.4 Representing language switching only with flags
Countries and languages are not the same.
Display language names as text.
21.5 UI breaks when translated text becomes longer
Design multilingual UI with variable text length, spacing, and wrapping in mind.
21.6 Leaving URL design until later
Changing it later makes redirects and SEO support difficult.
Decide early whether to use /ja/... URLs or a session-based approach.
22. Checklist for Distribution
Basic Design
- [ ] Supported languages are decided
- [ ] You have decided whether to include language in URLs or manage it through user settings
- [ ]
localeandfallback_localeare configured
Translation Files
- [ ] Translation files are managed with
lang:publish - [ ] Rules for PHP array translations and JSON translations are decided
- [ ] Validation messages and attribute names are translated
- [ ] Email subjects and bodies are included in translation targets
UI / Accessibility
- [ ]
<html lang="">reflects the current language - [ ] Language switching does not rely only on flags
- [ ] The selected language is indicated with
aria-currentor similar - [ ] Error summaries are associated with input fields
- [ ] The UI does not break when translated text becomes longer
Dates / Numbers
- [ ] Date formats match language or region
- [ ] Time zones are considered
- [ ] Rules exist for displaying currencies and units
SEO / Caching
- [ ]
hreflangis set on public pages - [ ] Meta titles and descriptions are translated
- [ ] Cache keys include locale
Testing
- [ ] Display tests exist for each supported language
- [ ] Unsupported languages return 404
- [ ] Validation message translations are checked
- [ ] The language-switching UI can be operated by keyboard
23. Conclusion
Laravel multilingualization can begin with translating text using __(). However, in real projects, you need to consider translation files, URL design, validation, dates, currencies, emails, SEO, caching, testing, and accessibility.
First, decide supported languages and URL design, organize text in the lang directory, and set the locale correctly with middleware. Then expand support to validation, emails, dates, and number formatting to create a more natural experience for users.
Accessibility is especially important: the lang attribute, text-based language switching, and clear error messages matter. Multilingualization is not only for overseas support. It is also a design practice for delivering information correctly to diverse users. Start small, then gradually improve translations and UI.

