
Creating a bilingual site is always complicated, but not with CodeIgniter 4! Thanks to the localization features and the translation files in several languages, creating a multilingual site is very easy and fast. In this article I explain how to create a website in five languages. You will see that it is just as easy to make a site in two, five or 25 languages with the translation files.
CodeIgniter's language files
First, you have to copy the CodeIgniter's language files. These files contain the framework messages, among others the messages displayed when validating a form. Go to GitHub to get the project codeigniter4/translations. You can copy only the directories of the languages you need into the app/Language/
directory. For this example, we will translate our site into English, French, Spanish, German and Japanese. So we will copy the de
, es
, fr
and ja
directories. The files for English are already included with CodeIgniter in system/Language/en
. They are in the right place. No need to copy or move them in our application.
The language files for our application
To translate an application, you only need to create one or more files for each of the languages supported by the application. In this example, one file will suffice. Create a Blog.php
file in the directory of each of the five languages and add the following sentences. As I don't speak any Spanish, German or Japanese, I used a translation application (DeepL). I apologize in advance if the sentences are not correct. This is only to demonstrate CodeIgniter's multilingual capabilities.
app/Language/de/Blog.php
<?php
/**
*
* Blog - German / Allemand
*
*/
return [
'title' => "Willkommen in meinem Blog",
'metaDescription' => "Beispiel für eine mit CodeIgniter erstellte mehrsprachige Website",
'metaKeywords' => "CodeIgniter, Sprache, Beispiel",
'languageName_de' => "Deutsch",
'languageName_en' => "Englisch (English)",
'languageName_es' => "Spanisch (Español)",
'languageName_fr' => "Französisch (Français)",
'languageName_ja' => "Japanisch (日本語)",
'aboutTitle' => "Über uns",
'aboutText' => "Hallo, hier ist ein Beispiel für einen mehrsprachigen Blog.",
];
app/Language/en/Blog.php
<?php
/**
*
* Blog - English / Anglais
*
*/
return [
'title' => "Welcome to my blog",
'metaDescription' => "Example of a multilingual web site made with CodeIgniter",
'metaKeywords' => "CodeIgniter, language, example",
'languageName_de' => "German (Deutsch)",
'languageName_en' => "English",
'languageName_es' => "Spanish (Español)",
'languageName_fr' => "French (Français)",
'languageName_ja' => "Japanese (日本語)",
'aboutTitle' => "About",
'aboutText' => "Hello, here is an example of a multilingual blog.",
];
app/Language/es/Blog.php
<?php
/**
*
* Blog - Spanish / Espagnol
*
*/
return [
'title' => "Bienvenido a mi blog",
'metaDescription' => "Ejemplo de sitio web multilingüe realizado con CodeIgniter",
'metaKeywords' => "CodeIgniter, lenguaje, ejemplo",
'languageName_de' => "Alemán (Deutsch)",
'languageName_en' => "Inglés (English)",
'languageName_es' => "Español",
'languageName_fr' => "Francés (Français)",
'languageName_ja' => "Japonés (日本語)",
'aboutTitle' => "Sobre nosotros",
'aboutText' => "Hola, este es un ejemplo de blog multilingüe.",
];
app/Language/fr/Blog.php
<?php
/**
*
* Blog - French / Français
*
*/
return [
'title' => "Bienvenue sur mon blog",
'metaDescription' => "Exemple d'un site web multilingue créé avec CodeIgniter",
'metaKeywords' => "CodeIgniter, languege, exemple",
'languageName_de' => "Allemand (Deutsch)",
'languageName_en' => "Anglais (English)",
'languageName_es' => "Espagnol (Español)",
'languageName_fr' => "Français",
'languageName_ja' => "Japonais (日本語)",
'aboutTitle' => "À propos",
'aboutText' => "Bonjour, voici un exemple d'un blog multilingue.",
];
app/Language/ja/Blog.php
<?php
/**
*
* Blog - Japanese / Japonais
*
*/
return [
'title' => "私のブログへようこそ",
'metaDescription' => "CodeIgniterで作られた多言語Webサイトの例",
'metaKeywords' => "CodeIgniter, 言語, 例",
'languageName_de' => "ドイツ語 (Deutsch)",
'languageName_en' => "英語 (English)",
'languageName_es' => "スペイン語 (Español)",
'languageName_fr' => "フランス語 (Français)",
'languageName_ja' => "日本語",
'aboutTitle' => "私たちについて",
'aboutText' => "こんにちは、ここに多言語ブログの例があります。",
];
Language configuration
In the application configuration file, define which languages will be supported with the $supportedLocales
parameter. To have the application automatically detect the visitor's language, set the $negotiateLocale
parameter to true
. In cases where the visitor's language is not supported, we will display them the site in the language defined in $defaultLocale
. In this case, the default language is English.
app/Config/App.php
public $defaultLocale = 'en';
public $negotiateLocale = true;
public $supportedLocales = ['en', 'es', 'fr', 'de', 'ja'];
The routes
The language selection is done via the URL. If /
is accessed and $negotiateLocale
is set to true
, CodeIgniter will attempt to determine the visitor's language. If it is set to false
, the default language is selected. If /es
is accessed, Spanish is selected. And so on for all languages. To take advantage of this feature, we just need to add {locale}
to our routes. Note that the /
route must always be defined. Otherwise, you will get a 404 error if you go to /
without specifying the language.
app/Config/Routes.php
$routes->setAutoRoute(false);
$routes->get('/', 'HomeController::index');
$routes->get('/{locale}', 'HomeController::index');
The controllers
In the BaseController
, I like to keep an array of data that should be available to all views.
protected $viewData = [];
I add the visitor's language and all the languages supported by the application.
$this->viewData['locale'] = $request->getLocale();
$this->viewData['supportedLocales'] = $request->config->supportedLocales;
Here is the complete code of the base controller:
app/Controllers/BaseController.php
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* Class BaseController
*
* BaseController provides a convenient place for loading components
* and performing functions that are needed by all your controllers.
* Extend this class in any new controllers:
* class Home extends BaseController
*
* For security be sure to declare any new methods as protected or private.
*/
class BaseController extends Controller
{
/**
* An array of helpers to be loaded automatically upon
* class instantiation. These helpers will be available
* to all other controllers that extend BaseController.
*
* @var array
*/
protected $helpers = [];
/**
* The data to be passed to the view.
* @var array
*/
protected $viewData = [];
/**
* Constructor.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param LoggerInterface $logger
*/
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
// Do Not Edit This Line
parent::initController($request, $response, $logger);
//--------------------------------------------------------------------
// Preload any models, libraries, etc, here.
//--------------------------------------------------------------------
// E.g.: $this->session = \Config\Services::session();
$this->viewData['locale'] = $request->getLocale();
$this->viewData['supportedLocales'] = $request->config->supportedLocales;
}
}
Then just pass $this->viewData
to the view:
app/Controllers/HomeController.php
<?php
namespace App\Controllers;
class HomeController extends BaseController
{
public function index()
{
return view('index', $this->viewData);
}
}
The views
In our application's template, we specify the language for the page content with the variable $locale
that we defined in the array $viewData
of the base controller:
<html lang="<?= $locale ?>">
This will result in the following:
<html lang="en">
To display a sentence in the correct language, we use the function lang()
:
<title><?= lang('Blog.title') ?></title>
The first word Blog
is the name of the file where this sentence is defined. The next word, title
, is the key for this sentence as defined in the language file:
'title' => "Welcome to my blog",
Here is the view for our template. The content of each page will be inserted in the main_content
section:
<?= $this->renderSection('main_content') ?>
Under the title, we display a menu allowing us to select one of the available languages. To do this, we loop over the values of $supportedLocales
:
app/Views/template.php
<?php
/**
* @var \CodeIgniter\View\View $this
* @var string $locale
*/
?>
<!DOCTYPE html>
<html lang="<?= $locale ?>">
<head>
<title><?= lang('Blog.title') ?></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="<?= lang('Blog.metaDescription') ?>">
<meta name="keywords" content="<?= lang('Blog.metaKeywords') ?>">
<meta name="author" content="includebeer.com">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous">
</head>
<body>
<header class="text-center mt-4">
<h1><?= lang('Blog.title') ?></h1>
<?php foreach ($supportedLocales as $supLocale): ?>
<?= anchor($supLocale, lang("Blog.languageName_{$supLocale}"), ['class' => 'mx-3'])?>
<?php endforeach; ?>
</header>
<div class="container">
<?= $this->renderSection('main_content') ?>
</div>
<footer class="my-5">
<p class="text-center">© 2021 <?= anchor($locale, lang('Blog.title'))?></p>
</footer>
</body>
</html>
Here is the content of the home page which simply displays a small description of the website:
app/Views/index.php
<?php
/**
* @var \CodeIgniter\View\View $this
*/
?>
<?= $this->extend('template') ?>
<?= $this->section('main_content') ?>
<section class="px-3 py-5">
<div class="container">
<div class="mb-5">
<h3><?= lang('Blog.aboutTitle') ?></h3>
<p><?= lang('Blog.aboutText') ?></p>
</div>
</div>
</section>
<?= $this->endSection() ?>
The Localize filter
At this point, the site is already multilingual and everything works perfectly... or almost!
If for example our computer is in spanish and we go to /
it displays the site in our language (the same content as if we go to /es
). If we go to /ru
it displays the site in the default language (the same content as /en
), because Russian is not available in our application. Which is a bit strange. In my opinion, if we go to /
we should be redirected to our language. And if we request the site in a language that is not supported, we should either get an error or be redirected to the default language. In this example, I decided to display a 404 error when requesting a language that is not supported.
I have created a filter called Localize
to allow us to handle this little problem.
app/Filters/Localize.php
<?php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class Localize implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
log_message('debug', "FilterLocalize --- start ---");
$uri = &$request->uri;
$segments = array_filter($uri->getSegments());
$nbSegments = count($segments);
log_message('debug', "FilterLocalize - {$nbSegments} segments = " . print_r($segments, true));
// Keep only the first 2 letters (en-UK => en)
$userLocale = strtolower(substr($request->getLocale(), 0, 2));
log_message('debug', "FilterLocalize - Visitor's locale $userLocale");
// If the user's language is not a supported language, take the default language
$locale = in_array($userLocale, $request->config->supportedLocales) ? $userLocale : $request->config->defaultLocale;
log_message('debug', "FilterLocalize - Selected locale $locale");
// If we request /, redirect to /{locale}
if ($nbSegments == 0)
{
log_message('debug', "FilterLocalize - Redirect / to /{$locale}");
log_message('debug', "FilterLocalize --- end ---");
return redirect()->to("/{$locale}");
}
log_message('debug', "FilterLocalize - segments[0] = " . $segments[0]);
$locale = $segments[0];
// If the first segment of the URI is not a valid locale, trigger a 404 error
if ( ! in_array($locale, $request->config->supportedLocales))
{
log_message('debug', "FilterLocalize - ERROR Invalid locale '{$locale}'");
log_message('debug', "FilterLocalize --- end ---");
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
log_message('debug', "FilterLocalize - Valid locale '$locale'");
log_message('debug', "FilterLocalize --- end ---");
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// Do something here
}
}
To make the new filter run, just add it to the filter configuration file. Define an alias with the class name including its namespace. Then add this alias to the before
section so that the filter is executed before calling the controller.
app/Config/Filters.php
public $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
// Define the localize alias
'localize' => \App\Filters\Localize::class,
];
public $globals = [
'before' => [
// Run the localize filter before the controller
'localize',
],
'after' => [
'toolbar',
],
];
First part completed!
You now have a website in five languages! You can access http://ci4.test:8888/fr, http://ci4.test:8888/en, http://ci4.test:8888/de, etc.
Okay, not so fast. We're not going to write blog posts in translation files! We should save them in a database and display only the articles in the selected language. That's what we're going to do right now in part 2.