Laravel Localization Nested JSON Language File

Viewed 2372

I'm trying to make my project multi-language.

I want to work with JSON files since my project has lots of pages,so a lot of strings to translate.

In order to avoid confusion I want to use nested JSON objects instead of just keys.

For example my en.json file looking like this:

{
    "login": {
        "login": "Login",
        "email": "Email",
        "emailPlaceholder": "Registered email address",
        "password": "Password",
        "passwordPlaceHolder": "Your account password"
    },

   "dashboard": {}
}

So I want to have a key for each page.

But when I use it on the view files, it reads just as a regular string:

<label for="email">
    {{ __('login.email') }}
</label>

enter image description here

Any help would be nice, thank you very much.

5 Answers

Try:

<label for="email">
    {{ __('login')['email'] }}
</label>

You can get this approach by changing the default __ helper by:

use Illuminate\Support\Arr; 

function __($key = null, $replace = [], $locale = null)
{
    // Default behavior
    if (is_null($key)) return $key;        
    if (trans()->has($key)) return trans($key, $replace, $locale);

    // Search in .json file
    $search = Arr::get(trans()->get('*'), $key);
    if ($search !== null) return $search;

    // Return .json fallback
    $fallback = Arr::get(trans()->get('*', [], config('app.fallback_locale')), $key);
    if ($fallback !== null) return $fallback;

    // Return key name if not found
    else return $key;
}

Creating custom bootstrap helpers

if you dont know how to change the default helper, create a file with any name (ex: bootstrap/helpers.php), then in the file public/index.php add this line just before 'Register The Auto Loader'

/*
|--------------------------------------------------------------------------
| Register Custom Helpers
|------------------------------------------------------------------------
*/

require __DIR__.'/../bootstrap/helpers.php';

(Optional) Variables feature

if you also want to use the Variables feature, just like __(welcome.user, ['user'=>'david']) you must create a new helper on that file:

use Illuminate\Support\Str;

function trans_replacements($line, array $replace)
{
    if (empty($replace)) return $line;

    $shouldReplace = [];

    foreach ($replace as $key => $value) {
        $shouldReplace[':'.Str::ucfirst($key)] = Str::ucfirst($value);
        $shouldReplace[':'.Str::upper($key)] = Str::upper($value);
        $shouldReplace[':'.$key] = $value;
    }

    return strtr($line, $shouldReplace);
}

and then replace return $search with trans_replacements($search, $replace) and return $fallback with trans_replacements($fallback, $replace)

(Optional) Countable feature

for Countable feature (ex: 'an apple|many apples'), is the same process, just add this helper:

use Illuminate\Support\Facades\App;

function trans_choice($key, $number, array $replace = [], $locale = null)
{        
    // Get message
    $message = __($key, $replace, $locale);

    // If the given "number" is actually an array or countable we will simply count the
    // number of elements in an instance.
    if (is_array($number) || $number instanceof Countable) 
    $number = count($number);

    $replace['count'] = $number;

    return trans_replacements(
        trans()->getSelector()->choose($message, $number, $locale = App::getLocale()), 
        $replace
    );
}

Bootstrap Helpers File

here is the file, just in case: bendeckdavid/laravel_locale_nested_json

Laravel not supporting this by default is annoying. However, you can use this function I made:

function ___($path) {
        $properties = explode(".", $path);
        $base = __($properties[0]);
        unset($properties[0]);
        foreach($properties as $property) {
            // Allows specification of indexes
            if(is_numeric($property)) {
                $property = intval($property);
            }
            // If the key has not been found, return the initial parameter like __()
            try {
                $base = $base[$property];
            } catch (\Throwable $th) {
                return $path;
            }
        }
        return $base;
}

If you are someone like me stumbling upon here wanting to use a file structure like below without the need to specify keys for each string when using php language files.

-app
-lang
  --fr
    ---Headers.json

A little modification to David G's answer above,

<?php

use Illuminate\Support\Arr; 

function ___($path = null, $replace = [], $locale = null)
{
    // Default behavior
    if (is_null($path)) return $path;
    if(!strrpos($path, '.')) {
        return  $path;
    }

    $properties = explode(".", $path);
    $currentLocale = app()->getLocale();    

    // If the file/folder doesn't exist 
    // return the last part of the dot notation
    $translated_string = substr($path, strrpos($path, '.') + 1);

    if(is_dir(lang_path($currentLocale))) {
        if (is_file(lang_path($currentLocale.'\\'.$properties[0].'.json'))) {
            $contents = json_decode(file_get_contents(lang_path($currentLocale.'\\'.$properties[0].'.json')), true);
            // If no value exists for the key, the key is itself returned
            $translated_string = Arr::get($contents, $properties[1], $properties[1]);
        }
    }

    return $translated_string;
}

As of now this doesn't work for files under subdirectories under the locale folder. I am sure, the above code can be optimized, but this should give you an idea.

Also, this function uses triple-underscore {{___()}} instead of the regular double-underscore {{__()}} to avoid conflicts.

Works for,

{{___('Messages.Welcome') }} // lang/(locale)/Messages.json
{{___('Welcome')}} // lang/(locale).json

Assuming your json is stored in $json, you want to parse it into array by using json_decode

$data = json_decode($json, TRUE);

You can access it by:

<label for="email">
    {{ $data['login']['emailPlaceholder'] }}
</label>
Related