Laravel Log Missing Translation Strings
Tags: laravel, webdev, programming, beginners
Published: 2024-04-23 08:30:42

Laravel has built-in functionality for dealing with translations. You can store arrays for language strings inside
the `resources/lang` folders and depending on your lcaole settings Laravel will display the correct language.

To use these translation strings in your blade template you can use the `__()` function or the `@lang` blade directive.

```php
echo __('messages.welcome');

@lang('messages.welcome')
```

To find out more about translation strings you can view the documentation here.

[Localization](https://laravel.com/docs/5.7/localization)

The problem you have with using translation strings like this is for each language you need to make sure that you keep
all the translation strings in sync. What if you forget to add one for certain languages?

When you use the `__()` function Laravel will search for the key in your translation strings and return the value. If
Laravel can't find a matching key then it will just output the key on your page, this means you could
have `messages.welcome` appear to your visitors and you may not even know about it.

How do we solve this problem?

In this tutorial we're going to make some changes to how Laravel finds your translation strings and will log any missing
translation strings.

## The __() Function

Laravel has a helper file `helpers.php` which includes the `__()` function. When we look at this code we can see exactly
how it works.

```php
if (! function_exists('__')) {
    /**
     * Translate the given message.
     *
     * @param  string  $key
     * @param  array  $replace
     * @param  string  $locale
     * @return string|array|null
     */
    function __($key, $replace = [], $locale = null)
    {
        return app('translator')->getFromJson($key, $replace, $locale);
    }
}
```

As you can see it will instantiate a translator class from the app container and then call the `getFromJson()` function
to find the translation strings.

What we're going to do is override the `translator` class then we can change the behaviour of the `getFromJson()`
function.

## Translator Override Class

First we need to create a new class that will extend the `Illuminate\Translation\Translator` class.

If you look inside the `getFromJson()` function you'll see the internally this will call the translator `get()` method.
This is then used to look up the translation based off the key you send it. Therefore we're going to override
this `get()` function in our new JSON Translator class.

Inside our `get()` method we need to first call the parent get method.

```php
$translation = parent::get($key, $replace, $locale, $fallback);
```

If the `$translation` variable is the same as the `$key` then the translation was not found and we need to log that it
wasn't found.

```php
if ($translation === $key) {
    // Log missing translation string
}
```

```php
<?php

namespace App\Translator;

use Illuminate\Support\Facades\Log;
use Illuminate\Translation\Translator as BaseTranslator;

/**
 * Class JsonTranslator
 * @package App\Translator
 */
class JsonTranslator extends BaseTranslator
{
    /**
     * @param string $key
     * @param array $replace
     * @param null $locale
     * @param bool $fallback
     *
     * @return array|null|string|void
     */
    public function get($key, array $replace = [], $locale = null, $fallback = true)
    {
        $translation = parent::get($key, $replace, $locale, $fallback);

        if ($translation === $key) {
            Log::warning('Language item could not be found.', [
                'language' => $locale ?? config('app.locale'),
                'id' => $key,
                'url' => config('app.url')
            ]);
        }
        
        return $translation;
    }
}
```

## Override the Default Laravel Translator

To override the default Laravel translator we need to add some code to our `AppServiceProvider.php` file.

In the register method we can hook into the `$this->app` container and extend the `translator` key object.

From this we can take the existing translator class and return our new JsonTranslator class passing in the selected
locale.

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Translation\Translator;
use App\Translator\JsonTranslator;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
       
        // Override the JSON Translator
        $this->app->extend('translator', function (Translator $translator) {
            $trans = new JsonTranslator($translator->getLoader(), $translator->getLocale());
            $trans->setFallback($translator->getFallback());
            return $trans;
        });
    }
}
```

## Logging

Now when you visit your application and it has missing translation strings you'll notice there is new information in
your
log file telling you what translation is missing and the locale.

# Laravel 10.x or more

In the latest versions of Laravel this feature is not built in, you can achieve the above by using the following code
inside you AppServiceProvider.php file.

```php
use Illuminate\Support\Facades\Lang;
 
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Lang::handleMissingKeysUsing(function (string $key, array $replacements, string $locale) {
        info("Missing translation key [$key] detected.");
 
        return $key;
    });
}
```