Localization
FloCMS sites can speak several languages. New projects come with English, Arabic and Kurdish, including right-to-left support for Arabic and Kurdish.
Enabling languages
Languages are set in .env:
LANGUAGES=en,ar,ku
DEFAULT_LANG=en
Each code needs a file in lang/, such as lang/ar.php. Use lowercase codes, because the language is read from the URL in lowercase.
Languages in URLs
The first segment of a URL picks the language when it's one of LANGUAGES:
| URL | Language |
|---|---|
/blog |
The default language |
/ar/blog |
Arabic |
/ku/admin/posts |
Kurdish, in the admin panel |
Everything after the language works as usual. See routing.
These constants describe the active language. They're defined on every page request:
| Constant | Example (Arabic) | Use |
|---|---|---|
ACTIVE_LANG |
ar |
The active language code |
ACTIVE_LANG_PATH |
ar/ |
The URL prefix; empty for the default language |
LANG_SUFFIX |
_ar |
A column suffix such as title_ar; empty for English |
Build links that keep the visitor's language with ACTIVE_LANG_PATH:
<a href="<?= SITE_URI . '/' . ACTIVE_LANG_PATH ?>blog">@lang('nav.blog')</a>
For a language switcher, App::getRouter()->changeLang('ar') returns the path of the current page in another language:
<?php $router = \FloCMS\Core\App::getRouter(); ?>
<a href="<?= SITE_URI . $router->changeLang('en') ?>">English</a>
<a href="<?= SITE_URI . $router->changeLang('ar') ?>">العربية</a>
Translation files
A language file returns an array of keys and texts:
<?php
// lang/en.php
return array(
'lng.dir' => 'ltr',
'nav.blog' => 'Blog',
'auth.welcome' => 'Welcome back, :name!',
);
Keys are looked up in lowercase, so write them in lowercase. If the file of the active language is missing, lang/en.php is used.
Printing translations
In a view, @lang('key') prints a translation:
<h1>@lang('website.title')</h1>
In PHP, __('key') returns it:
$this->data['title'] = __('nav.blog');
A missing key returns the key itself, so you can spot it on the page. Pass a third argument for a different fallback: __('nav.blog', [], 'Blog').
Placeholders
Words starting with : are replaced from an array:
__('auth.welcome', ['name' => $user['fullname']]); // Welcome back, Sara!
:Name gives the value with a capital first letter, and :NAME in capitals.
Warning
@lang() and __() don't escape HTML, so translations may contain markup. When a placeholder holds something a visitor typed, print the result escaped: {{ __('auth.welcome', ['name' => $name]) }}.
Right-to-left languages
A language is right-to-left when its file has 'lng.dir' => 'rtl'. Lang::isRTL() tells you, and the default layout uses it to load an extra stylesheet:
<?php if (\FloCMS\Core\Lang::isRTL()): ?>
<link rel="stylesheet" href="<?= template_asset('css/style.rtl.css') ?>">
<?php endif; ?>
Set the page direction and language on the <html> element of your layout as well:
<html lang="<?= ACTIVE_LANG ?>" dir="@lang('lng.dir')">
Translated content in the database
For content that differs per language, a common pattern is one column per language, such as title, title_ar and title_ku. LANG_SUFFIX picks the right one:
$title = ($post['title' . LANG_SUFFIX] ?? '') ?: $post['title'];
LANG_SUFFIX is empty for English, so the plain column holds the English text.
Adding a language
php flo lang:add fr
php flo lang:add fa --rtl
lang:add creates lang/<code>.php with every key of lang/en.php and empty values, ready to translate. --rtl marks it right-to-left, --base=ar copies the keys of another language, and --force replaces an existing file. Then add the code to LANGUAGES.
Finding missing translations
php flo lang:missing
php flo lang:missing --lang=ar
lang:missing compares each language with lang/en.php and lists missing, empty and extra keys. It also reports codes in LANGUAGES that have no file. It exits with 1 when keys or files are missing, so you can run it in CI. --base= picks another reference language, and --format=json prints the report as JSON.
The API
API requests pick their language from ?lang=, then the Accept-Language header, then the default language. The response's Content-Language header says which one was used, and __() works in API controllers too. See API middleware.