Managing languages with Laravel Nova

The real title of this post should be Managing many languages with Laravel Nova. You’ll find out why, very soon.

Using Spatie’s Laravel Translatable and Nova Translatable packages managing multiple languages with Laravel Nova is easy. Follow their instructions and it probably takes you less than 20 minutes to make your model translatable. I will not repeat those steps here.

Many languages

What’s not so easy is using the Nova component to manage a larger number of languages. When you edit your model in Nova it shows each translatable field in every language that you configured. This gets messy (and slow) real quick.

Unfortunately there’s no good way to deal with so many fields in the current version of Nova. There are 3rd-party components that implement tabs etc., but nothing that could be considered the de-facto standard and long-term way of implementing this.

If Nova gains the ability to better structure a long form natively, we’d probably start leveraging that in a new major version of the package.

Spatie’s Github page for Nova Translatable

Spatie is probably right to wait for Nova to provide a native solution. Until then, we have the solution provided below. In fact, I believe the solution below will be preferred even when Nova solves this issue.

Ideally we see only two languages: the language we’re working on and the primary language of the website. (Assuming all content is initially created in a single primary language.) Turns out this is fairly easy to do in Laravel.

Locale switcher

Before we continue: I recommend you to install a browser add-on that allows you to switch locale and language quickly. I currently use Locale Switcher in a Chrome based browser.

The Locale Switcher helps us to see what our translators see in Nova as well as what our users see on the front-end. (If you’re translating your site, I bet you’ve installed this already.)

Laravel

While Spatie’s packages do not require this, I prefer to be explicit and define which languages the site supports. In our config/app.php we find the locale definition and add one for locales:

   /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    |
    | The application locale determines the default locale that will be used
    | by the translation service provider. You are free to set this value
    | to any of the locales which will be supported by the application.
    |
    */

    'locale' => 'en',
    'locales' => [ 'en', 'zh', 'nl', 'ja' ],

In my case I’m allowing English, Chinese, Dutch and Japanese.

Next is our Nova resource. In my Post resource at app/Nova/Post.php I have two fields that I want to make translatable: Title and Content.

Text::make('Title'),
Markdown::make('Content'),

Which we make translatable by wrapping them:

Translatable::make([
    Text::make('Title'),
    Markdown::make('Content'),
])

So far so good. This is what you probably had already. Unfortunately this results in every Nova user seeing the Title and Content field in all of the languages we are using. Four in my example, potentially dozens in your case.

We need to tell the Translatable field which languages we want to see exactly. In my case that’s English, the primary language of the website, as well as the native (or Locale Switcher) language of the user.

To be more precise: I want the primary language, as well as the native language if the native language is one of the languages we support. We accomplish this by putting the two languages in an array which we then feed to the locales method on Translatable.

$locales = [ 'en' ];
if(app()->getLocale() != 'en' && in_array(app()->getLocale(), Config::get('app.locales')))
    $locales = [app()->getLocale(), 'en'];

If you have only one translatable model you can put this in its Nova resource. Otherwise, you may want to find a more suitable location.

Now we add the locales method to Translatable and end up with something like this:

/**
 * Get the fields displayed by the resource.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return array
 */
public function fields(Request $request)
{
    $locales = [ 'en' ];
    if(app()->getLocale() != 'en' && in_array(app()->getLocale(), Config::get('app.locales')))
        $locales = [app()->getLocale(), 'en'];

    return [
        ID::make()->sortable(),

        Translatable::make([
            Text::make('Title'),
            Markdown::make('Content')
        ])->locales($locales),
    ];
}

That’s it! Our user will now only see English + their native language. (If their native language is not English.)

You may be wondering: if the user submits only 2 languages when making changes, what happens to all the other languages in the database? Good news: nothing happens to them. They’re completely safe.

Addendum

I hardcoded the primary language to English in my example. You can replace the three occurrences of ‘en’ with Config::get(‘app.locale’) to properly honour the configuration made in app.php.