Internationalization is always tricky as it involves several layers, from routing to controller logic and view outputs to URL building and redirecting.
Set up a default language in your configs:
'Config' => [
'language' => 'de',
],
Any Configure::read('Config.language')
or I18n::getLocale()
call should return that default language.
To detect and switch based on language, you can leverage the Language class.
Either you manually use findMatches()
to sort through, or you use the convenience method findFirstMatch()
:
use Tools\Utility\Language;
$language = Language::findFirstMatch(['de', 'en']);
It will use the language with the highest weight supported by the user agent data provided.
Note: When you have intl installed, you can also try to use the CakePHP core LocaleSelectorMiddleware.
In your AppController you can now do:
/**
* @return void
*/
public function initialize() {
parent::initialize();
// First check the session
$language = $this->request->getSession()->read('Config.language');
// Then check the browser preference for the whitelisted languages
if (!$language) {
$language = Language::findFirstMatch(Configure::read('Config.supportedLanguages'));
}
// Overwrite the system default
if ($language) {
Configure::write('Config.language', substr($language, 0, 2));
I18n::setLocale($language);
}
You then just need a switch on the website that allows the other to change the language (by writing it into the session):
<?php if ($this->Configure->read('Config.language') === 'de') {
echo $this->Html->image('flag_de.png', ['title' => __('German')]);
} else {
echo $this->Form->postLink($this->Html->image('flag_de.png'), ['prefix' => false, 'plugin' => 'Tools', 'controller' => 'ShuntRequest', 'action' => 'language', 'de'], ['block' => true, 'escape' => false, 'title' => __('German')]);
} ?>
<?php if ($this->Configure->read('Config.language') === 'en') {
echo $this->Html->image('flag_en.png', ['title' => __('English')]);
} else {
echo $this->Form->postLink($this->Html->image('flag_en.png'), ['prefix' => false, 'plugin' => 'Tools', 'controller' => 'ShuntRequest', 'action' => 'language', 'en'], ['block' => true, 'escape' => false, 'title' => __('English')]);
}?>
Make sure you included the routes for the Tools plugin for this to work or set them up manually in the src/Config/routes.php
file.
This example also uses Shim.Configure helper to avoid use statements.
Instead of the "easy" session-based way you might want to use URLs like domain.tld/de/controller/action
instead for the other languages of the website.
This can however affect also your view layer as you will have to overwrite the URL generation there to always include the
new language
param.
Switch out the above session check then with
$language = $this->request->getParam('language');
(before CakePHP 3.4+ it would be param()
)
And make sure your routes are all adjusted to accept and parse the language param:
Router::scope('/', function (RouteBuilder $routes) {
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
$routes->connect(
'/:language/:controller/:action/*',
[],
['language' => 'de']
);
...
}
In this case only /de/...
is a language param, since /...
(without any) would continue to use the default language (English).
You can of course also make the regexp here 'language' => 'de|en'
for all languages to be a param in the URL.
Only the root /
would then be allowed to fallback to the default one.
In this case you should have a canonical (pointed to the root) on that action to avoid it being treated as duplicate content.
Now you just have to overwrite Controller::redirect()
for the controller redirects and HtmlHelper::link()
/UrlHelper::build()
etc for the view level URLs generated.
For a more sophisticated approach on that routing topic you might want to look into the I18n plugin.
A locale is a combination of language and region (usually a country).
Most would not use locales here for the switch, but languages. The locales are merely used internally for the presentation details.
Examples for Germany/German:
de_DE
/de_AT
are some of the possible locale codesde
is the language (iso code) and internally uses one of those two locales
Examples for USE/English:
en_US
/en_GB
are some of the possible locale codesen
is the language (iso code) and internally uses one of those two locales
See here for a list of possible languages and their codes.