Automating Laravel Translation Extraction with a Custom Artisan Command

nasrulhazim

Nasrul Hazim Bin Mohamad

Posted on October 15, 2024

Automating Laravel Translation Extraction with a Custom Artisan Command

Translations play a crucial role in making your Laravel application accessible to users in multiple languages. However, manually identifying all instances of the __() helper across your codebase can be tedious, especially for large projects. What if you could automatically extract all translatable strings and output them to a JSON file ready for localization?

In this blog post, I will walk you through creating a custom Artisan command that scans your Laravel codebase, extracts all text used within the __() helper, and outputs the translations to a locale-specific JSON file. This solution will save time and make localization more efficient, especially when dealing with a growing number of strings to translate.

Why Automate Translation Extraction?

As your Laravel application grows, so does the number of translatable strings. These strings are typically defined using the __() helper, which allows developers to localize their application by referring to language lines. However, manually tracking down all instances of __() across controllers, Blade views, and other parts of your code can be overwhelming.

With this custom command, you can:

  • Automatically find all instances of the __() helper.
  • Output these strings into a JSON file ready for translation.
  • Specify the locale and output directory for the extracted strings.

Step-by-Step Guide: Building the lang:extract Artisan Command

Step 1: Create the Artisan Command

Let’s start by generating the Artisan command using Laravel’s built-in make:command feature:

php artisan make:command ExtractLangText
Enter fullscreen mode Exit fullscreen mode

This creates a new command file located in app/Console/Commands/ExtractLangText.php.

Step 2: Write the Command Logic

Now, let’s implement the command logic. We want the command to:

  • Accept a locale (e.g., ms for Malay) as an argument.
  • Optionally allow users to provide a custom output path for the JSON file (defaulting to base_path('/lang')).
  • Scan through the application directories (e.g., app, routes, and resources/views) to find all occurrences of the __() helper.
  • Write the extracted strings into a locale-specific JSON file.

Here’s the full command code:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

class ExtractLangText extends Command
{
    // Define the command signature to accept a locale argument and an optional path
    protected $signature = 'lang:extract {locale} {path?}';

    protected $description = 'Extract all text within the __() helper and output to the /lang/{locale}.json file or a custom path';

    // Execute the command
    public function handle()
    {
        // Get the locale and optional output path from the command arguments
        $locale = $this->argument('locale');
        $path = $this->argument('path') ?: base_path('lang'); // Default to base_path('/lang') if no path is provided

        // Ensure the directory exists
        if (! is_dir($path)) {
            mkdir($path, 0755, true);
        }

        $outputFile = $path."/$locale.json";

        if (file_exists($outputFile) && ! $this->confirm("$outputFile already exists. Are you sure want to overwrite it?")) {
            return;
        } else {
            unlink($outputFile);
            $this->components->info("$outputFile removed.");
        }

        // Find all files in app, routes, and resources/views directories
        $directories = [
            base_path('app'),
            base_path('routes'),
            base_path('resources/views'),
        ];

        $translations = [];

        foreach ($directories as $directory) {
            $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
            foreach ($files as $file) {
                if ($file->isFile() && in_array($file->getExtension(), ['php', 'blade.php'])) {
                    $content = file_get_contents($file->getPathname());

                    // Regex to find all instances of __()
                    preg_match_all("/__\(\s*[\'\"](.*?)[\'\"]\s*\)/", $content, $matches);

                    // Store the results
                    foreach ($matches[1] as $key) {
                        if (! isset($translations[$key])) {
                            $translations[$key] = $key;  // Initial extraction without translation
                        }
                    }
                }
            }
        }

        ksort($translations);

        // Write translations to a JSON file in the target directory
        file_put_contents($outputFile, json_encode($translations, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));

        $this->components->info("Translations extracted successfully and saved to $outputFile");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: How the Command Works

  • Locale Argument: The locale argument is mandatory and will determine the name of the output file (e.g., ms.json for Malay translations).
  • Path Argument: The path argument is optional and allows you to specify where the output JSON file should be saved. If no path is provided, it defaults to base_path('lang').
  • Directory Scanning: The command scans through the app, routes, and resources/views directories, searching for all instances of the __() helper.
  • Regex Matching: A regular expression is used to extract the text within the __() helper.
  • JSON Output: The extracted text is saved in a JSON file, where the keys are the translatable strings and the values are initially set to the same strings. This file can then be manually updated or passed to translators for localization.

Step 4: Running the Command

To use the command, you can run it like this:

  1. Using Default Path (base_path('lang')):
   php artisan lang:extract ms
Enter fullscreen mode Exit fullscreen mode

This will create a file at base_path('lang/ms.json') containing all the extracted translatable strings.

  1. Using Custom Path:

If you need to specify a different path (for example, resources/lang for older Laravel versions), you can do so:

   php artisan lang:extract ms resources/lang
Enter fullscreen mode Exit fullscreen mode

This will create the file at resources/lang/ms.json.

Example JSON Output

The output file will look something like this:

{
    "Active": "Active",
    "Inactive": "Inactive",
    "You have successfully updated the record.": "You have successfully updated the record.",
    "Welcome": "Welcome",
    "Dashboard": "Dashboard"
}
Enter fullscreen mode Exit fullscreen mode

You can then translate the values as needed.

Why This Command is Useful

  • Efficiency: Instead of manually combing through your files, this command automates the extraction of all translatable strings.
  • JSON Format: Since Laravel supports JSON-based translation files, this command outputs the strings directly in JSON format, making it easy to integrate with Laravel’s localization system.
  • Customizable Output: By allowing users to specify the output path, this command works for both newer and older Laravel projects that might have different default language directories.

Conclusion

With this custom Artisan command, you can now easily automate the extraction of translatable strings from your Laravel application. Whether you're preparing your app for a single language or multiple locales, this tool simplifies the process, saving you valuable time.

Feel free to extend this solution, customize it to fit your workflow, or even share it with your team to enhance your localization efforts!


I hope this tutorial helps you streamline the localization process in your Laravel projects. If you have any questions or suggestions, feel free to drop a comment below!

Happy coding! 🎉

💖 💪 🙅 🚩
nasrulhazim
Nasrul Hazim Bin Mohamad

Posted on October 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related