Building a Laravel Translation Package – Handling Missing Translation Keys

Published on by

Building a Laravel Translation Package – Handling Missing Translation Keys image

In the last instalment of this series, we talked about building the frontend translation management tool. In this article, we are going to move away from the frontend and follow the process of building another backend feature.

One of the most frustrating things about translation management in a Laravel application is forgetting to add translations to the corresponding language file. This has the undesirable result of either the translation key or the default language being rendered on the page rather than the translation for the current language.

To mitigate this issue, the Laravel Translation package provides a way to scan a project for translations that don’t exist in the translation files for all languages supported by the application.

We achieve the scanning part of this process by recursively looking through all files of the configured directories for instances of any of the Laravel translation retrieval methods (e.g. __(), trans()). These methods are captured using regular expressions and the translation strings extracted. These strings can be compared against the existing translations to see whether or not they already exist or need to be created.

Configuration

In the package configuration, there are two keys defined to make the scanner more flexible and efficient.

'translation_methods' => ['trans', '__']

The translation_methods option allows the user to provide an array of translation retrieval methods which the scanner uses to look for keys.

'scan_paths' => [app_path(), resource_path()]

The scan_paths key allows the user to define an array of directories for the scanner to look through when searching for translation keys.

Of course, it’s possible to use base_path() here to search through the whole project, but defining only the directories where you expect to find translations will add significant speed improvements to the scanning process.

Implementation

The scanning functionality is handled by a single class. This class accepts an instance of Laravel’s Illuminate\Filesystem\Filesystem, which it uses to traverse directories and interact with files, along with the array of translation methods and scan paths outlined above.

The class contains a single method findTranslations, which is responsible for carrying out the task. It utilises the following regular expression which is influenced by a combination of those found in Barry vd. Heuvel’s Laravel Translation Manager package and Mohamed Said’s Laravel Language Manager package.

$matchingPattern =
'[^\w]'. // Must not start with any alphanum or _
'(?<!->)'. // Must not start with ->
'('.implode('|', $this->translationMethods).')'. // Must start with one of the functions
"\(". // Match opening parentheses
"[\'\"]". // Match " or '
'('. // Start a new group to match:
'.+'. // Must start with group
')'. // Close group
"[\'\"]". // Closing quote
"[\),]"; // Close parentheses or new parameter

The method recursively iterates over all of the files in the provided directories using the regular expression to find instances of the provided translation retrieval methods, returning an array of all of the matches.

On each match, an additional check is performed to determine whether the match is a group translation (php array style) or single translation (JSON style). This is done by simply checking whether or not the match contains a period. If so, everything before the period is the file and everything after is the key (e.g. you could find the translation for validation.accepted by getting the accepted key from the validation.php file).

In the end, we have an array which looks similar to the following:

[
'single' => [
'Welcome' => 'Welcome'
],
'group' => [
'validation' => [
'accepted' => 'The :attribute must be accepted.',
...
],
],
];

Of course, doing this will give us every translation found in the configured paths, so how do we go about determining those which are missing? It’s simple really, we now have all the tagged translations in the app in an array and we can use our file driver created earlier in the series to get all the translations in the language files in an array format.

Not only that, but the format of the two arrays will be the same, so all we need to do is diff the two. We can conclude that anything in the array of translations gathered from scanning the app which doesn’t appear in the language file translations needs to be added to the relevant language file.

To do this, we simply iterate over the missing translations and utilise the methods already built in the file driver to add them.

$missingTranslations = $this->findMissingTranslations($language);
 
if (isset($missingTranslations['single'])) {
foreach ($missingTranslations['single'] as $key => $value) {
$this->addSingleTranslation($language, $key);
}
}
 
if (isset($missingTranslations['group'])) {
foreach ($missingTranslations['group'] as $group => $keys) {
foreach ($keys as $key => $value) {
$this->addGroupTranslation($language, "{$group}.{$key}");
}
}
}

This article brings us to the end of the file-based functionality of the translation package. Next time, we will utilise the groundwork we have in place to start our database driver. See you next time, and as usual, if you have any questions, please feel free to contact me on Twitter.

Joe Dixon photo

Founder and CTO of ubisend. Proud Father to two tiny heroes, Husband, developer, occasional globetrotter.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Tinkerwell

Version 4 of Tinkerwell is available now. Get the most popular PHP scratchpad with all its new features and simplify your development workflow today.

Visit Tinkerwell
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Apply Dynamic Filters to Eloquent Models with the Filterable Package image

Apply Dynamic Filters to Eloquent Models with the Filterable Package

Read article
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article
Asserting Exceptions in Laravel Tests image

Asserting Exceptions in Laravel Tests

Read article
Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4 image

Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4

Read article
Basset is an alternative way to load CSS & JS assets image

Basset is an alternative way to load CSS & JS assets

Read article
Integrate Laravel with Stripe Connect Using This Package image

Integrate Laravel with Stripe Connect Using This Package

Read article