C#のリソースの仕組みを使って多言語対応させようぜ☆（＾～＾）

# Visual Studio 2017 を使って C# のプログラムを多言語対応させようぜ

日本だけで使うプログラムなら、多言語対応の書き方は　一切　目にすることもないと思う。
見なくていいものは目に触れさせない、
そこらへん、Visual Studio / C#、　はたまた　オブジェクト指向プログラミング　というやつ は
よく出来てある……。

## 状況設定

ここは東京。　フランス人と　インド人と　日本人が　働いているとするぜ。

私の書いたプログラムが　運悪く　ファイル書き込み時に　ファイルのロックに引っかかって　何度リトライしてもダメ、
諦めて　タイムアウトさせて　異常終了することにするとしよう。
SendMail の機能を使って フランスチーム、インドチーム、日本チームに　異常通知メールを飛ばすという状況を想像してくれだぜ。

```Plain
### 疑似コード

### 件名と本文。グーグル翻訳でいいだろ……、情報量の少ない日本語の直訳は、きっとおかしなことになっている。
SendMail("ツール障害", "ファイルのロックに引っかかった。")
SendMail("Défaillance de l'outil", "J'ai attrapé un verrou de fichier.")
SendMail("Tool failure", "I caught on a file lock.")
```

こんな感じで　ベタ書き　すれば　その場の用は足りるが、何と言ったらいいのだろう。
ソースコードが　１文字　でも増えるのは労力だ。
定時に帰りたいマンは　こんなことはしない。
というのも、**テキスト** は人類にとって便利だが、 **テキストを扱う** のはきっと人類が得意なものではない……。

## そこそこ解決しよう

例えば、以下のようにする。

```Plain
### 疑似コード

foreach culture in ["ja-JP(日本)", "fr-FR(フランス)", "en-US(インド)"]
    Resources.Culture = culture
    SendMail(Resources.ToolFailureSubject, Resources.ToolFailureBody)
```

文化を配列に入れておいてループで回し、
ループの中では　文化を切り替えて使う。翻訳の文章は、別のところに表を持っておく。

```Shell
# コーヒーブレイク　－　東京弁←→大阪弁に切り替えできるか？
# 
# en は英語、US はアメリカ方言で、合わせて en-US になる。英国方言なら en-EN。
# これは 言語コード という。
# 日本は ja-JP しかない。 大阪弁を追加しようと ja-OS（日本（大阪）） を作ろうとしたら
# 言語コードは OS（コンピューター）への登録制になっていて、管理者権限 が求められたので止めた。
# だったら fr-FR（フランス） に大阪弁を詰め込めば 東京弁／大阪弁 切り替えも実現可能……だが、
# こういう本来意図しない使い方をすると　定時に誰も帰れないくそコード　になってしまう。
```

別の表は、以下のような形になっているとしよう。

Resources.ja-JP.resx

|Key|Value|
|---|---|
|ToolFailureSubject|ツール障害|
|ToolFailureBody|ファイルのロックに引っかかった。|

Resources.fr-FR.resx

|Key|Value|
|---|---|
|ToolFailureSubject|Défaillance de l'outil|
|ToolFailureBody|J'ai attrapé un verrou de fichier.|

Resources.resx

|Key|Value|
|---|---|
|ToolFailureSubject|Tool failure|
|ToolFailureBody|I caught on a file lock.|

ここで、
Resources.ja-JP.resx には日本向けの、
Resources.fr-FR.resx にはフランス向けの、
Resources.resx にはアメリカ向けの言葉が入っているとするぜ。

インド人はどこに行ったのかというと、英語でいいらしい。

とりあえず、　英語（米国）　を共通言語としておこう。 **Key** 列は　英語（米国）　で書く。
あとは **ja-JP** を指定すれば 日本語テーブルの **Value** 列が、
**fr-FR** を指定すれば フランス語テーブルの **Value** 列が出てくる仕組みと考えれば　すっきりしてるだろ。

**en-US** 、これは英語（米国）だが、を指定すれば　デフォルトのテーブルが出てくると思えだぜ。

で、あとから言語を増やしたり減らしたりしたければ、 Resources.zh-CN.resx ファイルを追加したり、
Resources.fr-FR.resx ファイルを消したりすれば、文化を書いていた配列を修正する程度の手間で
対応でき……るという夢を　ひとまず見ておこう。

## エディターを見つけよう

Visual Studio 2017 という開発統合環境（IDE）があるが、機能拡張しなければ　便利ではない。
例えば [ResXManager](https://marketplace.visualstudio.com/items?itemName=TomEnglert.ResXManager) といった感じのフリーソフトをインストールするとしよう。すると、

|Key|en-US|ja-JP|fr-FR|
|---|---|---|---|
|ToolFailureSubject|Tool failure|ツール障害|Défaillance de l'outil|
|ToolFailureBody|I caught on a file lock.|ファイルのロックに引っかかった。|J'ai attrapé un verrou de fichier.|

横一列に 文化 が並び、翻訳作業、あるいは　抜けチェック　が楽になるかもしれない。
モデル／ビュー／コントローラー　という言葉を聞いたことがあるなら、これは　ビュー　に当たる。
しかも、 [Translation] ボタンを押すと グーグル翻訳 と連携して １列を埋めてくれるかもしれない。

「だろう」とか「しれない」というのは　サーバーからアクセス制限を食らったりといった　実践問題　が
いろいろ　あるから　期待を下げているわけだが、「ベタ書き」よりは　はるかにマシになるだろう。

次に、ソースコードを改造している間に、ソースコードで使っていないリソース　が出てきて、
不要だから Resources.resx ファイルから消したい、ということも出てくるかもしれない。
自動でやってくれれば……、と思うものの Visual Studio 2017 に期待しすぎてはいけない。
NuGet とか Marketplace とか、プラグインをダウンロードする仕組みを使って
欲しいものは自分で探して勝手に導入するのが　流行りだぜ。

例えば [RESX-Unused-Finder](https://github.com/Microsoft/RESX-Unused-Finder) を使って、使っていないリソースは消してしまおう。
ぼーっとしているときに　ファイルパスを間違えて　どこかで何かが消えているヒューマンミスも起こりえるが、
人気が出れば　誰かが　GUI　を改善してくれるだろう。

これで多言語対応は　ばっちりだ……、と思うと　そうでもない。フリーソフトは　バグを避けながら使うものだぜ。
便利ではない Visual Studio 2017 にデフォルトで付いている「リソース」とかいうエディターを使う場面が出てくる。
それが **Keyの変更** だぜ。

Resources.resx ファイルを　フリーソフトでいじっても、C# のソースコードの方はリファクタリング（プロパティ名の変更）してくれない。
理屈は分かる。
.resx とかいう XML ファイルをいじるのは簡単でも、C# のソースコードのリファクタリングまでやってくれるかというと、
その作業は 全く別物なのかもしれない。（あるいは　欲しければ自分で作らなければならない）

ところで　C# のソースを貼っておく。

```C#
using System;

// ↓この角括弧が何やってるか分けわかんなければ消してくれだぜ。
[assembly: CLSCompliant(true)]
namespace Grayscale.ResourcePracticeWithConsole
{
    using System.Diagnostics;
    using System.Globalization;
    using System.Threading;

    // ↓リソースファイルを使おうとしている。
    // リソースファイルの作り方は Qiita で別の人が書いてるだろ、勝手に調べろだぜ。
    using Grayscale.ResourcePracticeWithConsole.Properties;

    /// <summary>
    /// プログラム。
    /// </summary>
    public static class Program
    {
        /// <summary>
        /// エントリーポイント。
        /// </summary>
        public static void Main()
        {
            // 使うリソースを 英語（米国）を使う。
            Resources.Culture = new CultureInfo("en-US");

            // ↓C#デフォルトのエラーメッセージや、画面に効いてくる。
            Thread.CurrentThread.CurrentCulture = Resources.Culture;
            Thread.CurrentThread.CurrentUICulture = Resources.Culture;

            // ↓英語（米国）テーブルの HelloWorld キーに対応した値が表示される。
            Trace.WriteLine(Resources.HelloWorld);

            // 使うリソースを 日本 に変える。
            Resources.Culture = new CultureInfo("ja-JP");

            // ↓日本テーブルの HelloWorld キーに対応した値が表示される。
            Trace.WriteLine(Resources.HelloWorld);

            // 15秒待つ。
            Thread.Sleep(15000);
        }
    }
}
```

だいたいの **基本** を紹介した。

変えたカルチャーを元に戻すために　Old 変数に古いのを一時退避しておいて後で使う　スワップ　とか使いこなして、
ログは英語、通知メールは自分の言語に切り替えるとか、そういうのやり始めるのは **応用** なんで説明しない。

データベースに 通知メール受け取りユーザーや、そのユーザーの言語を覚えさせといて
ユーザーの追加に対応するとか **発展** なんで、要件を満たした後で　勝手に遊べだぜ。

じゃあ、

* 「リソース」エディターの中では [Shift]+[Enter] キーで改行できるが、他のエディターではどうやって改行を入れるのか？
* 「&amp;#39;」って何よ？
* 「&amp;amp;gt;」って何よ？

など、いろいろな　実践問題が出てくるので　１つ１つ　解決していって欲しい。
多分、フリーソフトの人気が上がって　利用者が儲かれば　コミュニティの誰かが直してくれると思うが……。

## 年月日時分

また、 `12, FEB` と書いてあっても　わたしには　何かわからないし、 `2019/02/12` と書いていても　何月か分からないし、
データベースに 年月日を 20190212 と 8桁で入れている日本企業があるとしても　何月か分からない。

日付の **書式** をハードコーディングしていては　定時には帰れないだろう。
そんなときにも CultureInfo を使う。

```C#
	// 日付。
	var dateValue = new DateTime(2019, 2, 16, 13, 57, 0);
	var cultures = new[]
	{
		new CultureInfo("ja-JP"),
		new CultureInfo("en-US"),
		new CultureInfo("fr-FR"),
		new CultureInfo("de-DE")
	};

	foreach (var culture in cultures)
	{
		Trace.WriteLine(string.Format(
			"{0}: {1}",
			culture.Name,
			dateValue.ToString(culture)));
	}
```

書式はハードコーディングせず、文化を指定すれば 勝手に変換されるように書いておく。

```Plain
ja-JP: 2019/02/16 13:57:00
en-US: 2/16/2019 1:57:00 PM
fr-FR: 16/02/2019 13:57:00
de-DE: 16.02.2019 13:57:00
```

参考。

* [system.iformatprovider](https://docs.microsoft.com/ja-jp/dotnet/api/system.iformatprovider?view=netframework-4.7.2)

しかし、見た目だけ対応していても、日本はアメリカより９時間早い。
時差に対応する必要がある。　これも　ハードコーディングしていては　定時には帰れないだろう。

そこで、日本時間を　いったん世界標準時に戻して、そこから　タイムゾーンを指定して　ずらす。
夏時間とか、　アメリカといっても東部と西部のどっちなのかとか、いっさい考えない。

```C#
	var universalDate = DateTimeOffset.Now.ToUniversalTime();

	// System.ValueTuple
	(CultureInfo CultureInfo, string TimeZone)[] cultures =
	{
		(new CultureInfo("ja-JP"), "Tokyo Standard Time"),
		(new CultureInfo("en-US"), "GMT Standard Time"),
		(new CultureInfo("fr-FR"), "Romance Standard Time"),
		(new CultureInfo("de-DE"), "Romance Standard Time"),
	};

	foreach (var culture in cultures)
	{
		Trace.WriteLine(string.Format(
			"{0}: {1} ({2})",
			culture.CultureInfo.Name,
			TimeZoneInfo.ConvertTime(
				universalDate,
				TimeZoneInfo.FindSystemTimeZoneById(culture.TimeZone)).ToString(culture.CultureInfo),
				culture.TimeZone));
	}
```

その結果、

```Plain
ja-JP: 2019/02/16 14:48:15 +09:00 (Tokyo Standard Time)
en-US: 2/16/2019 5:48:15 AM +00:00 (GMT Standard Time)
fr-FR: 16/02/2019 06:48:15 +01:00 (Romance Standard Time)
de-DE: 16.02.2019 06:48:15 +01:00 (Romance Standard Time)
```

と時差も考慮してくれる。
アメリカの東なのか西なのかは　聞いておくといいのかもしれないが、めんどくさいんで　イギリスのタイムゾーンにしている。

+09:00 というのは、グリニッジ天文台の時計から＋９時間ですよ、という意味なので、９を引けば　グリニッジ標準時に戻せる。
日本で　朝８時にメールが届く、ということは　グリニッジから見れば　昨日の２３時　にメールが届いている。
フランスから見れば　０時に届いていると分かる。　これで時差は　だいたい配慮できた。

／／　ヒマがあれば　もっと書く。