, (*1)
Intro
This plugins provides:, (*2)
- A route class for generating and matching urls with language prefix.
- A middleware which sets locale using
I18n::setLocale()
based on language prefix in URL and also provides redirection to appropriate
URL with language prefix when accessing site root.
- A class for retrieving translation messages stored in the database instead of using po/mo files.
- A validation class for auto translating validation message.
- A widget to generate select box with list of timezone identifiers.
Installation
composer require admad/cakephp-i18n
Usage
Load the plugin by running command:, (*3)
bin/cake plugin load ADmad/I18n
The plugin contains multiple classes useful for internationalization. You can pick
and chose the ones you require., (*4)
I18nRoute
The I18nRoute
class helps generating language prefixed routes of style
/{lang}/{controller}/{action}
., (*5)
For e.g. you can add routes to your routes.php
similar to the ones shown below:, (*6)
$routes->scope('/', function ($routes) {
$routes->connect(
'/{controller}',
['action' => 'index'],
['routeClass' => 'ADmad/I18n.I18nRoute']
);
$routes->connect(
'/{controller}/{action}/*',
[],
['routeClass' => 'ADmad/I18n.I18nRoute']
);
});
Fragment /{lang}
will be auto prefixed to the routes which allows matching
URLs like /en/posts
, /en/posts/add
etc. The lang
element is persisted so
that when generating URLs if you don't provide the lang
key in URL array it
will be automatically added based on current URL., (*7)
When connecting the routes you can use lang
key in options to provide regular
expression to match only languages which your app supports. Or your can set
config value I18n.languages
, which the route class will use to auto generate
regex for lang
element matching:, (*8)
// In your config/app.php
...
'I18n' => [
'languages' => ['en', 'fr', 'de']
]
...
Note: I18nRoute
extends core's DashedRoute
so the URL fragments will be
inflected accordingly., (*9)
I18nMiddleware
While not necessary, one would generally use the I18nMiddleware
too when using
language prefixed routes with the help of I18nRoute
., (*10)
You can setup the I18nMiddleware
in your src/Application::middleware()
as
shown:, (*11)
$middlware->add(new \ADmad\I18n\Middleware\I18nMiddleware([
// If `true` will attempt to get matching languges in "languages" list based
// on browser locale and redirect to that when going to site root.
'detectLanguage' => true,
// Default language for app. If language detection is disabled or no
// matching language is found redirect to this language
'defaultLanguage' => 'en',
// Languages available in app. The keys should match the language prefix used
// in URLs. Based on the language the locale will be also set.
'languages' => [
'en' => ['locale' => 'en_US'],
'fr' => ['locale' => 'fr_FR'],
],
]));
The keys of languages
array are the language prefixes you use in your URL., (*12)
To ensure that the lang
router param is available, you must add this middleware
after adding CakePHP's default routing middleware (i.e. after ->add(new RoutingMiddleware($this))
)., (*13)
The middleware does basically two things:, (*14)
-
When accessing site root /
it redirects the user to a language prefixed URL,
for e.g. /en
. The langauge it redirects to depends on the configuration keys
detectLanguage
and defaultLanguage
shown above., (*15)
Now in order to prevent CakePHP from complaining about missing route for /
,
you must connect a route for /
to a controller action. That controller action
will never be actually called as the middleware will intercept and redirect
the request., (*16)
For e.g. $routes->connect('/', ['controller' => 'Foo']);
, (*17)
-
When accesing any URL with language prefix it sets the app's locale based
on the prefix. For that it checks the value of lang
route element in current
request's params. This route element would be available if the matched route
has been connected using the I18nRoute
., (*18)
Using the array provided for the languages
key it sets the App.language
config to the language prefix through Configure::write()
and the value of locale
is used for the I18n::setLocale()
call., (*19)
DbMessagesLoader
By default CakePHP uses .po
files to store the static string translations. If
for whatever reason you can't/don't want to use .po
files, you can use the
DbMessagesLoader
to store the translation messages in the database instead.
Personally I belive having the messages in a table instead of .po
files makes
it much easier to make a web interface for managing translations., (*20)
To use this class first create the i18n_messages
database table using the sql
file provided in the plugin's config
folder., (*21)
Add code similar to what's shown below in your app's config/bootstrap.php
:, (*22)
// NOTE: This is should be done below Cache config setup.
// Configure `I18n` to use `DbMessagesLoader` for the `default` domain. You need to do
// this for each domain separately.
\Cake\I18n\I18n::config('default', function ($domain, $locale) {
return new \ADmad\I18n\I18n\DbMessagesLoader(
$domain,
$locale
);
});
Now you can use the translation functions like __()
etc. as you normally would.
The I18n
class will fetch the required translations from the i18n_messages
table instead of .po
files., (*23)
Use the admad/i18n extract
command to extract the translation messages from your
code files and populate the translations table. Updating the database records with
translations for each language is upto you., (*24)
bin/cake admad/i18n extract
The extract command needs the list of languages/locales to populate the i18n_messages
table. This can be done by setting the I18n.languages
config or by specifying
the languages list using the languages
option., (*25)
// In your config/app.php
...
'I18n' => [
'languages' => ['en', 'fr', 'de']
]
...
bin/cake admad/i18n extract --languages en,fr,de
You can run the command multiple times as needed. It will add new messages it
finds to the tables, keeping the ones already present untouched., (*26)
In your AppView::initialize()
configure the FormHelper
to use TimezoneWidget
., (*27)
// src/View/AppView.php
public function initialize(): void
{
$this->loadHelper('Form', [
'widgets' => [
'timezone' => ['ADmad/I18n.Timezone'],
],
]);
}
You can generate a select box with timezone identifiers like:, (*28)
// Generates select box with list of all timezone identifiers grouped by regions.
$this->Form->control('fieldname', ['type' => 'timezone']);
// Generates select box with list of timezone identifiers for specified regions.
$this->Form->control('fieldname', [
'type' => 'timezone',
'options' => [
'Asia' => DateTimeZone::ASIA,
'Europe' => DateTimeZone::EUROPE,
],
]);
As shown in example above note that unlike normal select box, options
is now
an associative array of valid timezone regions where the key will be used as
optgroup
in the select box., (*29)