いろいろ翻訳、"Translation"

[Symfony Component Advent Calendar 2023](https://qiita.com/advent-calendar/2023/symfony-component)の2日目の記事です。

## いろいろ翻訳、"Translation"

[Translation](https://symfony.com/doc/current/translation.html)は、翻訳を司るコンポーネントです。

### インストール

```shell
composer require symfony/translation
```

### 翻訳

翻訳を行うには、元の文と翻訳文をセットにした翻訳用のリソースを読み込ませます。

```php
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\ArrayLoader;

$translator = new Translator('ja');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', [
    'Hello World!' => 'こんにちは、世界！',
    "This is an apple." => 'これはりんごです。',
], 'ja');

echo $translator->trans('Hello World!'); // こんにちは、世界！
```

### Symfonyの場合

Symfonyの場合は、翻訳用のリソースを`translations`ディレクトリに用意し設定を行うことで、自動的にリソースを読み込めます。
また、`TranslatorInterface`をDIすることで、`Translator`オブジェクトを自動注入できます。

```yaml:config/packages/translation.yaml
framework:
    default_locale: ja # デフォルトで翻訳する言語
    translator:
        default_path: '%kernel.project_dir%/translations' # 翻訳ファイルの置き場所
```

```yaml:translations/messages.ja.yaml
"Hello world!": "こんにちは、世界"
"This is an apple.": "これはりんごです。"
```

```php:src/Command/SomeCommand.php
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\Translation\TranslatorInterface;

class TranslateCommand extends Command
{
    public function __construct(private readonly TranslatorInterface $translator)
    {
        parent::__construct();
    }


    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $message = 'Hello world!';
        
        $translated = $this->translator->trans($message);
        $io->info($translated); // こんにちは、世界！

        return Command::SUCCESS;
    }
}
```

### メッセージIDの指定

元の文章ではなく、メッセージIDを指定することで翻訳文を入手することも可能です。

```yaml:translations/messages.ja.yaml
"Hello world!": "こんにちは、世界"
apple: "これはりんごです。" # ID指定
```

```
$translator->trans('apple'); // これはりんごです。
```

### Twig上での利用

Twigではフィルター機能とタグで翻訳することが可能です。

```twig:index.html.twig
{% set message = 'Helllo world!' %}
{{ message|trans }}

{% trans %}Hello world!{% endtrans %}
{* どちらも、「こんにちは、世界！」 *}
 ```

### ICUメッセージフォーマット

たとえば英語だと、りんごは単数形だと`apple`、複数形だと`apples`です。また、男性に対しては`he`、女性に対しては`she`といったように、翻訳したい箇所で、別の単語に切り替えたい時があります。そのような場合、 [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/)というものを利用して翻訳することができます。

```yaml:translations:messages+intl-icu.ja.yaml
num_of_apples: >-
    {apples, plural,
        =0  {りんごはないです。}
        =1 {りんごはひとつです。}
        =2 {りんごはふたつです。}
        =3 {りんごはみっつです。}
        =4 {りんごはよっつです。}
        =5 {りんごはいつつです。}
        =6 {りんごはむっつです。}
        =7 {りんごはななつです。}
        =8 {りんごはやっつです。}
        =9 {りんごはここのつです。}
        other   {りんごは#個です。}   
    }
intivation: >-
        {gender, select,
        female  {{name}さんに招待状をお送りしました。彼女の招待コードは{code}です}
        male    {{name}さんに招待状をお送りしました。彼の招待コードは{code}です}
        other   {{name}さんに招待状をお送りしました。{name}さんの招待コードは{code}です}
    }
```

```php
for ($i = 0; $i <= 10; $i ++) {
    echo $translator->trans('num_of_apples', ['apples' => $i]);
}

/*
りんごはないです。
りんごはひとつです。
りんごはふたつです。
りんごはみっつです。
りんごはよっつです。
りんごはいつつです。
りんごはむっつです。
りんごはななつです。
りんごはやっつです。
りんごはここのつです。
りんごは10個です。
*/

echo $translator->trans(
    'invitation',
    ['gender' => 'male', 'name' => 'すみだ', 'code' => '12345']
);

// すみださんに招待状をお送りしました。彼の招待コードは12345です。
```

### 外部プロバイダーの利用

現在は[Crowdin](https://crowdin.com/)など、外部の翻訳サービスも充実しています。そんな外部サービスを利用して翻訳ファイルを生成することも可能です。現在利用できるのは、`Crowdin` `Loco` `Lokalise`で、それぞれのプロバイダーを追加インストールします。

#### インストール＆設定

```shell
composer require symfony/crowdin-translation-provider
```

```yaml:config/packages/translation.yaml
framework:
    translator:
        providers:
            crowdin:
                dsn: '%env(CROWDIN_DSN)%'
                locales: ['en', 'ja']
```

#### 外部サービスから翻訳ファイル生成

```shell
bin/console translation:pull
```

#### 外部サービスに翻訳ファイルをアップロード

```shell
bin/console translation:push
```

#### Crowdinにアップロードした結果

![スクリーンショット 2023-11-26 17.36.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9882/1a0358ee-0ab5-4b7d-5134-bfa16a377f42.png)

たとえば本番デプロイ後に外部サービスから翻訳ファイルを生成するようにしておけば、コードと翻訳ファイルを分けて扱うことが可能になります。

## まとめ

今回は`Translation`を紹介しました。日本向けのサービスであればあまり活躍の機会はありませんが、グローバルなサイトを作る時には必須な機能になると思うので、要チェックなコンポーネントになります。使い方も楽ちんです。