Paulund
2023-11-26 #laravel

Laravel Log Missing Translation Strings

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.

echo __('messages.welcome');

@lang('messages.welcome')

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

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.

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.

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

if ($translation === $key) {
    // Log missing translation string
}
<?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

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.

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