Skip to content

Languages ​

The Languages page ​

The core adds a Languages page at /admin/languages, for super-admins and admins. It holds three choices:

ChoiceMeaning
Site languagesThe languages the site is published in
Default languageThe one served at the root of the site
Admin panel languageThe language of the panel. It does not have to be a site language

locales and admin_locale in config/mela-cms.php are the starting values. Once the page has been saved, the saved values are used and the config entries are ignored.

Reading the languages ​

Read the languages in site code through Melasistema\MelaCms\Languages, never from the config:

php
use Melasistema\MelaCms\Languages;

$languages = app(Languages::class);

$languages->all();          // ['en', 'it'], the default first
$languages->default();      // 'en'
$languages->translations(); // ['it']
$languages->has('it');      // true
Languages::name('it');      // 'Italiano'

Language URLs ​

The default language is at the root of the site. Every other language has a prefix:

URLLanguage, with en as the default
example.com/aboutEnglish
example.com/it/aboutItalian
example.com/en/aboutRedirects to example.com/about

Write each route once, without a language in it:

php
Route::get('/about', AboutController::class)->name('about');

On /it/about the same route runs with the app locale set to it, and route(), url(), redirects and pagination links keep the /it prefix. asset() and Vite URLs stay at the root.

When an admin changes the default language, the new one moves to the root and the old one gets a prefix at once. There are no routes to rebuild and no cache to clear.

Subdomains

To put the languages on subdomains (it.example.com), set 'locale_urls' => 'subdomain' in config/mela-cms.php and point each subdomain to the app. Set SESSION_DOMAIN=.example.com if a login has to be shared between them.

Language switcher and hreflang ​

blade
<head>
    @hreflang
</head>

@foreach (app(\Melasistema\MelaCms\Languages::class)->alternates() as $locale => $url)
    <a href="{{ $url }}">{{ \Melasistema\MelaCms\Languages::name($locale) }}</a>
@endforeach
php
$languages->current();              // 'it' on /it/about
$languages->alternates();           // ['en' => 'https://example.com/about', 'it' => 'https://example.com/it/about']
$languages->url('it', '/contact');  // 'https://example.com/it/contact'

For a record with a different address in each language, the code that serves it calls Languages::localize() with the path in each language, and alternates() and @hreflang follow. Pages do this by themselves; see Your own content for an entity.

Translatable content ​

Translations are stored in the model's own table, in JSON columns, through spatie/laravel-translatable, which is installed with the core. There are no translation tables, and no data moves when the default language changes.

php
// Migration
$table->json('title');
$table->json('body')->nullable();

// Model
use Spatie\Translatable\HasTranslations;

class Article extends Model
{
    use HasTranslations;

    public array $translatable = ['title', 'body'];
}

$article->title is the title in the language of the request. A missing translation falls back to the default language of the site.

In a Filament form, wrap the translatable fields in Translations::make() and write each field once:

php
use Melasistema\MelaCms\Filament\Forms\Translations;

Translations::make([
    TextInput::make('title')->required(),
    RichEditor::make('body'),
]),
BehaviourDetail
TabsOne per site language, the default first. A site in one language gets the fields without tabs
required()Applies to the default language only. Every other rule applies to each language
Removed languageIts translations stay in the database and come back if the language is added again

Changing the default language ​

The default language can be changed at any time, however much content the site already has. Pick another one on the Languages page and save:

  • Nothing in the database moves: every translation stays in its record's row.
  • The new default language is served at the root, and the old one moves under its prefix.
  • route(), url(), alternates() and @hreflang follow at once.
  • A page or entity with no address in the new default language keeps the address it had. An address falls back to the default language, then to the next site language that has one.

A record that was never written in the new default language still needs that translation. Until then its title and text in that language are empty.

The admin panel language ​

The panel is shown in the admin panel language, also under a prefix (/it/admin).

Changing the page ​

Extend Melasistema\MelaCms\Filament\Pages\ManageLanguages (its fields are in formComponents()) and name your class as pages.languages in the config. Set it to null to remove the page.

Known limits ​

  • The labels the core adds to the panel are English only. Filament's own texts follow the admin language.
  • A Livewire component on a translated page sends its updates to the root of the site. Links it renders again during an update lose the prefix, unless they are built with $languages->url().

Code released under the MIT License. The MelaCMS name and logo are trademarks, see Brand.