Visual Studio 2017 を使って C# のプログラムを多言語対応させようぜ
日本だけで使うプログラムなら、多言語対応の書き方は 一切 目にすることもないと思う。
見なくていいものは目に触れさせない、
そこらへん、Visual Studio / C#、 はたまた オブジェクト指向プログラミング というやつ は
よく出来てある……。
状況設定
ここは東京。 フランス人と インド人と 日本人が 働いているとするぜ。
私の書いたプログラムが 運悪く ファイル書き込み時に ファイルのロックに引っかかって 何度リトライしてもダメ、
諦めて タイムアウトさせて 異常終了することにするとしよう。
SendMail の機能を使って フランスチーム、インドチーム、日本チームに 異常通知メールを飛ばすという状況を想像してくれだぜ。
### 疑似コード
### 件名と本文。グーグル翻訳でいいだろ……、情報量の少ない日本語の直訳は、きっとおかしなことになっている。
SendMail("ツール障害", "ファイルのロックに引っかかった。")
SendMail("Défaillance de l'outil", "J'ai attrapé un verrou de fichier.")
SendMail("Tool failure", "I caught on a file lock.")
こんな感じで ベタ書き すれば その場の用は足りるが、何と言ったらいいのだろう。
ソースコードが 1文字 でも増えるのは労力だ。
定時に帰りたいマンは こんなことはしない。
というのも、テキスト は人類にとって便利だが、 テキストを扱う のはきっと人類が得意なものではない……。
そこそこ解決しよう
例えば、以下のようにする。
### 疑似コード
foreach culture in ["ja-JP(日本)", "fr-FR(フランス)", "en-US(インド)"]
Resources.Culture = culture
SendMail(Resources.ToolFailureSubject, Resources.ToolFailureBody)
文化を配列に入れておいてループで回し、
ループの中では 文化を切り替えて使う。翻訳の文章は、別のところに表を持っておく。
# コーヒーブレイク - 東京弁←→大阪弁に切り替えできるか?
#
# 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 といった感じのフリーソフトをインストールするとしよう。すると、
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] ボタンを押すと グーグル翻訳 と連携して 1列を埋めてくれるかもしれない。
「だろう」とか「しれない」というのは サーバーからアクセス制限を食らったりといった 実践問題 が
いろいろ あるから 期待を下げているわけだが、「ベタ書き」よりは はるかにマシになるだろう。
次に、ソースコードを改造している間に、ソースコードで使っていないリソース が出てきて、
不要だから Resources.resx ファイルから消したい、ということも出てくるかもしれない。
自動でやってくれれば……、と思うものの Visual Studio 2017 に期待しすぎてはいけない。
NuGet とか Marketplace とか、プラグインをダウンロードする仕組みを使って
欲しいものは自分で探して勝手に導入するのが 流行りだぜ。
例えば RESX-Unused-Finder を使って、使っていないリソースは消してしまおう。
ぼーっとしているときに ファイルパスを間違えて どこかで何かが消えているヒューマンミスも起こりえるが、
人気が出れば 誰かが GUI を改善してくれるだろう。
これで多言語対応は ばっちりだ……、と思うと そうでもない。フリーソフトは バグを避けながら使うものだぜ。
便利ではない Visual Studio 2017 にデフォルトで付いている「リソース」とかいうエディターを使う場面が出てくる。
それが Keyの変更 だぜ。
Resources.resx ファイルを フリーソフトでいじっても、C# のソースコードの方はリファクタリング(プロパティ名の変更)してくれない。
理屈は分かる。
.resx とかいう XML ファイルをいじるのは簡単でも、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] キーで改行できるが、他のエディターではどうやって改行を入れるのか?
- 「'」って何よ?
- 「&gt;」って何よ?
など、いろいろな 実践問題が出てくるので 1つ1つ 解決していって欲しい。
多分、フリーソフトの人気が上がって 利用者が儲かれば コミュニティの誰かが直してくれると思うが……。
年月日時分
また、 12, FEB
と書いてあっても わたしには 何かわからないし、 2019/02/12
と書いていても 何月か分からないし、
データベースに 年月日を 20190212 と 8桁で入れている日本企業があるとしても 何月か分からない。
日付の 書式 をハードコーディングしていては 定時には帰れないだろう。
そんなときにも CultureInfo を使う。
// 日付。
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)));
}
書式はハードコーディングせず、文化を指定すれば 勝手に変換されるように書いておく。
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
参考。
しかし、見た目だけ対応していても、日本はアメリカより9時間早い。
時差に対応する必要がある。 これも ハードコーディングしていては 定時には帰れないだろう。
そこで、日本時間を いったん世界標準時に戻して、そこから タイムゾーンを指定して ずらす。
夏時間とか、 アメリカといっても東部と西部のどっちなのかとか、いっさい考えない。
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));
}
その結果、
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 というのは、グリニッジ天文台の時計から+9時間ですよ、という意味なので、9を引けば グリニッジ標準時に戻せる。
日本で 朝8時にメールが届く、ということは グリニッジから見れば 昨日の23時 にメールが届いている。
フランスから見れば 0時に届いていると分かる。 これで時差は だいたい配慮できた。
// ヒマがあれば もっと書く。