コンテンツにスキップ
PHP Ecosystem
Explore

Configuration guide

このコンテンツはまだ日本語訳がありません。

yii2\extensions\localeurls\UrlLanguageManager extends yii\web\UrlManager and replaces the urlManager component. This guide shows how to combine its configuration properties.

<?php
declare(strict_types=1);
use yii2\extensions\localeurls\UrlLanguageManager;
return [
'components' => [
'urlManager' => [
'class' => UrlLanguageManager::class,
'enablePrettyUrl' => true,
'languages' => ['en', 'de'],
],
],
];
<?php
declare(strict_types=1);
use yii2\extensions\localeurls\UrlLanguageManager;
return [
'components' => [
'urlManager' => [
'class' => UrlLanguageManager::class,
'enableLanguageDetection' => true,
'enableLanguagePersistence' => true,
'enablePrettyUrl' => true,
'languages' => ['en-US', 'en', 'es', 'fr', 'de'],
'rules' => [
'' => 'site/index',
'<controller:\w+>/<action:\w+>' => '<controller>/<action>',
],
'showScriptName' => false,
],
],
];

Define available languages for your application.

'languages' => [
// Specific locale
'en-US',
// Language only
'en',
// German
'de',
// Portuguese
'pt',
],

Map custom URL segments to language codes.

'languages' => [
'en-US',
'en',
// /deutsch/page → German (de)
'deutsch' => 'de',
// /french/page → French (fr)
'french' => 'fr',
// /at/page → Austrian German (de-AT)
'at' => 'de-AT',
],

Aliases match in any case; see Language code case for the redirect to the lowercase alias.

Support language variants with wildcards.

'languages' => [
'en-US',
'en',
// Matches: de-AT, de-CH, de-DE, etc.
'de-*',
// Matches: es-MX, es-AR, es-ES, etc.
'es-*',
// Custom wildcard pattern
'wc-*',
],

The language part of a wildcard matches in any case and keeps its configured spelling: /ES-MX/page resolves to es-MX. The region must have two or three letters. A literal * never matches: a persisted or detected value es-* resolves to es.

The default language has no code in the URL unless enableDefaultLanguageUrlCode is true. Without defaultLanguage, the manager uses the application language at the time the manager is created.

// Default: the application language when the manager is created
'defaultLanguage' => null,
// Explicit default language, applied at the start of every request
'defaultLanguage' => 'en',

When defaultLanguage is set, the manager assigns it to Yii::$app->language once per request object, at the start of parseRequest() and before the enableLocaleUrls, languages, and ignore pattern checks. The URL code, the persisted language, or the detected language then replaces it as usual. A language set earlier in the same request, for example by a beforeRequest handler, is overwritten. Use one of the configured languages, and see Worker mode for long-running workers.

Control whether the default language appears in the URL.

// Default behavior: omit default code
'enableDefaultLanguageUrlCode' => false,
// Alternative: include default code
'enableDefaultLanguageUrlCode' => true,

Control case handling for language codes in URL.

// Default: convert to lowercase (recommended)
'keepUppercaseLanguageCode' => false, // /de-at/page
// Alternative: keep the uppercase region
'keepUppercaseLanguageCode' => true, // /de-AT/page

With the default, /de-AT/page redirects to /de-at/page, and /ES-MX/page with es-* redirects to /es-mx/page. A language configured with a lowercase region, such as de-at, applies on /de-at/page without a redirect.

A spelling that differs from its lowercase form redirects to the lowercase form of the alias or code used in the URL: with ['de', 'deutsch' => 'de'], /Deutsch/page redirects to /deutsch/page and /DE/page redirects to /de/page.

Enable/disable automatic language detection.

// Default: detect from the `Accept-Language` header
'enableLanguageDetection' => true,
// Disable header detection (GeoIP detection still applies)
'enableLanguageDetection' => false,

Detection priority order.

  1. URL language code.
  2. Session persisted language.
  3. Cookie persisted language.
  4. Browser Accept-Language headers.
  5. GeoIP detection (if configured, regardless of enableLanguageDetection).

Control language persistence in session and cookies.

// Default: persist language choice
'enableLanguagePersistence' => true,
// Disable persistence
'enableLanguagePersistence' => false,

Before a redirect, the manager converts a persisted or detected language to its configured form, the language or the language and its region: with ['en', 'es'], a stale es-MX cookie redirects to /es/.... A value that matches no configured language causes no redirect.

Configure session-based language persistence.

// Default session key
'languageSessionKey' => '_language',
// Custom session key
'languageSessionKey' => 'user_lang',
// Disable session persistence
'languageSessionKey' => false,

While languageSessionKey is a string, every request without a language code reads the session, which starts a session and sends its cookie even for visitors that had none. Set it to false to persist the language in the cookie only.

Configure cookie-based language persistence.

// Default cookie name
'languageCookieName' => '_language',
// 30 days (default)
'languageCookieDuration' => 2592000,
// Additional cookie options
'languageCookieOptions' => [
'httpOnly' => true,
'secure' => true,
'sameSite' => 'Lax',
'domain' => '.example.com',
],

To disable cookie persistence.

// Disable cookie persistence
'languageCookieDuration' => 0,

Configure automatic language detection based on visitor’s country.

// Default header name
'geoIpServerVar' => 'HTTP_X_GEO_COUNTRY',
'geoIpLanguageCountries' => [
// German for Germany, Austria
'de' => ['DEU', 'AUT'],
// French for France
'fr' => ['FRA'],
// English for US, UK
'en' => ['USA', 'GBR'],
// Spanish for multiple countries
'es' => ['ESP', 'MEX', 'ARG'],
],

Country values must match the GeoIP provider exactly. For example, Cloudflare uses two-letter codes such as DE and US, so configure those values instead of DEU and USA when using CF-IPCountry.

If using a different GeoIP service.

// CloudFlare
'geoIpServerVar' => 'HTTP_CF_IPCOUNTRY',
// Custom header
'geoIpServerVar' => 'HTTP_X_COUNTRY_CODE',
// Server variable, for example from mod_geoip
'geoIpServerVar' => 'GEOIP_COUNTRY_CODE',

The manager reads geoIpServerVar through the request:

  • A name that starts with HTTP_ is read from Request::getHeaders() as the header it represents (HTTP_X_GEO_COUNTRY is the X-Geo-Country header), never from the server parameters.
  • Any other name is read from the server parameters of the request: getServerParams() when the request provides it, as under psr-bridge, otherwise $_SERVER.

Yii builds the request headers from getallheaders() when PHP provides it. Behind Apache or PHP-FPM, a value that the web server sets only as a server variable with an HTTP_ name, for example with SetEnvIf or fastcgi_param HTTP_X_GEO_COUNTRY, may therefore be missing from the headers. Send the country as a real request header, or set a server variable without the HTTP_ prefix, such as GEOIP_COUNTRY_CODE.

Exclude specific routes and URLs from language processing.

'rules' => [
'login' => 'site/login',
'register' => 'site/register',
],
'ignoreLanguageUrlPatterns' => [
// Routes `api/...`, created and requested as the URLs `api/...`
'#^api/#' => '#^api/#',
// Debug toolbar and debugger of `yiisoft/yii2-debug`
'#^debug/#' => '#^debug/#',
// Routes `site/login` and `site/register`, created as the URLs `login` and `register` by the rules above
'#^site/(login|register)#' => '#^(login|register)#',
],

Pattern format: 'route_pattern' => 'url_pattern'.

  • Route patterns (keys) match the route in createUrl(), which then adds no language code.
  • URL patterns (values) match the path info of the request in parseRequest(), which then resolves no language.

Each URL pattern must match the URLs that the URL rules create for the routes of its key. When they disagree, for example with the site/login pattern above but without the login rule, createUrl() produces /site/login without a code, parseRequest() does not ignore it, and a language redirect targets /site/login?language=de instead of a language URL.

Ignored URLs resolve no language: they run in the default language, without the URL code, the persisted language, or detection.

Combine with standard Yii URL rules.

'rules' => [
'' => 'site/index',
'/custom' => 'test/action',
'/slug/<name>' => 'test/slug',
[
'pattern' => '/slash',
'route' => 'test/slash',
'suffix' => '/',
],
'<controller:\w+>/<action:\w+>' => '<controller>/<action>',
],

A catch-all rule such as '<slug:.+>' => 'page/view' encodes the / of its parameter as %2F. Set 'encodeParams' => false on the rule to keep the slashes.

Configure URL suffixes for all routes.

// Add a trailing slash to every URL
'suffix' => '/',
// No suffix (default)
'suffix' => '',

Individual rules can override the global suffix.

'rules' => [
[
'pattern' => '/noslash',
'route' => 'test/noslash',
// Override global suffix
'suffix' => '',
],
],

Customize the language parameter name used in URL generation.

// Default parameter name
'languageParam' => 'language',
// Custom parameter name
'languageParam' => 'lang',

Usage.

// Default
Url::to(['site/index', 'language' => 'de']);
// Custom
Url::to(['site/index', 'lang' => 'de']);

Configure the HTTP status code for language redirects.

// Default: temporary redirect
'languageRedirectCode' => 302,
// Permanent redirect
'languageRedirectCode' => 301,

A redirect required only by the URL normalizer keeps the normalizer status code (301 by default).

A language redirect keeps the route and the query parameters of the request, numeric keys included: /en/site/page?105=1&5=1, with en as the default language, redirects to /site/page?105=1&5=1. The request query parameter named by languageParam is dropped, so it never selects the redirect language or reaches the redirect URL: /en/site/page?language=fr redirects to /site/page.

A custom URL rule may throw yii\web\UrlNormalizerRedirectException itself:

  • A string URL, relative or absolute, propagates unchanged, whether or not a language redirect is pending.
  • An array URL ([route, ...params]) is created through the manager, so it carries the language code of the request, or the pending language when the request also needs a language redirect, and both become one redirect.

A request whose URL lacks the code of the language that the manager selects is redirected whatever its HTTP method. Browsers repeat a POST request as a GET after a 301 or 302 redirect, so the request body is lost, and an AJAX request receives Yii’s X-Redirect header instead of Location. Create form actions and AJAX URLs with Url::to(), or exclude them with ignore patterns.

Combine with Yii URL normalizer. Extra slashes after the language code remain available to the normalizer, including its redirect status or ACTION_NOT_FOUND. URL rules retain their own normalizer settings. Without a normalizer, the remaining path is parsed with its extra slashes instead of silently removing them.

<?php
declare(strict_types=1);
use yii\web\UrlNormalizer;
use yii2\extensions\localeurls\UrlLanguageManager;
return [
'components' => [
'urlManager' => [
'class' => UrlLanguageManager::class,
'languages' => ['en', 'de'],
'normalizer' => [
'class' => UrlNormalizer::class,
'action' => UrlNormalizer::ACTION_REDIRECT_TEMPORARY,
],
],
],
];

React to language changes with event handlers.

<?php
declare(strict_types=1);
use yii2\extensions\localeurls\{LanguageChangedEvent, UrlLanguageManager};
return [
'components' => [
'urlManager' => [
'class' => UrlLanguageManager::class,
'languages' => ['en', 'de', 'fr'],
'on languageChanged' => static function (LanguageChangedEvent $event) {
$oldLang = $event->oldLanguage;
$newLang = $event->language;
// Log language change
Yii::info("Language changed from {$oldLang} to {$newLang}");
// Update user preferences
if (Yii::$app->user->isGuest === false) {
Yii::$app->user->identity->language = $newLang;
Yii::$app->user->identity->save();
}
},
],
],
];

Alternative event handler configuration.

'on languageChanged' => [\app\handlers\LanguageHandler::class, 'handleChange'],

For applications that only serve API.

<?php
declare(strict_types=1);
use yii2\extensions\localeurls\UrlLanguageManager;
return [
'components' => [
'urlManager' => [
'class' => UrlLanguageManager::class,
'languages' => ['en', 'de'],
'ignoreLanguageUrlPatterns' => [
// Skip all API routes
'#^api/#' => '#^api/#',
],
],
],
];

For applications installed in subdirectories.

<?php
declare(strict_types=1);
use yii2\extensions\localeurls\UrlLanguageManager;
return [
'components' => [
'urlManager' => [
'class' => UrlLanguageManager::class,
'languages' => ['en', 'de'],
// Application subdirectory
'baseUrl' => '/myapp',
],
],
];

baseUrl is a path. To create absolute URLs for a specific host, set hostInfo, for example 'hostInfo' => 'https://example.com'; an absolute baseUrl is not supported. createUrl() treats a URL as absolute only when it starts with a scheme, such as https://, so a relative URL whose path contains :// gets its language code after the base URL like any other URL.

For environments where index.php must be visible.

<?php
declare(strict_types=1);
use yii2\extensions\localeurls\UrlLanguageManager;
return [
'components' => [
'urlManager' => [
'class' => UrlLanguageManager::class,
'languages' => ['en', 'de'],
// Show `index.php` in URL
'showScriptName' => true,
],
],
];

The manager runs unchanged in long-running workers, such as yii2-extensions/psr-bridge under FrankenPHP or RoadRunner. psr-bridge creates new request, response, session, and urlManager components for every request; the rules below cover the state that outlives a request: the application language, PHP’s session, class-level event handlers, and the server environment of the runtime.

  • Redirects are thrown from parseRequest() as yii\web\UrlNormalizerRedirectException, and yii\web\Application::handleRequest() turns them into a redirect response. The manager never calls exit(). String redirect URLs thrown by custom URL rules propagate unchanged; see Redirect URLs.
  • The manager keeps no request state: every step works on the request being parsed. Each Request object is processed once, and later parseRequest() calls only parse it. psr-bridge creates a new Request for every request; a runner or test that mutates and reuses one Request instance gets no language processing after its first request.
  • Do not list urlManager in persistentComponents: it is a core component, and psr-bridge would rebuild it as a plain yii\web\UrlManager.

Set language explicitly in the application configuration, or set defaultLanguage on the manager. The application is reconfigured for every request, and a configuration without language keeps the language of the previous request, which also becomes the default language of the new manager. defaultLanguage resets Yii::$app->language at the start of every request and does not depend on the application language when the manager is created.

  • Keep the psr-bridge useSession flag at true, its default. The bridge then opens PHP’s session with the session cookie of each request and closes it afterwards. With useSession set to false, nothing resets PHP’s session state between requests, and on RoadRunner later visitors receive the session id and the session data of earlier requests. If the application must run with useSession set to false, set languageSessionKey to false so the manager never opens a session.
  • Call ini_set('session.use_cookies', '0') in the entry script before the application runs. psr-bridge emits the session cookie itself; without this setting, PHP adds its own Set-Cookie: PHPSESSID header under FrankenPHP.
  • While languageSessionKey is a string, the manager reads the session on every request without a language code. A plain yii\web\UrlManager starts no session, but with this manager anonymous visitors, bots, and health checks get a PHPSESSID cookie and, under FrankenPHP, PHP’s no-store cache headers. Set languageSessionKey to false to persist the language in the cookie only and keep these responses stateless.

With cookie validation enabled, the Yii default, set cookieValidationKey on the request component. psr-bridge signs the _language cookie and ignores unsigned values, so a _language cookie written before validation was enabled is not read. A missing key throws yii\base\InvalidConfigException on the first cookie read.

yii\web\Response::redirect() makes the redirect URL absolute with the host info of the request. Behind a TLS-terminating proxy, set trustedHosts on the request component to the proxy addresses, so that X-Forwarded-Proto: https marks the request as secure and redirect URLs start with https://. FrankenPHP sets $_SERVER['HTTPS'] for HTTPS requests; RoadRunner does not.

Attach languageChanged handlers in the urlManager configuration ('on languageChanged' => ...), which attaches them again to the manager of every request. psr-bridge calls yii\base\Event::offAll() after each request, so a class-level handler attached with Event::on(UrlLanguageManager::class, ...) fires for the first request only.

The manager reads headers, cookies, and server parameters only through the Request object: getHeaders(), getCookies(), and getServerParams() when the request provides it. It falls back to $_SERVER only for a GeoIP name without the HTTP_ prefix on a request without getServerParams(), such as a plain yii\web\Request.

RoadRunner refreshes $_SERVER for every request through spiral’s PSR7Worker, leaves $_COOKIE empty, and has no getallheaders(). FrankenPHP refreshes $_SERVER, $_COOKIE, and getallheaders() for every request. Under psr-bridge, the request reads headers, cookies, and server parameters from the PSR-7 request, so the manager behaves the same on both runtimes.

<?php
declare(strict_types=1);
use yii2\extensions\localeurls\{LanguageChangedEvent, UrlLanguageManager};
use yii2\extensions\psrbridge\http\Request;
return [
'components' => [
'request' => [
'class' => Request::class,
// Signs the `_language` cookie; keep the real key out of version control.
'cookieValidationKey' => 'replace-with-a-long-random-secret',
// Proxy addresses whose `X-Forwarded-Proto` header marks HTTPS requests.
'trustedHosts' => ['10.0.0.0/8'],
],
'urlManager' => [
'class' => UrlLanguageManager::class,
// Applied to `Yii::$app->language` at the start of every request.
'defaultLanguage' => 'en',
'languages' => ['en', 'de', 'fr'],
// Stateless option: persist the language in the cookie only and never open a session.
'languageSessionKey' => false,
'showScriptName' => false,
// Attached again for every request, unlike class-level `Event::on()` handlers.
'on languageChanged' => static function (LanguageChangedEvent $event): void {
Yii::info("Language changed from {$event->oldLanguage} to {$event->language}.");
},
],
],
];