An incomplete guide in Laravel 8 localization
Tags: php, laravel, i18n, localization
Published: 2021-10-12 21:18:45


We recently localized [Siggy](http://www.siggy.ai), 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](https://github.com/xcf33/laravel8-localization) 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](#chatper-1)
2. [Localization approaches](#chapter-2)
3. [Setting up the new Laravel application](#chapter-3)
4. [Locale configuration/scaffolding](#chapter-4)
5. [Basic translation](#chapter-5)
6. [Building the language switcher](#chapter-6)
7. [Translating static pages](#chapter-7)
8. [Localize menu links, authentication routes](#chapter-8)
9. [(Bonus) Form validation](#chapter-9)
10. [Learning References](#references)
11. [Resources](#resources) 

##1. Introduction to localization <a name="chapter-1"></a>
🇺🇸 🇷🇺 🇮🇳 🇩🇪 🇦🇺 🇩🇰 🇧🇷 🇨🇳 

**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 <a name="chapter-2"></a>

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vex1z4j7jzs4pruyqrep.png)

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

![Localized google home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7va89h1rprxoqwvy2xi.png)

(_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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qzmseec4rdz2okqb6j93.gif)

> 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 <a name="chapter-3"></a>

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
```
(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
```

Add a simple authentication package for the project, in your command line
```
composer require laravel/ui --dev
``` 

Install the authentication scaffolding 
```
php artisan ui vue --auth
```

If needed, re-compile the assets (CSS/javascript)
```
npm run dev
```

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

Fire up the local server
```
php artisan serve
```

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

![Laravel home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2osvb1fbbergy7o4qxd9.png)

##4. Locale configuration/Scaffolding <a name="chapter-4"></a>

**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.

```php
##
# Custom configuration for our app locales
#
return [
    'locales' => ['da', 'en', 'zh_CN'],
    'default_locale' => 'en',
];
```

> 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
```

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

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);
    }
}
```
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` 

```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,
        ],
    ];
```


> 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

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3t2157n4vthxfjpws6ep.gif)

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

```php
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');    
});
```

##5. Basic translation <a name="chapter-5"></a>

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') }}
```

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

![What we are translating](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6p8og4jxoyuvhmvn82be.png)

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

```html
@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
```

**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`

```json
{
    "Documentation": "文档",
    "Laravel News": "Laravel 新闻",
    "Vibrant Ecosystem": "充满活力的生态系统",
    "Home": "主页",
    "Login": "登陆",
    "Log in": "登陆",
    "Register": "注册"
}
```

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

![Chinese translation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50mhsazq03zo33k3esoo.png)

> 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 <a name="chapter-6"></a>

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

![Language switcher](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qzmseec4rdz2okqb6j93.gif)

{% codepen https://codepen.io/xcf33/pen/KKvPwwO %}

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`

```html
<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>
```

**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:

```css
  .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');
  }
```

**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. 

```javascript
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');
```

Then simply run the build in the command line

```
npm run dev
```

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. 🤦 

```html
<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" />
```

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

```html
 @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
```

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

```html
<script type="text/javascript">
    $(function() {
        let switcher = new Switcher();
        switcher.init();
    });
</script>
```

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

![Language switcher](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ypfnozsiamv3or1rzo2.gif)

##7. Translating static pages <a name="chapter-7"></a>

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
```

The controller should look like:

```php
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);
        }
    }
}
```

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`

```html
<!-- /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
```

```html
<!-- /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
```

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

```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');    

});
```

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
```html
<script src="{{ asset('js/switcher.js') }}" defer></script>
```

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

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

Finally, before the end of `<body>`
```html
<script type="text/javascript">
    $(function() {
        let switcher = new Switcher();
        switcher.init();
    });
</script>
```

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

![Static page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ocv837b4i06dmftyk7j.gif)

##8. Localize menu links, authentication routes <a name="chapter-8"></a>

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`:

```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();
});
```

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

![URL error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3cb2w3kur0ao1vj229m.png)


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:

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

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:

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5ou63wwhsdpdbrc07f3.gif)
 
###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](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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/319bvjbo739r477gg6kl.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkx9kuzw2pgv1hit2eu9.png)

As a bonus, the validation messages are also translated:

![Translated validation message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zgrycrhnh6inksxmoqs.png)

###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.

```php
public function redirectTo()
{
    return app()->getLocale() . '/example';
}
```

![Localized login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ychar5rcb4vjup85d24c.gif)

###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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dht6pn53d5i52pgzsgd9.png)

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`.

```php
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);
    }
}
```

> It is worth mentioning from the [official Laravel documentation](https://laravel.com/docs/8.x/urls#default-values) 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 <a name="chapter-9"></a>

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](https://github.com/Laravel-Lang/lang) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4ubw94jaguh861gr1xy.png)

```html
@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
```

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

```
php artisan make:controller ExampleFormController
```

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

```php
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()]);
    }
}
```

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() { ... })`

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

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

```json
{
    ...
    "Example Form": "示例表格",
    "Submit": "提交",
    "Enter a integer number": "输入一个整数"
}
```

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

![Localized form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c56oslr8xmoarg4dbico.png)

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

![Validation message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/re32ii46e6f76fhvks8r.png)

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:

```php

return [
...

    'attributes' => [
        'int_number'            => '整数',
    ],
];
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/os6948tc3pz8u7434bpt.png)

##Learning References  <a name="references"></a>

[Laravel from Scratch](https://laracasts.com/series/laravel-8-from-scratch)

[Laravel Language Switcher]
(https://www.youtube.com/watch?v=KqzGKg8IxE4&t=939s)

[Multi-Language Routes and Locales with Auth](https://laraveldaily.com/multi-language-routes-and-locales-with-auth/)

##Resources <a name="resources"></a>

[Laravel starter translation files](https://github.com/Laravel-Lang/lang)

[CSS Flag icons](https://github.com/lipis/flag-icon-css)