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:
composer require yiisoft/translator-message-phpInstale o extrator como dependência de desenvolvimento:
composer require --dev yiisoft/translator-extractorOs 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
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:
'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
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.phpmessages/en-US/app.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
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
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
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
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:
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:
<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:
use Yiisoft\I18n\Locale;
$locale = new Locale('de-DE');
echo $locale->language(); // de
echo $locale->region(); // DE
echo $locale->fallbackLocale()->asString(); // deIsso é ú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:
src/Web/HomePage/de-DE/template.phpsrc/Web/HomePage/de/template.phpsrc/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:
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
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:
composer yii-config-rebuildExtraia as mensagens dos arquivos-fonte da aplicação:
./yii translator/extract src --languages=en-US,deO comando procura chamadas como:
$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.phpDepois 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:
./yii translator/extract src --languages=en-US,de --category=shopO extrator exclui vendor/ por padrão. Use --only ou --except quando precisar de uma varredura mais restrita:
./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:
'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:
$translator->translate('checkout.submit', category: 'shop');Os arquivos de mensagens ficam assim:
messages/de/shop.php
messages/en-US/shop.phpMantenha os IDs de mensagens estáveis. Mudar um ID cria uma nova tarefa de tradução para cada idioma.