Skip to content

Internacionalização ​

A internacionalização prepara uma aplicação para mais de um locale. A localização é a parte específica de cada locale: textos traduzidos, formas de plural, formatos de data e número e templates de view para um locale como en-US ou de.

Numa aplicação Yii, as partes habituais são:

  • a escolha do idioma da requisição atual;
  • as mensagens traduzidas da aplicação;
  • a formatação de plurais e parâmetros;
  • templates de view localizados para páginas que precisam de marcação diferente.

Este tutorial parte de um projeto novo do yiisoft/app.

Instalando os pacotes ​

O yiisoft/app já inclui yiisoft/i18n e yiisoft/translator. Instale o armazenamento de mensagens em arquivos PHP:

sh
composer require yiisoft/translator-message-php

Instale o extrator como dependência de desenvolvimento:

sh
composer require --dev yiisoft/translator-extractor

Os exemplos usam a formatação de mensagens ICU para as formas de plural. Confira se a extensão intl do PHP está habilitada. Se você só precisa de placeholders simples, use Yiisoft\Translator\SimpleMessageFormatter na configuração abaixo.

Configurando o idioma da aplicação ​

O template da aplicação guarda as configurações básicas em config/common/application.php. Defina ali o idioma de origem:

php
<?php

declare(strict_types=1);

return [
    'charset' => 'UTF-8',
    'locale' => 'en-US',
    'name' => 'My Project',
];

O layout padrão usa esse valor no atributo <html lang=""> por meio de ApplicationParams. Quando a aplicação escolhe o idioma por requisição, defina o idioma escolhido na view, como mostrado em Escolhendo o idioma por requisição.

Configure os padrões do tradutor em config/common/params.php:

php
'yiisoft/translator' => [
    'locale' => 'en-US',
    'fallbackLocale' => 'en-US',
    'defaultCategory' => 'app',
],

locale é o idioma padrão das traduções. fallbackLocale é usado quando falta uma tradução para o idioma atual. defaultCategory é a categoria usada quando uma chamada a translate() não especifica nenhuma.

Configurando o armazenamento de mensagens ​

Crie config/common/di/translator.php:

php
<?php

declare(strict_types=1);

use Yiisoft\Aliases\Aliases;
use Yiisoft\Translator\CategorySource;
use Yiisoft\Translator\IntlMessageFormatter;
use Yiisoft\Translator\Message\Php\MessageSource;

/** @var array $params */

return [
    'translation.app' => [
        'definition' => static function (Aliases $aliases) use ($params): CategorySource {
            $messageSource = new MessageSource($aliases->get('@root/messages'));

            return new CategorySource(
                $params['yiisoft/translator']['defaultCategory'],
                $messageSource,
                new IntlMessageFormatter(),
                $messageSource,
            );
        },
        'tags' => ['translation.categorySource'],
    ],
];

Isso registra a categoria app. O MessageSource lê arrays PHP do diretório messages/ e grava as mensagens extraídas de volta no mesmo diretório.

Crie os arquivos de mensagens de origem e de destino:

messages/
├── de/
│   └── app.php
└── en-US/
    └── app.php

messages/en-US/app.php:

php
<?php

return [
    'home.title' => 'Home',
    'home.welcome' => 'Welcome to Yii3',
    'cart.items' => '{count, plural, =0{No items} one{# item} other{# items}}',
];

messages/de/app.php:

php
<?php

return [
    'home.title' => 'Home',
    'home.welcome' => 'Willkommen bei Yii3',
    'cart.items' => '{count, plural, =0{Keine Artikel} one{# Artikel} other{# Artikel}}',
];

Traduzindo mensagens ​

Injete Yiisoft\Translator\TranslatorInterface onde você prepara os dados de uma resposta. Para a action padrão da página inicial, altere src/Web/HomePage/Action.php:

php
<?php

declare(strict_types=1);

namespace App\Web\HomePage;

use Psr\Http\Message\ResponseInterface;
use Yiisoft\Translator\TranslatorInterface;
use Yiisoft\Yii\View\Renderer\WebViewRenderer;

final readonly class Action
{
    public function __construct(
        private WebViewRenderer $viewRenderer,
        private TranslatorInterface $translator,
    ) {}

    public function __invoke(): ResponseInterface
    {
        return $this->viewRenderer->render(__DIR__ . '/template', [
            'itemCount' => 3,
            'translator' => $this->translator,
        ]);
    }
}

Use o tradutor em src/Web/HomePage/template.php:

php
<?php

declare(strict_types=1);

use App\Shared\ApplicationParams;
use Yiisoft\Html\Html;
use Yiisoft\Translator\TranslatorInterface;
use Yiisoft\View\WebView;

/**
 * @var WebView $this
 * @var ApplicationParams $applicationParams
 * @var int $itemCount
 * @var TranslatorInterface $translator
 */

$this->setTitle($translator->translate('home.title'));
?>

<div class="text-center">
    <h1><?= Html::encode($translator->translate('home.welcome')) ?></h1>

    <p><?= Html::encode($translator->translate('cart.items', ['count' => $itemCount])) ?></p>
</div>

Escape o texto traduzido antes de exibi-lo. Guarde HTML confiável separadamente ou renderize-o a partir de um template de view.

Escolhendo o idioma por requisição ​

Numa aplicação web, escolha o idioma cedo na pilha de middlewares e aplique-o tanto ao tradutor quanto à view. O exemplo abaixo usa o parâmetro lang da query string e uma lista fixa de idiomas permitidos:

php
<?php

declare(strict_types=1);

namespace App\Web\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Translator\TranslatorInterface;
use Yiisoft\View\WebView;

final readonly class LocaleMiddleware implements MiddlewareInterface
{
    private const DEFAULT_LOCALE = 'en-US';
    private const SUPPORTED_LOCALES = ['en-US', 'de'];

    public function __construct(
        private TranslatorInterface $translator,
        private WebView $view,
    ) {}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $locale = $this->getLocale($request);

        $this->translator->setLocale($locale);
        $this->view->setLocale($locale);

        return $handler->handle($request);
    }

    private function getLocale(ServerRequestInterface $request): string
    {
        $locale = $request->getQueryParams()['lang'] ?? null;

        return is_string($locale) && in_array($locale, self::SUPPORTED_LOCALES, true)
            ? $locale
            : self::DEFAULT_LOCALE;
    }
}

Registre o middleware em config/common/routes.php ou no grupo de rotas que precisa de saída localizada. Adicione o middleware antes da action da rota:

php
use App\Web\Middleware\LocaleMiddleware;
use Yiisoft\Router\Route;

return [
    Route::get('/')
        ->middleware(LocaleMiddleware::class)
        ->action(App\Web\HomePage\Action::class)
        ->name('home'),
];

Altere src/Web/Shared/Layout/Main/layout.php para ler o idioma do estado atual da view:

php
<html lang="<?= Html::encode($this->getLocale()) ?>">

Use a mesma abordagem quando o idioma estiver guardado na sessão, num cookie, no perfil do usuário ou num parâmetro de rota.

O yiisoft/i18n oferece Yiisoft\I18n\Locale para trabalhar com códigos de locale BCP 47:

php
use Yiisoft\I18n\Locale;

$locale = new Locale('de-DE');

echo $locale->language(); // de
echo $locale->region(); // DE
echo $locale->fallbackLocale()->asString(); // de

Isso é útil quando você precisa derivar um idioma de reserva ou inspecionar as partes do locale antes de escolher um idioma suportado.

Arquivos de view localizados ​

Use tradução de mensagens para rótulos, títulos, botões, mensagens de validação e parágrafos curtos. Use arquivos de view localizados quando uma página precisa de marcação diferente ou de uma quantidade diferente de texto para cada idioma.

O yiisoft/view procura arquivos localizados num subdiretório com o nome do locale, ao lado do arquivo de view original. Por exemplo, quando src/Web/HomePage/template.php é renderizado com o locale de-DE, o Yii verifica:

  1. src/Web/HomePage/de-DE/template.php
  2. src/Web/HomePage/de/template.php
  3. src/Web/HomePage/template.php

O fallback para o código de idioma de duas letras é automático.

Você pode definir o idioma para uma única chamada de renderização:

php
return $this->viewRenderer
    ->withLocale('de-DE')
    ->render(__DIR__ . '/template');

Quando o idioma é definido no WebView dentro de um middleware, o mesmo idioma é aplicado às views renderizadas pelo WebViewRenderer.

Extraindo mensagens ​

Configure o extrator para que ele consiga ler as mensagens existentes e gravar novos IDs no mesmo armazenamento de mensagens.

Crie config/common/di/translator-extractor.php:

php
<?php

declare(strict_types=1);

use Yiisoft\Aliases\Aliases;
use Yiisoft\Definitions\DynamicReference;
use Yiisoft\Translator\Message\Php\MessageSource;
use Yiisoft\TranslatorExtractor\CategorySource as ExtractorCategorySource;
use Yiisoft\TranslatorExtractor\Extractor;

/** @var array $params */

return [
    Extractor::class => [
        '__construct()' => [
            [
                DynamicReference::to([
                    'class' => ExtractorCategorySource::class,
                    '__construct()' => [
                        $params['yiisoft/translator']['defaultCategory'],
                        DynamicReference::to(
                            static fn (Aliases $aliases): MessageSource => new MessageSource($aliases->get('@root/messages')),
                        ),
                        DynamicReference::to(
                            static fn (Aliases $aliases): MessageSource => new MessageSource($aliases->get('@root/messages')),
                        ),
                    ],
                ]),
            ],
        ],
    ],
];

Reconstrua a configuração depois de adicionar o arquivo de configuração do extrator:

sh
composer yii-config-rebuild

Extraia as mensagens dos arquivos-fonte da aplicação:

sh
./yii translator/extract src --languages=en-US,de

O comando procura chamadas como:

php
$translator->translate('home.welcome');
$translator->translate('cart.items', ['count' => $itemCount]);

Ele grava os IDs que faltam em:

messages/en-US/app.php
messages/de/app.php

Depois da extração, edite os arquivos dos idiomas de destino e troque os IDs de origem pelo texto traduzido.

Use --category quando uma chamada pertence a outra categoria:

sh
./yii translator/extract src --languages=en-US,de --category=shop

O extrator exclui vendor/ por padrão. Use --only ou --except quando precisar de uma varredura mais restrita:

sh
./yii translator/extract src --languages=en-US,de --only=**/*.php --except=**/runtime/**

Trabalhando com categorias ​

A categoria padrão app basta para a maioria das mensagens da aplicação. Adicione categorias quando um pacote ou um recurso grande tem seus próprios arquivos de mensagens.

Para uma categoria shop, crie outro category source com a tag:

php
'translation.shop' => [
    'definition' => static function (Aliases $aliases): CategorySource {
        $messageSource = new MessageSource($aliases->get('@root/messages'));

        return new CategorySource(
            'shop',
            $messageSource,
            new IntlMessageFormatter(),
            $messageSource,
        );
    },
    'tags' => ['translation.categorySource'],
],

Traduza informando o nome da categoria:

php
$translator->translate('checkout.submit', category: 'shop');

Os arquivos de mensagens ficam assim:

messages/de/shop.php
messages/en-US/shop.php

Mantenha os IDs de mensagens estáveis. Mudar um ID cria uma nova tarefa de tradução para cada idioma.