DEV Community

Cover image for An incomplete guide in Laravel 8 localization
Chang Xiao
Chang Xiao

Posted on

An incomplete guide in Laravel 8 localization

We recently localized Siggy, our Laravel-based web app. It was frustrating to not find one comprehensive guide on how to localize an app end-to-end.

I wrote this to cover as much as possible on how to localize your Laravel application using the URL as the context (e.g. example.com/en/, example.com/fr/) It is written and tested with Laravel 8.

You can checkout the codebase for this guide to follow along.

What's covered

Feel free to jump to a that applies to you if you do not want to follow the whole guide.

  1. Introduction to localization
  2. Localization approaches
  3. Setting up the new Laravel application
  4. Locale configuration/scaffolding
  5. Basic translation
  6. Building the language switcher
  7. Translating static pages
  8. Localize menu links, authentication routes
  9. (Bonus) Form validation
  10. Learning References
  11. Resources

1. Introduction to localization

๐Ÿ‡บ๐Ÿ‡ธ ๐Ÿ‡ท๐Ÿ‡บ ๐Ÿ‡ฎ๐Ÿ‡ณ ๐Ÿ‡ฉ๐Ÿ‡ช ๐Ÿ‡ฆ๐Ÿ‡บ ๐Ÿ‡ฉ๐Ÿ‡ฐ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡จ๐Ÿ‡ณ

Locale != Country != Language
It is important in some locales (within a country), different languages are used. For example, pt-BR (Portuguese used in Brazil) and pt-PT (Portuguese used in Portugal).

Sometimes it is ok to think of locales just as languages (English, Portuguese, Chinese, etc) if you simply want to provide language translations. However, understanding the nuances in a locale can be helpful if your goal is to serve a specific market.

2. Localization approaches

There are many approaches in building localization into an application, usually it consists of three parts:

Detecting the locale

The "locale context" can be detected in many different ways via Auto locale detection and/or Manual context

Auto locale detection
For example, you can use language detection from the user's web browser preferences to guess the locale. You can also use the geolocation information (e.g. IP address) to identify the location.

Google language preference

(For example, setting your google account language preference will provide a language context to google Chrome.)

Localized google home page

(Subsequently, Google will use this context to localize its content for you.)

Manual context
Manual context means the user explicitly gives you the information on their language/locale preferences. The most common example is the user using a language switcher on a website. Additionally, if the user revisits an app/website frequently, they can also set a language preference under their user account.

Language switcher

There is no right or wrong way to obtain the context from the user, it is often the best practice to use a combination of auto locale detection and manual context (override).

Setting the locale

Once you have determined the user's locale, you can set the locale in the app so it knows how to personalize/translate the user interface and contents. This can be done via the URL (e.g. example.com/en/, example.com/fr/), via a session variable, a cookie, or setting the locale persistently for the user in the app database.

For this guide, we will be using the URL to set the locale for the user.

Translation

Finally, there will be translated content, UI elements, and others associated with each locale. Translation can be provided by either:

  • Machine translation from google translate or similar via an API.
  • Manual translation from humans provided in translation strings

We will be using manual translations in this guide.

3. Setting up the new Laravel application

In this example, we are setting up a vanilla Laravel (not Jetstream or other enhanced starter versions).

Create a new Laravel project, in your command line

Laravel new localization-example
Enter fullscreen mode Exit fullscreen mode

(Assuming you have Laravel installed globally)

Under your project .env file, make the necessary configuration changes, at a minimal set up a database connection for the app.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=example
DB_USERNAME=user
DB_PASSWORD=pass
Enter fullscreen mode Exit fullscreen mode

Add a simple authentication package for the project, in your command line

composer require laravel/ui --dev
Enter fullscreen mode Exit fullscreen mode

Install the authentication scaffolding

php artisan ui vue --auth
Enter fullscreen mode Exit fullscreen mode

If needed, re-compile the assets (CSS/javascript)

npm run dev
Enter fullscreen mode Exit fullscreen mode

Finally, run the database migration command in your command line to create the authentication-related database tables:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Fire up the local server

php artisan serve
Enter fullscreen mode Exit fullscreen mode

You should now have a bare bone Laravel app with authentication that looks like (http://127.0.0.1:8000/):

Laravel home page

4. Locale configuration/Scaffolding

Locale configuration
We will first create our locale configuration file under the config directory of your Laravel app called locale.php, we will rely on this set of configurations for our locale checking/setting. We define the list of locales allowed for our app as well as a default locale.

##
# Custom configuration for our app locales
#
return [
    'locales' => ['da', 'en', 'zh_CN'],
    'default_locale' => 'en',
];
Enter fullscreen mode Exit fullscreen mode

There is a reason we are adding a default_locale configuration vs. using app.locale which will be explained later.

Creating a middleware
Next, we will create a middleware that sets the locale based on the URL pattern (e.g. example.com/da/). The user will manually provide their locale to us via a language switcher widget on the website.

In your command line

php artisan make:middleware SetLocale
Enter fullscreen mode Exit fullscreen mode

Inside of the middleware we have created, it should look like:


namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL;

class SetLocale
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $locale = $request->segment(1);
        if (in_array($locale, config('locale.locales'))) {
            App::setLocale($locale);
        } 
        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

This essentially checks for the first part of the URL path (e.g. example.com/en/some/path) and sets the app locale if it matches with one of the locales (en) in our configuration. It prevents users from attempting to guess for the locale in the URL that does not exist (e.g. example.com/foo)

Also, add the new middleware in the app/http/Kernel.php

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \App\Http\Middleware\SetLocale::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
Enter fullscreen mode Exit fullscreen mode

We will come back to the middleware later to add default URL parameters.

Routing groups and default locale

We will now create a new route group in the web.php route file. This will add the locale prefix for all of the routes in this group.

For example, let's move the default home page route under the routing group

Route::group(['prefix' => '{locale}'], function() {
    Route::get('/', function () {
        return view('welcome');
    })->name('welcome');    
});
Enter fullscreen mode Exit fullscreen mode

As you can see, the new home can be accessed via different locales e.g. example.com/en/, example.com/fr/, etc. However, if you access the root URL, you will get a 404 because it no longer exists.

Home page 404

Let's add a redirect from the root URL in the web.php. This will redirect to a default locale (in our case /en)

Route::get('/', function () {
    return redirect()->route('welcome', ['locale' => config('locale.default_locale')]);
}); 

// Localized routes
Route::group(['prefix' => '{locale}'], function() {
    Route::get('/', function () {
        return view('welcome');
    })->name('welcome');    
});
Enter fullscreen mode Exit fullscreen mode

5. Basic translation

To recap, at this point we have:

  1. A new Laravel app scaffolded with authentication
  2. We have created a custom configuration locale.php to house the list of allowed locales and the default locale for our app (In this case, en, da, zh_CN)
  3. We have created a middleware that takes the first part of the URL path and use it to detect and set the locale (e.g. example.com/en/, example.com/zh_CN/)
  4. We created a new route group in web.php and moved our home page route there.

At this point, all we need is some translated content and we can show some localization! Let's work on the home page (resources/views/welcome.blade.php)

Making texts translatable
The first thing we need to do is make sure the texts (strings) in the template are translatable. This means they should look like the following:

{{ __('Some translatable string') }}
Enter fullscreen mode Exit fullscreen mode

For demo purposes, let's find the following texts from the welcome.blade.php and wrap them in the {{ __() }} function :

What we are translating

For example, the top navigation links now should look similar to:

@if (Route::has('login'))
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
    @auth
    <a href="{{ url('/home') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Home') }}</a>
    @else
    <a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Log in') }}</a>

    @if (Route::has('register'))
    <a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Register') }}</a>
    @endif
    @endauth
</div>
@endif
Enter fullscreen mode Exit fullscreen mode

Creating the translation file
Now the texts are translatable, we are finally able to provide the translation for them. There are several ways to do this but we will be using the {locale}.json method.

We will provide a Chinese simplified translation file in resources/lang/zh_CN.json

{
    "Documentation": "ๆ–‡ๆกฃ",
    "Laravel News": "Laravel ๆ–ฐ้—ป",
    "Vibrant Ecosystem": "ๅ……ๆปกๆดปๅŠ›็š„็”Ÿๆ€็ณป็ปŸ",
    "Home": "ไธป้กต",
    "Login": "็™ป้™†",
    "Log in": "็™ป้™†",
    "Register": "ๆณจๅ†Œ"
}
Enter fullscreen mode Exit fullscreen mode

Now if you visit the homepage (http://127.0.0.1:8000/zh_CN/) you should see these texts translated!

Chinese translation

We only translated part of the page as a demo, we will come back to this later to show a way to translate the content of the entire page (static page)

6. Building the language switcher

We have now built the essential framework for localizing content in the app, the only main component left is the language switcher.

Language switcher

There are 3 parts to the language switcher: the template markup, CSS, and javascript.

The template markup

Let's create a blade template called resources/views/switcher.blade.php

<select class="lang-switcher">
    <option value="en" class="test" data-flag="us">English</option>
    <option value="zh_CN" data-flag="cn">็ฎ€ไฝ“ไธญๆ–‡</option>
    <option value="da" data-flag="dk">Dansk</option>
</select>

<div class="lang-select">
    <button class="lang-button" value=""></button>
    <div class="lang-b">
        <ul id="lang-a"></ul>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

The CSS

Copy the CSS from the codepen into resources/css/app.css, copy the corresponding flag icon svg into the public/img/flags/ directory, the CSS for your flag icons should look like:

  .flag-icon-cn {
    background-image: url('/img/flags/cn.svg');
  }

  .flag-icon-dk {
    background-image: url('/img/flags/dk.svg');
  }

  .flag-icon-us {
    background-image: url('/img/flags/us.svg');
  }
Enter fullscreen mode Exit fullscreen mode

The Javascript

The script should be placed into resources/js/switcher.js, note that it requires jQuery to run.

Putting it together

First, we will also need to adjust the webpack.mix.js file for the app to compile our new javascript and CSS. This will add our javascript and CSS into the compiled public/css/app.css and create a new public/js/switcher.js respectively.

mix.js('resources/js/app.js', 'public/js')
    .js('resources/js/switcher.js', 'public/js/switcher.js')
     .vue()
     .sass('resources/sass/app.scss', 'public/css')
     .css('resources/css/app.css', 'public/css');
Enter fullscreen mode Exit fullscreen mode

Then simply run the build in the command line

npm run dev
Enter fullscreen mode Exit fullscreen mode

Now, let's add jQuery, the switcher.js and app.css into the <head> section of the resources/views/welcome.blade.php since it uses its own HTML template. ๐Ÿคฆ

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="{{ asset('js/switcher.js') }}"></script>
<link href="{{ asset('css/app.css') }}" rel="stylesheet" />
Enter fullscreen mode Exit fullscreen mode

We then also drop the language switcher @include('switcher') in before the login link in resources/views/welcome.blade.php

 @if (Route::has('login'))
                <div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
                    @include('switcher')
                    @auth
                        <a href="{{ url('/home') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Home') }}</a>
                    @else
                        <a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Log in') }}</a>

                        @if (Route::has('register'))
                            <a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Register') }}</a>
                        @endif
                    @endauth
                </div>
            @endif
Enter fullscreen mode Exit fullscreen mode

Finally, we need to initialize the language switcher, let's add some javascript before the end of <body> tag.

<script type="text/javascript">
    $(function() {
        let switcher = new Switcher();
        switcher.init();
    });
</script>
Enter fullscreen mode Exit fullscreen mode

Now if you visit the app at http://127.0.0.1:8000/en/, you should see the language switcher at work!

Language switcher

7. Translating static pages

The example above is great for translating text (strings) on a page. However, if we have an entire page of content, the {locale}.json method is not practical.

Let's create a controller (in the command line):

php artisan make:controller PageController
Enter fullscreen mode Exit fullscreen mode

The controller should look like:

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PageController extends Controller
{
    /**
     * Static page router
     */
    public function page($locale, $page) {
        // Views directory are structured with locales as sub directories
        $default_dir = 'pages.' . config('locale.default_locale'); // default locale
        if (in_array($locale, config('locale.locales'))) {
            $view_dir = 'pages.' . $locale;
        }
        // Return the default language page when there's no corresponding view found
        try {
            return view($view_dir . '.' . $page);
        } catch(\InvalidArgumentException $e) {
            return view($default_dir . '.' . $page);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The controller simply looks for blade templates in the views directory such as /resources/views/pages/{locale}/{page_name}.blade.php and serve it.

Next, let's create some example contents, we will create 2 template files, /resources/views/pages/en/example.blade.php and /resources/views/pages/zh_CN/example.blade.php

<!-- /resources/views/pages/en/example.blade.php -->
@extends('layouts.app', ['class' => 'example'])

@section('content')
<p>Winnie-the-Pooh, also called Pooh Bear and Pooh, is a fictional anthropomorphic teddy bear created by English author A. A. Milne and English illustrator E. H. Shepard.</p>

<p>The first collection of stories about the character was the book Winnie-the-Pooh (1926), and this was followed by The House at Pooh Corner (1928). Milne also included a poem about the bear in the children's verse book When We Were Very Young (1924) and many more in Now We Are Six (1927). All four volumes were illustrated by E. H. Shepard.</p>

<p>The Pooh stories have been translated into many languages, including Alexander Lenard's Latin translation, Winnie ille Pu, which was first published in 1958, and, in 1960, became the only Latin book ever to have been featured on The New York Times Best Seller list.</p>

<p>In 1961, Walt Disney Productions licensed certain film and other rights of Milne's Winnie-the-Pooh stories from the estate of A. A. Milne and the licensing agent Stephen Slesinger, Inc., and adapted the Pooh stories, using the unhyphenated name "Winnie the Pooh", into a series of features that would eventually become one of its most successful franchises.</p>

<p>In popular film adaptations, Pooh has been voiced by actors Sterling Holloway, Hal Smith, and Jim Cummings in English, and Yevgeny Leonov in Russian.</p>
@endsection
Enter fullscreen mode Exit fullscreen mode
<!-- /resources/views/pages/zh_CN/example.blade.php -->
@extends('layouts.app', ['class' => 'example'])

@section('content')
<p>ๅฐ็†Š็ปดๅฐผ๏ผˆไธญๅ›ฝๅคง้™†ไนŸไฝœๅ™—ๅ™—็†Š๏ผ‰[1]๏ผŒ1925ๅนด12ๆœˆ24ๆ—ฅ้ฆ–ๆฌก้ขไธ–๏ผŒไปฅๅœฃ่ฏžๆ•…ไบ‹ๅฝขๅผๅœจไผฆๆ•ฆใ€Šๆ–ฐ้—ปๆ™šๆŠฅใ€‹ๅˆŠๅ‡บ๏ผ›็ฌฌไธ€ๆœฌใ€Šๅฐ็†Š็ปดๅฐผใ€‹ๆ•…ไบ‹ไนฆไบŽ1926ๅนด10ๆœˆๅ‡บ็‰ˆใ€‚ๅฐ็†Š็ปดๅฐผๆ˜ฏ่‰พไผฆยทไบšๅŽ†ๅฑฑๅคงยท็ฑณๆฉไธบไป–็š„ๅ„ฟๅญๅˆ›ไฝœ็š„ไธ€ๅช็†Š้€ ๅž‹ๅก้€šๅฝข่ฑก๏ผ›่€Œๅคๅ…ธ็ปดๅฐผๆ˜ฏ็”ฑ่ฐขๅŸนๅพท๏ผˆE.H.Shepard๏ผ‰ๆ‰€็ป˜๏ผŒๅŽ็”ฑๅŽ็‰นยท่ฟชๅฃซๅฐผๅ…ฌๅธ่ดญๅ…ฅๅนถ็ป่ฟ‡้‡ๆ–ฐ็ป˜ๅˆถ๏ผŒๆŽจๅ‡บๅŽๅ› ๅ…ถๅฏ็ˆฑ็š„ๅค–ๅž‹ไธŽๆ†จๅŽš็š„ไธชๆ€ง๏ผŒ่ฟ…้€Ÿๆˆไธบไธ–็•Œ็Ÿฅๅ็š„ๅก้€š่ง’่‰ฒไน‹ไธ€ใ€‚ๆญคๅŽไบบไปฌไธบไบ†ๅŒบๅˆซไธค็งไธๅŒ้ฃŽๆ ผ็š„็ปดๅฐผ๏ผŒ็งฐๅ‘ผ็ฑณๅฐ”ๆฉๆ—ถๆœŸ็”ฑ่ฐขๅŸนๅพท๏ผˆE.H.Shepard๏ผ‰็ป˜ๅˆถ็š„็ปดๅฐผไธบโ€œๅคๅ…ธ็ปดๅฐผโ€๏ผˆClassic Pooh๏ผ‰๏ผ›่€ŒๅŽๅ…จ็ƒ็ฒ‰ไธไพฟๆŠŠ็ฑณๅฐ”ๆฉ็š„็”Ÿๆ—ฅ๏ผˆ1ๆœˆ18ๆ—ฅ๏ผ‰ๆŽจไธบๅ›ฝ้™…ๅฐ็†Š็ปดๅฐผๆ—ฅใ€‚</p>
@endsection
Enter fullscreen mode Exit fullscreen mode

Next, let's add the controller method in the routing file web.php, our route group should now look like:

// Localized routes
Route::group(['prefix' => '{locale}'], function() {
    Route::get('/', function () {
        return view('welcome');
    })->name('welcome');

    Route::get('/example', [App\Http\Controllers\PageController::class, 'page'])
    ->name('example')
    ->defaults('page', 'example');    

});
Enter fullscreen mode Exit fullscreen mode

We are using default parameters to tell the controller which corresponding page view to use.

Finally, similar to what we did in the welcome.blade.php, let's add the language switcher in the /resources/views/layouts/app.blade.php

In the <head> section

<script src="{{ asset('js/switcher.js') }}" defer></script>
Enter fullscreen mode Exit fullscreen mode

Next, in the top right navigation menu, add the switcher markup:

<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
    <li class="nav-item pt-2">@include('switcher')</li>
Enter fullscreen mode Exit fullscreen mode

Finally, before the end of <body>

<script type="text/javascript">
    $(function() {
        let switcher = new Switcher();
        switcher.init();
    });
</script>
Enter fullscreen mode Exit fullscreen mode

Now let's hit the URL http://localhost:8000/en/example, you should see the page with the language switcher in action:

Static page

8. Localize menu links, authentication routes

After translating basic text strings and static content, let's look at the authentication pages. These are the login, registration, password reset pages provided by Laravel when we first created the project under 3. Setting up the new Laravel application

Localize menu routes

The first step is to add the authentication routes into our route group in your web.php:

// Localized routes
Route::group(['prefix' => '{locale}'], function() {
    Route::get('/', function () {
        return view('welcome');
    })->name('welcome');

    Route::get('/example', [App\Http\Controllers\PageController::class, 'page'])
    ->name('example')
    ->defaults('page', 'example');    

    Auth::routes();
});
Enter fullscreen mode Exit fullscreen mode

After doing this, when you visit the http://localhost:8000, you might see an error page that looks like:

URL error

This error occurs because we have links in templates that are not localized. In this case in our /resources/views/welcome.blade.php we have links that look like:

<a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Log in') }}</a>
Enter fullscreen mode Exit fullscreen mode

We will need to go through the codebase for all blade templates under /resources/views and replace all the links with localized versions.

For example:

<a href="{{ route('login', ['locale' => app()->getLocale()]) }}" class="text-sm text-gray-700 dark:text-gray-500 underline">{{ __('Log in') }}</a>
Enter fullscreen mode Exit fullscreen mode

Once all the links are localized, we can now access the localized authentication pages again (user login, registration, etc)

2021-10-08 09.30.12

Translate authentication routes

To translate the texts on the authentication routes, we will take a lazy approach of using contributed language starter packs from https://github.com/Laravel-Lang/lang since these are standard texts for most of the web applications.

Let's download the repo and take the locale we want (zh_CN):

Language pack

Notice it comes with a zh_CN.json as well as other authentication, validation related translation files.

  1. Let's copy the entire zh_CN directory to your Laravel project under /resources/lang.

  2. Combine the content of the zh_CN.json into the one we have created previously, be aware there might be some duplicated JSON keys such as login, register.

If you come back to the authentication pages, most of the content is now translated!

Registration page

As a bonus, the validation messages are also translated:

Translated validation message

Login/Registration redirect

Now that we can access the authentication routes such as the login/registration form, let's make the form redirects to their localized destinations.

You should be able to find LoginController.php and RegisterController.php under app/Http/Controllers/Auth in the project directory.

Inside of the controller, simply create a method called redirectTo. In our example, I am redirecting the user to the example page (e.g. /en/example) after they log in.

public function redirectTo()
{
    return app()->getLocale() . '/example';
}
Enter fullscreen mode Exit fullscreen mode

Localized login

Password Reset Email

If we try to reset our password via the password reset form, we will get an URL generation error.

URL reset form

Since these authentication controllers come from a package and we cannot change their URL parameters without changing the package code, the easiest solution is the set a default URL parameter for them.

In your SetLocale.php middleware, let's bring in the URL facade and set a default parameter of locale.

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL;

class SetLocale
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $locale = $request->segment(1);
        if (in_array($locale, config('locale.locales'))) {
            App::setLocale($locale);
            URL::defaults(['locale' => $locale]);
        } 
        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

It is worth mentioning from the official Laravel documentation to set the appropriate $middlewarePriority in the app/Http/Kernel.php with the SetLocale.php middleware to not interfere with Laravel's route model binding.

9. (Bonus) Form validation messages

Custom Forms are a common part of any web apps today and form validation messages are easily overlooked in localization.

If you have downloaded the corresponding language files from Translate authentication routes section earlier, this is a much easier task (Some default validation messages are already translated!!).

Let's first create a view under resources/views/form.blade.php. This will be a very simple example form that asks the user to enter an integer number to demonstrate the form validation messages.

Example form

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Example Form') }}</div>
                <div class="card-body">
                    <form method="POST" action="{{ route('example-form', ['locale' => app()->getLocale()]) }}">
                        @csrf
                        <div class="form-group row">
                            <label for="int_number" class="col-md-4 col-form-label text-md-right">{{ __('Enter a integer number') }}</label>

                            <div class="col-md-6">
                                <input id="int_number" type="text" class="form-control @error('int_number') is-invalid @enderror" name="int_number" value="{{ old('int_number') }}" autofocus>

                                @error('int_number')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Submit') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
Enter fullscreen mode Exit fullscreen mode

Next, let's create a controller for the form, in your command line:

php artisan make:controller ExampleFormController
Enter fullscreen mode Exit fullscreen mode

Inside of that controller, we will create two simple methods, one to render the form, one to check the form input.

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ExampleFormController extends Controller
{

    function view() {
        return view('form');
    }

    function store() {
        $validation_rules = [
            'int_number' => 'required|integer',
        ];

        request()->validate($validation_rules);

        return redirect()->route('example-form', ['locale' => app()->getLocale()]);
    }
}
Enter fullscreen mode Exit fullscreen mode

We specified 2 rules for our form input, making it required and making sure the user enters an integer.

Let's add routing to the controller actions, in the routes/web.php, let's add 2 more routes inside of our routing group Route::group(['prefix' => '{locale}'], function() { ... })

    Route::get('/form', [App\Http\Controllers\ExampleFormController::class, 'view'])->name('example-form');
    Route::post('/form', [App\Http\Controllers\ExampleFormController::class, 'store']);
Enter fullscreen mode Exit fullscreen mode

Finally, we can translate the form title, input, etc by adding lines to the zh_CN.json as we learned from earlier:

{
    ...
    "Example Form": "็คบไพ‹่กจๆ ผ",
    "Submit": "ๆไบค",
    "Enter a integer number": "่พ“ๅ…ฅไธ€ไธชๆ•ดๆ•ฐ"
}
Enter fullscreen mode Exit fullscreen mode

Now if you go to http://localhost:8000/zh_CN/form, you should see the localized form:

Localized form

The problem is that if we try to trigger a validation message, you will see part of the validation message translated.

Validation message

The translated validation message comes from the language files we downloaded earlier. Specifically, you can find a file called /resources/lang/zh_CN/validation.php.

In this file, let's add an element called attributes towards the end of the file:


return [
...

    'attributes' => [
        'int_number'            => 'ๆ•ดๆ•ฐ',
    ],
];
Enter fullscreen mode Exit fullscreen mode

The element name int_number is the name attribute from the input we specified in the blade template earlier. Now when the validation message is triggered, everything should be translated.

Validation message

Learning References

Laravel from Scratch

Laravel Language Switcher

Multi-Language Routes and Locales with Auth

Resources

Laravel starter translation files

CSS Flag icons

Top comments (0)