Courses

Multi-Language Laravel: All You Need to Know

Store in PHP or JSON? Use trans() or __()?

Have you seen something like this __() function in the code?

<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
</div>

These are Static Text, and in Laravel they can be translated with trans() and __() helper functions. In this lesson, we will see how to store translations and how to use those helpers.

To store the translations strings, we have a few options:

  • Multiple .php file(s) for each language
  • Or, one big .json file for each language

They both contain translations but have some key differences. Let's discuss both.


Storing Translations in .php Files

This was the default way for quite a long time.

Example: we have a View file with a translation string using the __() helper.

resources/views/auth/register.blade.php

<!-- Name -->
<div>
<x-input-label for="name" :value="__('auth.register.name')" />
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
<x-input-error :messages="$errors->get('name')" class="mt-2" />
</div>

To translate this, we need to add a translation string to the /lang/en/auth.php file:

/lang/en/auth.php

return [
'register' => [
'name' => 'Name',
'email' => 'Email',
// ...
],
'login' => [
'login' => 'Login',
// ...
],
];

Notice: "lang" Folder in Laravel versions

Looking at that /lang/en/auth.php file above, one thing you need to be aware of.

By default, Laravel static text translations are stored in the /lang folder. But in Laravel 10 that lang folder is not included in the beginning.

Running the following artisan command will add it:

php artisan lang:publish

This will create a lang folder in your root directory and add the en folder inside with default translation strings from Laravel core.

In earlier Laravel versions, you may find that translations are stored in the /resources/lang folder. That will also work in the latest Laravel version but is considered an old/obsolete approach.


So, the code __('auth.register.name') will load the translation string from the auth.php file and return the Name string.

See that auth.register.name parameter? The first part of it is the filename: /lang/[language]/[filename].php. All the other dot-separated parts are the keys of the array inside that specific file. In our case, it's register.name.

If there is no translation with that key, or if that file doesn't exist - you won't get an error, you'll just get the same key back:

Benefit of .php Files

  • You can have multi-level nested keys.
  • You will most likely separate your translations by the feature or sub-system. For example: auth.php, validation.php, pagination.php, etc. This will make it easier to find the translation you are looking for.
  • You can have identical keys in different files: __('auth.register.name') and __('validation.name') will both return the same translation but can be managed separately.
  • You can comment in the .php files. This is useful if you have a lot of translation strings, and want to add some context to them.

Drawback of .php Files

  • You need to type all strings in the files immediately. Otherwise, you risk displaying an ugly key to the end user.
  • Inconvenient for non-dev translator people: they will have to work with multiple files/paths and understand what they can and can't change in the code. Especially if you have nested keys.
  • Potentially bigger mess: things can quickly get out of hand, and you might end up with a lot of files and folders.

Storing Translations in .json Files

JSON files are a bit different from .php files. They contain a single list with all the translation strings.

In our blade, the translation will look like this:

resources/views/auth/register.blade.php

<!-- Name -->
<div>
<x-input-label for="name" :value="__('Name')" />
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
<x-input-error :messages="$errors->get('name')" class="mt-2" />
</div>

See that 'Name' parameter? It's not the auth.register.name key anymore, right? This is exactly the difference: the keys of JSON files are in a human-readable form.

So, even if Laravel doesn't find the key or the translation file - it will still display Name in our UI because that's the key we passed to the __() helper function.

But if we add a translation file - it will be used instead:

resources/lang/en.json

{
"Name": "Your Name"
}

This allows us to write complete text in the __() function key and not worry about a path or missing translations.

Benefits of .json Files

  • You can write full sentences as a key and later have them translated.
  • Passing these files to a non-dev translator is much easier: they don't need to worry about technical details.
  • Using the same key in multiple views will result in the same translation. For example: __('Name') in auth/register.blade.php and auth/login.blade.php will result in the same translation.

Drawbacks of .json Files

  • You can't have nested keys. This means that all your text will be in a single file and a single layer. It's not possible to write __('auth.Name') and have a JSON file with the auth key and Name key inside of it. But you can have "auth.Name": "Name" in the same file which will load. This is not ideal, and you should only use it to translate the default Laravel translations as seen here
  • You will not be able to have context for the translation. For example: __('Name') in auth/register.blade.php and auth/login.blade.php will result in the same translation. If different languages require different translations for the same word in different contexts - you won't be able to do that.
  • Your translation file will be huge. If you have a lot of translation strings - you'll end up with a huge JSON file. This can be a problem if you have a lot of languages, and you need to translate the same string in all of them.
  • You can't write comments in the .json files.

Which Should You Pick?

This is not a simple question to answer. The good thing is that they are pretty much interchangeable. With some caveats, of course.

You can move from .php files to .json files relatively easily. Moving from .json files to .php files is a bit more complicated as you will need to change all your keys to paths.

But it does not matter that much which one you will pick. As long as you are consistent - you can use either one or combine them.


Notice: Problems When Mixing JSON and PHP Files

Using both .php and .json files could lead you to a problem where you have a key in JSON that matches the filename in PHP. For example:

resources/lang/en/auth.php

return [
'name' => 'Name',
'failed' => 'These credentials do not match our records.',
'password' => 'The provided password is incorrect.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];

resources/lang/en.json

{
"Auth": "Authentication"
}

Let's call the __() helper function with a key of Auth anywhere in our view template:

resources/views/auth/register.blade.php

{{ __('Auth') }}

Running this will result in an error:

Let's dump the __('Auth') function to see what's going on:

As you can see, we got the content of the default /lang/en/auth.php file and not our expected string of Authentication.

This is because the __() helper function will first look for a translation file with the same name as the key. If it finds it - it will return the whole file. If it doesn't find it - it will look for a translation string in the lang/en.json file.


trans() VS __(): Which To Use?

You might have noticed that some people prefer __() over trans() and that's okay. But have you ever looked at what the difference is?

__() is a helper function that calls the trans() function under the hood:

vendor/laravel/framework/src/Illuminate/Foundation/helpers.php

if (! function_exists('__')) {
/**
* Translate the given message.
*
* @param string|null $key
* @param array $replace
* @param string|null $locale
* @return string|array|null
*/
function __($key = null, $replace = [], $locale = null)
{
if (is_null($key)) {
return $key;
}
 
return trans($key, $replace, $locale);
}
}

The biggest difference here is what happens if you pass no value to __():

  • __() will return null.
  • trans() will return the Illuminate\Translation\Translator instance. This allows you to chain more methods on it. For example, trans()->getLocale() will return the current locale.

So which should we use? It's up to you! I prefer __() for translation strings and trans() for other cases (like getting current locale, settings, etc).


In the next lesson, we'll work with locales and how to tell Laravel which is your primary language.

avatar

Thank you, Povilas. Could you please comment a difference regarding speed of the both solutions (JSON vs PHP files)? Which of the both solutions would be theoreticaly faster, and is there a notable difference?

👍 2
avatar

Hi, there's not really a big difference here. Previously we were using PHP files for translations but recently Laravel scaffolding (Breeze, Jetstream) started moving more towards the JSON files. It's more like a personal preference in my eyes.

avatar

In Laravel 10.9.0 with breeze 1.20.2 where would I find the /lang/en/auth.php file ?, I do not see it at all.

avatar

Repeating one paragraph from the lesson above.

"In Laravel 10 that lang folder is not included in the beginning. Running the following artisan command will add it:" php artisan lang:publish