Skip to content

Latest commit

 

History

History
120 lines (93 loc) · 4.88 KB

I18n.md

File metadata and controls

120 lines (93 loc) · 4.88 KB

I18n

Internationalization is always tricky as it involves several layers, from routing to controller logic and view outputs to URL building and redirecting.

Basics

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.

Detecting the user 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.

Session based language switch

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.

Using language in URLs

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.

Language vs Locale

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 codes
  • de 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 codes
  • en is the language (iso code) and internally uses one of those two locales

See here for a list of possible languages and their codes.