Blazor (Client-side) アプリケーションのテキストをローカライズする

# この記事の範囲

この記事では、Client-side Blazor における、テキストのローカライゼーションのシナリオについてのみ取り上げます。

日付、時刻、および通貨の形式、および、Server-side Blazor はこの記事の範囲外です。

# 既存のテキストローカライズソリューション

インターネット上で "Blazor Localize" などをキーワードに検索すると、既にいくつか解決策や記事を見つけることができます。

自分が見つけたのは下記です。

- **"Localization In Blazor App Using Microsoft.JSInterop"**
    - https://www.c-sharpcorner.com/article/localization-in-blazor-app-using-microsoft-jsinterop/
- **"Internationalizing a Blazor App with ASPNET Core as backend service"**
    - https://remibou.github.io/I18n-with-Blazor-and-ASPNET-Core/

しかし、自分は以下の 4 つの理由から上記のソリューションには満足できませんでした。

1. サーバーサイドの実装が必要
2. .resx エディタが必要
3. 静的型付ではない、"string" によるキーアクセス
4. Blazorコンポーネントライブラリに未対応

詳しく見ていきましょう。

# 上記の解決策に満足できなかった4つの理由

## 1.サーバーサイドの実装が必要

Blazor (Client-side) アプリは SPA であり、クライアントサイドのソリューションです。
ですから、Blazor (Client-side) アプリは GitHub ページのような静的コンテンツのみの HTTP サーバーにデプロイできます。

しかし、上記のローカライズソリューションではサーバーサイドの ASP.NET Core 実装が必要です。
当然、ASP.NET Core を実行できる Web サーバーも必要です (まぁ、普通に Linux Docker が動けばどこでも動くのは確かですが)。

## 2. .resx エディタが必要

.resx ファイルは XML 形式のファイルであり、一般的な C＃ プログラミングのローカライズシナリオで使用されます。

しかし、.resx ファイルを編集するには、事実上、Visual Studio の使用が必須でしょう。
素のテキストエディタで .resx ファイルを編集するのは、現実的ではないと思います。

## 3. 静的型付ではない、"string" によるキーアクセス

一般的なテキストローカライゼーションソリューションは、「キー」から対応するローカライズされたテキストを取得することとして機能します。

上記の解決策では、C＃/ Razor のソースコード中で文字列でキーを記述する必要があります。

しかし文字列としてキーを書くことはしばしばタイプミスを引き起こす可能性があり、そのミスはコンパイル時に検出できません。

また、エディタのコード補完機能の助けを借りることもできません。（例： "IntelliSense"）

文字列によるキーアクセスなどの遅延バインディングな解決策ではなく、静的な型付けの解決策が渇望されます。

## 4. Blazorコンポーネントライブラリに未対応

Blazor は、NuGet パッケージとしてのコンポーネントライブラリの作成と再利用をサポートしています。

しかし上記の解決策では、Blazor コンポーネントライブラリパッケージのローカライズには使えませんでした。


# ...ということで自作!

結局、インターネットで見つけたどんな解決策でも満足できなかったので、自分で新しい解決策を作ることにしました。


## アプローチ

自分が Blazor (Client-side) アプリのテキストをローカライズする仕組みを自作するにあたり、最も工夫した点は、ライブラリコードを書くだけでなく、ビルド時にいくつかのタスクを処理するためのビルドスクリプトを書くことでした。

すなわち、静的な型付けファイルの生成と、静的コンテンツとしてのローカライズテキストファイルの生成を、ビルド時に実施するようにした点です。


要点を以下に記します。

- キーローカライズされたテキスト対応表の作成には、.resx ファイル形式の代わりに、**単純な JSON / CSV 形式**を使うことにしました（これらを「ローカライズされたテキストソースファイル」と呼ぶことにします）。
- ビルド時に「ローカライズされたテキストソースファイル」から**静的型付き C＃クラス（「テキストテーブルクラス」と呼ぶことにします）ソースコードファイルを自動的に生成**します。
    - 「テキストテーブルクラス」は、「ローカライズされたテストソースファイル」の各キーに関連付けられたフィールドを持ちます。
- また、ビルド時に「ローカライズされたテキストのソースファイル」から「wwwroot」コンテンツフォルダの下に JSON 形式のファイル（「ローカライズされたテキストリソース JSON ファイル」と呼びます）を自動的に生成します。
    - 「ローカライズされたテキストリソース JSON ファイル」は Web サーバー上の**静的コンテンツファイルとしてデプロイ**され、それらは実行時にBlazor (Client-side) クライアントプロセスから取得されます。
- 「ローカライズされたテキストリソース JSON」ファイルを Blazor コンポーネントライブラリの **NuGet パッケージファイル内に収納**する **"msbuild" ビルドスクリプト**を含めます。

# 成果物

今回自作した Blazor (Client-side) のテキストローカライズを行なうライブラリ/ビルドスクリプトは、下記 NuGet パッケージから使用できます。

- **Blazor Internationalization(I18n) Text** - "Toolbelt.Blazor.I18nText" NuGet package
    - [https://www.nuget.org/packages/Toolbelt.Blazor.I18nText/](https://www.nuget.org/packages/Toolbelt.Blazor.I18nText/)

## .resx ファイル不要、単純な JSON 形式、そして CSV 形式も利用可能

![fig](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/fig.001.png)

## GitHub pages でも動作可 (静的コンテンツのみの HTTP サーバー)

- https://jsakamoto.github.io/Toolbelt.Blazor.I18nText/

![movie.1](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/movie-001.gif)

## インテリセンスもバッチリ動作OK!

![movie.2](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/movie-002.gif)

# 使い方

### Step.1 - "Toolbelt.Blazor.I18nText" パッケージをプロジェクトに追加

まずは `Toolbelt.Blazor.I18nText` NuGet パッケージを、対象となる Blazor (Client-side) アプリケーションのプロジェクトに追加します。

dotnet CLI を使うなら、こんな感じです。

```
$ dotnet add package Toolbelt.Blazor.I18nText
```

Windows OS 上で Visual Studio を使ってて、Visual Studio のパッケージマネージャーコンソールから実行するならこんな感じでしょう。

```
PM> Install-Package Toolbelt.Blazor.I18nText
```

### Step.2 - ローカライズされたテキストソースファイルをJSONまたはCSVとして作成

Blazor アプリプロジェクトフォルダの下の、`i18ntext` フォルダに、各言語のローカライズされたテキストソースファイルを追加します。

ローカライズされたテキストソースファイルは、次の例のように単純な Key-Value のみの JSON ファイルである必要があります。

```json
{
  "Key1": "Localized text 1",
  "Key2": "Localized text 2",
  ...
}
```

CSV 形式を使いたい場合は、ヘッダー行は無しで、2列だけの形式で Key-Value 形式の CSV ファイルを作成します。


```
Key1,Localized text 1
Key2,Localized text 2
```

**注意** - CSV ファイルの文字エンコード方式は **UTF-8** である必要があります。

そして、ローカライズされたテキストソースファイルの命名規則は、次のとおりとしてください。

```
<Text Table Name>.<Language Code>.{json|csv}
```

![fig.1](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/fig.001.png)

### Step.3 - ローカライズされたテキストソースファイルが作成または更新されたときは、常にプロジェクトをビルド

これらのローカライズされたテキストソースファイルを作成または更新したら、**その都度、Blazor アプリプロジェクトをビルド**してください。

プロジェクトをビルドすることで、ビルドプロセスによって "型付けされたテキストテーブルクラス" C＃ファイルが、`i18ntext/@types` フォルダ内に生成されます。

さらに「ローカライズされたテキストリソース JSON」ファイルも `wwroot/content/i18ntext` フォルダに生成されます。

![fig.2](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/fig.002.png)

#### 補足

ローカライズされたテキストソースファイル（.json または .csv）が変更されるたびに、テキストテーブルクラス及びローカライズされたテキストリソース JSON を自動で生成したい場合は、以下の引数を指定して dotnet watch コマンドを使用できます。

```shell
$ dotnet watch msbuild -t:CompileI18nText
```

上記 dotnet CLI コマンドを実行すると、dotnet CLI プロセスは常駐してローカライズされたテキストソースファイルの変更を監視します。
常駐している dotnet CLI がローカライズされたテキストソースファイルの変更を検出すると、dotnet CLI はローカライズされたテキストソースファイルをテキストテーブルクラスファイルとローカライズされたテキストリソース JSON ファイルに再コンパイルします。

![fig.2-2](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/fig.002-2.png)

### Step.4 - "I18nText" サービスを使用するようにアプリを構成

Blazor アプリの "Startup" クラスの C＃ ソースファイルをエディタで開き、  `Toolbelt.Blazor.Extensions.DependencyInjection` 名前空間を開くための using 句を追加して、Startup クラスの `ConfigureServices()` メソッドに次のコードを追加します。

```csharp
services.AddI18nText<Startup>();
```

![fig.3](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/fig.003.png)

### Step.5 - Blazor コンポーネント内で "テキストテーブル" オブジェクトを取得

Blazor コンポーネントファイル (.razor) を以下のとおり編集します:

1) `Toolbelt.Blazor.I18nText.I18nText` サービスを注入します。

```csharp
@inject Toolbelt.Blazor.I18nText.I18nText I18nText
```

2) ローカライズされたテキストソースファイルから生成された、テキストテーブルクラスのフィールドを追加し、既定のインスタンスを割り当てておきます。

```csharp
@functions {

    I18nText.MyText MyText = new I18nText.MyText();
```

**補足** - テキストテーブルクラスの名前空間は、`<default namespace of your Blazor project>` + `"I18nText"` となります。

3) Blazor コンポーネントの `OnInitAsync()` メソッドをオーバーライドし、先に作成したテキストテーブルクラスのフィールドに、先にインジェクトした `I18nText` サービスインスタンスから `GetTextTableAsync<T>()` メソッド呼び出しで取得したテキストテーブルオブジェクトを割り当てます。

```csharp
protected override async Task OnInitAsync()
{
  MyText = await I18nText.GetTextTableAsync<I18nText.MyText>(this);
```

![fig.4](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/fig.004.png)

### Step.6 - テキストテーブルオブジェクトからローカライズされたテキストを取得

以上の手続きで、テキストテーブルオブジェクトから、ローカライズされたテキストを取得できるようになります。

Windows OS で Blazor 拡張を入れた Visual Studio を使っている場合、あるいは、OS 問わず Visual Studio Code + C# 拡張を使って開発中の場合は、それら開発環境の "インテリセンス" および "ドキュメントコメント" の支援が得られます。

![movie.2](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/movie-002.gif)

#### 補足

テキストテーブルオブジェクトは、インデクサ構文を使った、文字列によるキー参照での動的なローカライズされたテキストの取得もサポートしています (下記例)。

```html
<h1>@MyText["HelloWorld"]</h1>
```

この方法は「遅延バインディング」と呼ばれることがあります。
この機能は場合によっては非常に便利です。

ただし、キー文字列のタイプミスを犯しても、これらの間違いはコンパイル時には見つかりません。
その場合、テキストテーブルオブジェクトはランタイム例外を発生させることなく、キー文字列をそのまま返します。

### Step.7 - あとは実行するだけ!

以上で、あとはビルドして実行するだけです。

`I18nText` サービスは、現在の言語設定を Web ブラウザから取得し、そしてもっとも適した言語のローカライズされたテキストリソース JSON をロードして動作します。

![fig.5](https://raw.githubusercontent.com/jsakamoto/Toolbelt.Blazor.I18nText/master/.assets/fig.005.png)

# さらに詳しく

さらなる詳細は、GitHub リポジトリ上の README を参照ください。

- [https://github.com/jsakamoto/Toolbelt.Blazor.I18nText](https://github.com/jsakamoto/Toolbelt.Blazor.I18nText)



Happy coding with Blazor :)