Skip to content

MS_InternationalizationItems

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

国際化対応項目

関連

本コンテンツに関連するコンテンツ

概要

国際化対応とは

アプリケーションの国際化対応とは

  • 多言語化
  • 地域化

を行い実行環境、ユーザによってアプリケーションの動作を切り替えることである。

以下サイトでは

「あるソフトウェアが、複数の、言語、文字、文化的慣習を混在させて扱うことができるようにすることである。」

と表現されている。

補足(i18n / L10n / g11n の区別): 国際化の文脈では
3 つの語が使い分けられるので整理しておく。

略記 意味 誰がやるか
i18n Internationalization(国際化) 切り替えられる作りにする 開発者
L10n Localization(地域化) 各言語のリソースを用意する 翻訳者・現地担当
g11n Globalization 上記の総称
i18n(国際化)… ハードコードされた文言をリソースに追い出す
                  書式を CurrentCulture に委ねる
                  → 一度やれば、言語が増えても作業は増えない

L10n(地域化)… ja.resx, en.resx を用意する
                  → 言語の数だけ作業が増える

i18n を後からやるのは非常に高くつく
(全画面・全メッセージを洗い出す必要がある)ため、
「今は日本語だけ」でも、将来の可能性があるなら
最初からリソース化しておく
、というのが実務上の判断になる。

国際化対応の要件

要件の例を以下に示す。

  • パッケージ・ソフトを多言語対応しておき、
    適用先次第でアプリケーション全体の言語を切り替える。

対象

  • 言語

    • クライアント側

      • ユーザが UI 表示やログ出力されるメッセージなどの言語を選択できる。
      • 動作環境(OS の言語、ブラウザ設定など)次第で表示言語が自動的に切り替わる。
    • サーバ側

      • 管理者が UI 表示やログ出力されるメッセージなどの言語を選択できる。
      • 動作環境(OS の言語、ブラウザ設定など)次第で表示言語が自動的に切り替わる。
  • UI 要素

    • 文字幅等が変わるので、コントロールのサイズ・配置が変更される。
    • アラビア語環境では文字方向を右から左になるため変更量が大きい。

切り替え方式

  • クライアント側

    • クライアント側ユーザの設定で切り替わる。
    • 動作環境(OS の言語、ブラウザ設定など)次第で表示言語が自動的に切り替わる。
  • サーバ側

    • サーバ側のユーザの設定で切り替わる(サーバ側のログ出力等)。
    • クライアント側のユーザの設定で切り替わる(クライアント側に出力される文字列等)。
    • 動作環境(OS の言語、ブラウザ設定など)次第で表示言語が自動的に切り替わる。

補足(ログの言語は固定する): 「サーバ側のログ出力」の言語を
クライアントに合わせないという点が、後述の
ベターユースでも強調されている重要な指針である。

ログをクライアントのカルチャで出力すると
  ・同じ障害が言語ごとに別の文言で記録される
  ・grep / 集計 / アラート ルールが機能しない
  ・調査する運用者が読めない言語で記録される

ログは「英語または運用者の言語」に固定するか、
さらに進めて **構造化ログ(メッセージ テンプレート + パラメータ)**に
するのが現在の定石である。

// 文言ではなくテンプレートとして記録される(Serilog / ILogger)
_logger.LogWarning("Order {OrderId} rejected: {Reason}", id, reason);

国際化対象

本項では国際化対象となる項目を示す。

それぞれのアプリケーションの要件に
合わせて対応する項目を取捨選択する。

項目の選択は以下を参考とした。

文言

文言を外部リソースファイル等に定義し文言の
メンテナンス、言語の増減を行いやすくする。

以下に文言の国際化対象項目を整理した。

UI 要素

UI 要素には以下の様な項目がある(クライアント側)。

  • 画面タイトル
  • ラベル、ボタン、ラジオ・ボタン、チェック・ボックスなどのテキスト
  • テキスト・ボックス中の説明文、紹介文などの文章
  • ドロップダウン、リスト・ボックスの選択候補
  • グリッド(表)のヘッダ、値

※ 初期値として静的に用意する文言、動的に置き換える文言がある。

メッセージ

メッセージには以下の様な項目がある。

  • 画面表示

    • 確認メッセージ
    • エラー・メッセージ
    • ツール・チップ
  • ログ出力等

    • 情報メッセージ
    • ワーニング・メッセージ
    • エラー・メッセージ

クライアント、サーバでの切り替え方式については別途検討する必要がある。

  • 可変文字列の組込
    また、可変文字列の組み込みには、順番の逆転を考慮して、
    予め String.Format を使用しておくと良い。
string msg = "アセンブリ名:[{0}]に、クラス名:[{1}]が存在しません。";string msg = "Class name: [{1}] does not exist in assembly name: [{0}].";

throw new ArgumentException(
  String.Format(msg, assemblyName, className));

補足(この指摘は国際化の要点そのもの): 原文が示す
**「{0}{1} の順序が言語によって逆転する」**という例は、
文字列連結による組み立てが破綻する理由を端的に表している。

// ✗ 連結:語順を変えられない
throw new Exception("アセンブリ名:[" + asm + "]に、クラス名:[" + cls + "]が存在しません。");

// ✗ 文字列補間:一見きれいだが、リソース化できない
throw new Exception($"アセンブリ名:[{asm}]に、クラス名:[{cls}]が存在しません。");

// ○ 書式文字列をリソースに置き、値だけを渡す
throw new Exception(string.Format(Resources.Errors.ClassNotFound, asm, cls));

**さらに難しいのが「複数形」と「性別」**である。

英語: 1 item / 2 items          (2 通り)
日本語: 1 個 / 2 個              (変化しない)
ロシア語: 1 файл / 2 файла / 5 файлов (3 通り)
アラビア語: 6 通り

string.Format だけでは表現できないため、
ICU MessageFormat 系の仕組みが必要になる。

{count, plural, one {# item} other {# items}}
環境 手段
.NET MessageFormat.NET、または言語ごとにキーを分ける
クライアント(JS) i18nextIntl.PluralRules

日本語のみを対象とするなら不要だが、
欧州言語・ロシア語・アラビア語を含める場合は必須になる。

書式

言語、地域により数値、日時の表現を切り替える。

数値書式

国により数値の小数点の文字、桁区切りの文字・区切る桁数が異なる。

表示する際に数値データの書式(フォーマット指定)を切り替える。

  • 日本の場合

    • 小数点文字:ピリオド
    • 区切り文字:カンマ
    • 区切り桁:3 桁または 4 桁
  • イギリス、アメリカの場合

    • 小数点文字:ピリオドまたはミドルドット
    • 区切り文字:カンマ
    • 区切り桁:3 桁
  • フランス、南アフリカの場合

    • 小数点文字:カンマ
    • 区切り文字:半角スペース
    • 区切り桁:3 桁
  • ドイツの場合

    • 小数点文字:カンマ
    • 区切り文字:ピリオド
    • 区切り桁:3 桁

なお、小数点以下の桁区切りの有・無は、国毎ではなく業務毎の仕様である模様。

補足(最も危険なのは「解析」の側): 表示(ToString)よりも、
入力の解析(Parse)の方が事故になりやすい

// de-DE 環境で実行すると…
decimal.Parse("1.234")     // → 1234(ピリオドが桁区切りと解釈される)
decimal.Parse("1,234")     // → 1.234

CSV やファイルの読み書きでこれが起きると、
値が 1000 倍ずれたまま処理が通ってしまう
(例外にならない)。

原則カルチャ の補足と同じ):

// ファイル・DB・API:必ず InvariantCulture
var v = decimal.Parse(s, CultureInfo.InvariantCulture);
var s = v.ToString(CultureInfo.InvariantCulture);

// ユーザー入力:CurrentCulture で、かつ TryParse
if (!decimal.TryParse(input, NumberStyles.Number,
                      CultureInfo.CurrentCulture, out var value))
    return BadRequest();

Parse / ToString を引数なしで呼んでいる箇所は、
国際化対応の際にすべて洗い出す必要がある。
アナライザー(CA1305: IFormatProvider を指定します)を
有効にすると機械的に検出できる。

日時

UTC と時差

最近の言語では Datetime 型を使用し、内部的には UTC(協定世界時)でデータを保持、
タイムゾーン(日付と時刻)に合わせて、時差、サマータイムを算出して表示を行う。

  • サマータイム開始・終了日の動作には注意が必要
    サマータイムによって重複する時間、無くなる時間

    • 条件(タイムゾーンの設定):

      • [タイムゾーン]コンボ・ボックス → (GMT-06:00)中部標準時(米国およびカナダ)
      • [自動的に夏時間の調整をする]チェック・ボックス → チェック
    • DateTime.Now の時間、システム時計の動作

      • サマータイムの開始日
        2013/3/10 01:59:59
        IsDaylightSavingTime=False

      • 1 秒進むと
        2013/3/10 03:00:00
        IsDaylightSavingTime=True
        ※ 1 時間進む。

      • サマータイムの終了日
        2013/11/3 01:59:59
        IsDaylightSavingTime=True

      • 1 秒進むと
        2013/11/3 01:00:00
        IsDaylightSavingTime=False
        ※ 1 時間戻って 2 回目の 1 時が始まる。

    • DateTime.UtcNow の時間

      • サマータイムの開始日
        2013/3/10 07:59:59
        IsDaylightSavingTime=False

      • 1 秒進むと
        2013/3/10 08:00:00
        IsDaylightSavingTime=True
        ※ サマータイムは影響しない

      • サマータイムの終了日
        2013/11/3 06:59:59
        IsDaylightSavingTime=True

      • 1 秒進むと
        2013/11/3 07:00:00
        IsDaylightSavingTime=False
        ※ サマータイムは影響しない

補足(この検証結果が示す設計指針): 原文の実測は
**「ローカル時刻は連続しない」**という決定的な事実を示している。

【春(開始)】01:59:59 → 03:00:00
    → 02:00〜02:59 は「存在しない時刻」
    → その時刻を DateTime で作ると、不正な値になる

【秋(終了)】01:59:59 → 01:00:00
    → 01:00〜01:59 が「2 回ある」
    → ログの時刻順が逆転する/バッチが 2 回動く

設計上の結論:

用途 使うもの
保存・比較・計算 UTCDateTime.UtcNow / DateTimeOffset
表示 ローカル時刻に変換
スケジュール(「毎日 2:30 に実行」) UTC で持つか、存在しない時刻を避ける
// 現在の推奨は DateTimeOffset(オフセットを保持する)
DateTimeOffset now = DateTimeOffset.UtcNow;
var local = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time"));

// 存在しない/曖昧な時刻の判定
var tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
tz.IsInvalidTime(dt);    // 春:存在しない時刻か
tz.IsAmbiguousTime(dt);  // 秋:2 回ある時刻か

DateTime より DateTimeOffset を使うのが現在の指針である
DateTimeKind(Utc/Local/Unspecified)を持つが、
Unspecified のまま渡り歩いて事故になることが多い)。

タイム ゾーン ID の互換性にも注意が要る。

Windows Linux / macOS
ID "Tokyo Standard Time" "Asia/Tokyo"(IANA)
.NET 6 以降 両方を受け付ける(相互変換される) 同左

.NET 6 より前はプラットフォームごとに ID を分岐する必要があった。
現在は TimeZoneInfo.FindSystemTimeZoneById が双方を解決する
(ただし ICU が必要。カルチャ の補足を参照)。

西暦

西暦の表記の仕方

世界標準規格として、ISO 8601 の "YYYY-MM-DD" 方式が確立している。

  • 日本では通常、年月日の順番に算用数字で表記するが、

    • 2013年04月05日
    • 2013/04/05
  • 順番の異なる国があるため表記を切り替える必要がある。

    • 月日年(米国)
    • 日月年(ロシア、オーストラリアなど多数)
  • また、順番だけでなく

April 05 2013

の様に数字以外での表記方法もある。
(日が 01-12 の範囲内の場合、月・日の区別が付き難いため)

  • Datetime.ToString() の既定の書式は「カルチャ」(CultureInfo)の
    影響を受ける。
DateTime dateTime = new DateTime(2000, 1, 23, 4, 5, 6);
// デフォルトのカルチャ(ja-JP)で日時を文字列化
Console.WriteLine("{0}: {1}", CultureInfo.CurrentCulture, dateTime.ToString());
// en-US(英語/アメリカ合衆国)のカルチャで日時を文字列化
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine("{0}: {1}", CultureInfo.CurrentCulture, dateTime.ToString());
// de-DE(ドイツ語/ドイツ)のカルチャで日時を文字列化
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");
Console.WriteLine("{0}: {1}", CultureInfo.CurrentCulture, dateTime.ToString());出力

ja-JP: 2000/01/23 4:05:06
en-US: 1/23/2000 4:05:06 AM
de-DE: 23.01.2000 04:05:06

補足(ISO 8601 を使う): 原文が冒頭で挙げている
ISO 8601 が、機械可読な日時表現の唯一の正解である。

// ラウンドトリップ書式("O"):ISO 8601 準拠。パースも確実
var s = DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture);
//  → 2026-08-19T05:06:07.8901234+00:00

var v = DateTimeOffset.Parse(s, CultureInfo.InvariantCulture,
                             DateTimeStyles.RoundtripKind);
用途 書式
API・ファイル・ログ "O"(ISO 8601)
ファイル名 "yyyyMMdd_HHmmss"
表示 ToString("d") / ("D") 等の標準書式

表示にはカスタム書式("yyyy/MM/dd")を使わない
のが国際化の観点では正しい。
標準書式指定子を使えば、カルチャに応じて自動で切り替わる。

dt.ToString("d")    // ja-JP: 2026/08/19  en-US: 8/19/2026
dt.ToString("D")    // ja-JP: 2026年8月19日  en-US: Tuesday, August 19, 2026

各国独自の暦

  • 日本の和暦に相当する暦に以下のものがある。

    • 仏暦
    • 中華民国暦
    • 台湾暦
    • 韓国暦
    • ヘブライ暦
    • 回教暦
    • .etc
  • 西暦から別の暦に変換するには各種暦(カレンダー)を使用する。
    これについては、Calendar クラスの派生クラスで確認できる。

    • Calendar クラス (System.Globalization)
      https://learn.microsoft.com/ja-jp/dotnet/api/system.globalization.calendar

      • System.Globalization.EastAsianLunisolarCalendar
        時間が月、日、年、および時代 (年号) に分割され、
        その日付が太陽と月の周期に基づいている暦を表します。

      • System.Globalization.GregorianCalendar
        グレゴリオ暦を表します。

      • System.Globalization.HebrewCalendar
        ヘブライ暦を表します。

      • System.Globalization.HijriCalendar
        回教暦を表します。

      • System.Globalization.JapaneseCalendar
        和暦を表します。

      • System.Globalization.JulianCalendar
        ユリウス暦を表します。

      • System.Globalization.KoreanCalendar
        韓国暦を表します。

      • System.Globalization.PersianCalendar
        ペルシャ暦を表します。

      • System.Globalization.TaiwanCalendar
        台湾暦を表します。

      • System.Globalization.ThaiBuddhistCalendar
        タイ仏暦を表します。

      • System.Globalization.UmAlQuraCalendar
        サウジ回教暦 (Umm-Al Qurah) を表します。

和暦

各国独自の暦の1つで、仕組み上、コレらと同列に扱える。

  • 変換処理
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();

DateTime target = new DateTime(2003, 7, 1);
string result = target.ToString("ggyy年M月d日", culture);
Console.WriteLine(result);
// 出力:平成15年7月1日
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();

string target = "平成15年7月1日";
DateTime result = DateTime.ParseExact(target, "ggyy年M月d日", culture);
Console.WriteLine(result.ToLongDateString());
// 出力:2003年7月1日

補足(ja-JP の既定カレンダーは西暦): new CultureInfo("ja-JP")
既定はグレゴリオ暦であり、
原文のように DateTimeFormat.Calendar を差し替えるか、
ja-JP-u-ca-japanese を使う必要がある。

// 現在はこちらが簡潔(.NET Core 3.0 以降で安定)
var culture = new CultureInfo("ja-JP-u-ca-japanese");
Console.WriteLine(new DateTime(2026, 8, 19).ToString("ggy年M月d日", culture));
//  → 令和8年8月19日

元年の表記にも注意が要る。

.NET Framework 4.8 / .NET Core 3.0 以降
  → 改元の年は「元年」と表示される("1年" ではない)
  → 解析側も「元年」を受け付ける

元年表記が不要な場合DateTimeFormatInfo を調整するか、
自前で置換する。

新元号(令和)改元対応

  • 和暦(JapaneseCalendar)を使用していれば、対応は少なくて済む。

  • 一点だけ、以下のような動作変更があるので必要に応じて対応が必要になる。

    • 動作の変更
      「昭和 65 年」などの表記は、法令上無効なものではなく、
      本来は例外をスローすることなく処理されるべきものとして、
      今回の新元号対応によって、上記のような場合も、
      例外がスローされることなく処理されるようになる。

    • 対応方法
      今回の新元号対応後も、これまでと同様に「昭和 65 年」のような
      表記を例外として処理するためには、以下の対策が必要になる。

      • .NET Framework 4.6 以降をターゲットとしているアプリ
        構成ファイル (App.Config / Web.Config) に、以下を設定する。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.Globalization.EnforceJapaneseEraYearRanges=true" />
  </runtime>
</configuration>
- .NET Framework 4.5.2 以前をターゲットとしているアプリ\
  以下のようにレジストリ値を変更する。
・キー
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AppContext
・名前
Switch.System.Globalization.EnforceJapaneseEraYearRanges
・種類
REG_SZ
・データ
1

移行メモ(レジストリ パスの表記): 原文の
HKEY_LOCALMACHINE\SOFTWARE\Microsoft.NETFramework\AppContext
区切りが欠落しているため、
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AppContext に修正した。

補足(次の改元に向けて): 令和改元で得られた教訓を
整理しておくと、次回の備えになる。

【改元で壊れる箇所】
  ・和暦を「文字列」で持っている("H31" 等)
  ・元号の一覧をコードにハードコードしている
  ・年号 1 文字目で判定している("平" / "令" が同じ "R" 等に化ける)
  ・「昭和65年」のような範囲外の年を許容していた/していなかった
  ・帳票のレイアウトが元号 1 文字前提("令和" は 2 文字)

設計上の指針:

指針 内容
内部は西暦(できれば UTC)で持つ 和暦は表示時に変換するだけ
元号の一覧を自前で持たない JapaneseCalendar に委ねる
実行環境を更新可能にしておく 改元は OS / ランタイムの更新で対応される
帳票の桁数に余裕を持たせる 元号は 1〜2 文字(+ アルファベット表記)

「実行環境が新しければ、再ビルド不要で対応できる」
(後述の参考にある通り、Win 7 + .NET 4 以降が条件)という
性質を保つには、自前で元号テーブルを持たないことが決定的である。

単位

国、地域により単位の表現を切り替える。

(1)通貨

  • 現地の通貨、またはいずれかの通貨に統一し表現する。
  • 通貨の書式指定子(C または c)は「カルチャ」(CultureInfo)の影響を受ける。
decimal moneyvalue = 1921.39m;
// デフォルトのカルチャ(ja-JP)で文字列化
Console.WriteLine(String.Format("Order Total: {0:C}", moneyvalue));
// en-US(英語/アメリカ合衆国)のカルチャで文字列化
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine(String.Format("Order Total: {0:C}", moneyvalue));
// de-DE(ドイツ語/ドイツ)のカルチャで文字列化
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");
Console.WriteLine(String.Format("Order Total: {0:C}", moneyvalue));出力

Order Total: \1,921
Order Total: $1,921.39
Order Total: 1.921,39

補足(通貨書式は「換算しない」): この例が示す最も重要な事実は、
{0:C} は記号を変えるだけで、金額は換算しないという点である。

1921.39 という値が
  ja-JP → ¥1,921    (小数が丸められる)
  en-US → $1,921.39
  de-DE → 1.921,39 €

  → 「1921 円」が「1921 ドル」に化ける

これは表示の事故ではなく、金額の意味が変わる重大な事故である。

原則:

// ✗ CurrentCulture 任せ
total.ToString("C");

// ○ 通貨は値と一緒に持ち、その通貨のカルチャで書式化する
var jp = new CultureInfo("ja-JP");
total.ToString("C", jp);      // 常に円として表示

ASP.NET Web Formsの国際化対応 で引用されている
**「Web オークション サイトでは UI カルチャは変わるが
カルチャ自体は変更されない」**という MSDN の記述は、
まさにこの問題を指している。

金額を扱う場合は「通貨コード(JPY / USD)を値と一緒に持つ」
のが正しい設計である。

(2)計測単位

  • 現地の計測単位、またはいずれかの計測単位に統一し表現する。

  • 最も使われているのはメートル法とヤード ポンド法。

  • Windows ストアアプリでは、
    Globalization 名前空間を使って正しいシステム計測を取得できる。

¥記号問題

(1)日本の円、中国の人民元はどちらも ¥ 記号を使う

(2)¥ はバックスラッシュやウォンに置き換わる

各国語用のフォントのうち U+005C の文字は、それぞれカスタマイズされている。
そのため使用するフォント次第で別の記号に置き換わる。

補足(U+005C 問題の実務的な影響): これは
文字コード とも関わる、日本特有の根深い問題である。

U+005C  REVERSE SOLIDUS(バックスラッシュ)
  → 日本語フォントでは「¥」として描画される
  → 韓国語フォントでは「₩」(ウォン)として描画される

U+00A5  YEN SIGN(本来の円記号)
U+FFE5  FULLWIDTH YEN SIGN(全角の¥)

同じバイト列が、フォントによって別の文字に見える

実務での影響:

場面 症状
Windows のパス C:\tempC:¥temp に見える(同じ文字)
プログラムのエスケープ "\n""¥n" に見える
通貨表示 フォント次第で「¥」が「\」に見える
Shift_JIS ↔ Unicode 変換 0x5C の扱いで文字化けや不正な変換が起きる

対処:

  • 通貨記号としては U+00A5 または U+FFE5 を使う(U+005C を使わない)
  • 等幅フォントの選定を意識する(開発時は Cascadia Code 等)
  • Shift_JIS からの変換では 0x5C の扱いを確認する

「ソースを Shift_JIS から UTF-8 に変換したらパスが壊れた」
という事故は、この問題に起因することが多い。

UI 要素

Windows Forms であれば、
リソースファイル」を用いることで、

下記、UI 要素の

  • 文言(前述)
  • サイズ
  • 配置順

を切り替えることができる。

サイズ

表示する文言の長さにより、画面項目のサイズを調整する。

配置順

言語、地域により文字の方向性を切り替える。
# アラビア語では文字方向が右から左

  • 左から右へ、右から左へ、左に向かって上から下
  • コントロールの配置順、表の列順

補足(RTL 対応は Web の方が容易): 原文は
Windows Forms の国際化対応 を前提に
「サイズと配置順を切り替える」と述べているが、
Web ではレイアウトの仕組み自体が対応している

<html dir="rtl" lang="ar">
/* 論理プロパティを使えば、LTR / RTL 双方で正しく効く */
.card { margin-inline-start: 1rem; padding-inline: 1rem; }
/*        ↑ margin-left ではなく inline-start        */
物理プロパティ 論理プロパティ
margin-left margin-inline-start
padding-right padding-inline-end
text-align: left text-align: start
border-left border-inline-start

論理プロパティを使っておけば、dir="rtl" を切り替えるだけで
レイアウトが反転する

Windows Forms でも RightToLeft / RightToLeftLayout プロパティが
あるが、Web の方が対応コストは低い

文言の長さの差(原文の「サイズ」)については、

日本語「保存」          →  英語 "Save"          (短い)
日本語「キャンセル」    →  ドイツ語 "Abbrechen"  (長い)
日本語「設定」          →  ロシア語 "Настройки"  (長い)

固定幅を避け、伸縮するレイアウト(Flexbox / Grid)にする
のが根本的な対処である
(経験則として 英語比 +30〜50% の余裕を見る)。

ロケール(地域と言語)の変更方法

本項では、Windows ロケール(地域と言語)の変更方法について説明する。
Windows ロケール(地域と言語)に対応するカルチャの変更方法については、

カルチャ」を参照。

地域と言語との関係

「コントロールパネル」の[時計、言語、および地域]
 →[地域と言語]「地域と言語」画面から変更する。

[形式]タブ

ユーザ・ロケール

  • 設定

    • [形式]コンボ・ボックス
      • 日付と時刻の表示形式を設定可能。
      • [追加の設定]で、数値と通貨の表示形式を設定可能。
  • 説明

    • ユーザ・ロケール、日付と時刻、数値や通貨の表示形式を設定する。
  • API

    • Win32:

      • GetDateFormat
      • GetCalendarInfo
      • GetTimeFormat
      • GetCurrencyFormat
      • GetNumberFormat
      • GetLocaleInfo
      • GetUserDefaultLangID
      • GetUserDefaultLCID
      • GetUserDefaultUILanguage
    • .NET: CultureInfo

      • CultureInfo.Calendar
      • CultureInfo.DateTimeFormat
      • System.DateTime(…, CultureInfo(LCID).Calendar).ToString
      • CultureInfo.NumberFormat

[場所]タブ

既定の場所

  • 設定

    • [現在の場所]コンボ・ボックス
  • 説明

    • GPS センサーなどを使用できない場合、
      パソコンを使用する既定の場所を指定する。
    • この情報をプログラムから使用できる。
  • API

    • Win32: GetUserGeoID
    • .NET: RegionInfo
    • COM: IDefaultLocation インターフェイス

[キーボードと言語]タブ(表示言語)

ダイアログや、メッセージの UI 表示言語

  • 設定

    • [表示言語]グループの[表示言語を選んで下さい]コンボ・ボックス
  • 説明

    • MUI(Multi User Interface)版の OS が必要になる。
    • 環境設定に対応した、ダイアログや、メッセージを表示する。

[キーボードと言語]タブ(入力ロケール)

入力ロケール

  • 設定

    • [キーボードの変更]ボタン → [テキスト サービスと入力言語]画面
  • 説明

    • ユーザが入力する際に使用するロケール
    • キーボードのレイアウトと、IME の言語判断に使用
  • API

    • .NET: CultureInfo.KeyboardLayoutId

移行メモ(見出しの重複): 原文では
「[キーボードと言語]タブ」という同名の見出しが 2 つ連続していたため、
内容に応じて**(表示言語)/(入力ロケール)**を補って区別した。
また「MUI(Multi User Inteface)」は
Multi User Interface の綴りの誤りと判断し修正した。

[管理]タブ

システム・ロケール(LCID)

  • 設定

    • [システム ロケールの変更]ボタン → [現在のシステムロケール]コンボ・ボックス
  • 説明

    • OS のロケール
    • メニューや画面の言語を変更する。
    • Unicode 対応されていない古いソフトで使用する。
  • API

    • Win32:

      • GetSystemDefaultLangID
      • GetSystemDefaultLCID
      • GetSystemDefaultUILanguage
    • .NET:CultureInfo.InstalledUICulture

補足(「システム ロケール」は非 Unicode アプリ用): 原文の
「Unicode 対応されていない古いソフトで使用する」という説明が要点で、
これは .NET アプリの表示言語とは無関係である。

システム ロケール(非 Unicode プログラムの言語)
  → ANSI コードページ([文字コード](MS_CharacterEncoding) の CP932 等)を決める
  → .NET アプリは内部が UTF-16 なので、原則として影響を受けない

ただし影響が出る場合がある
  ・Encoding.Default に依存したコード(.NET Framework)
  ・ネイティブ DLL との相互運用
  ・コンソールの出力コードページ

**Windows 10 以降では「ベータ: ワールドワイド言語サポートで
Unicode UTF-8 を使用」**というチェック ボックスが追加され、
システム ロケールを UTF-8(CP65001)にできる
ただし、古いアプリが壊れることがあるため、
業務端末での有効化は慎重に判断する。

参考

切り替え方式

大きく分けて、以下の切り替え方式がある。

  • システムの設定で動作が変更される方式

  • アプリケーションの設定で動作変更される方式

    • カルチャを変更する方式
    • カルチャ以外のパラメタを採用する方式

システムのアーキテクチャ毎の
カルチャを使い分けるポイントについて説明する。

スタンドアロン

  • システム設定をそのまま使用。
  • アプリケーション設定(1つの OS で多言語対応する必要がある場合)
    • カルチャを変更する方式
    • カルチャ以外のパラメタを採用する方式

クライアント・サーバ

  • クライアント

    • システム設定をそのまま使用。
    • アプリケーション設定(1つの OS で多言語対応する必要がある場合)
      • カルチャを変更する方式
      • カルチャ以外のパラメタを採用する方式
  • サーバ

    • システム設定をそのまま使用。

    • アプリケーション設定(config など)

      • カルチャを変更する方式
      • カルチャ以外のパラメタを採用する方式
    • クライアント側設定(HTTP ヘッダ、独自 cookie)

      • カルチャを変更する方式
      • カルチャ以外のパラメタを採用する方式

ベターユース

クライアント・サーバでは、考慮点が多く国際化が難しい。

ベターユースは以下の様に考える。

  • 各国から使用される国際化されたサーバ・アプリケーション

    • ロケール

      • クライアント・ロケールは可変
      • サーバ・ロケールは固定
    • UI 表示のリソースファイル(CurrentUICulture)

      • クライアント・ロケールで選択可能にする。
    • ログ・メッセージ等のリソースファイル(CurrentUICulture)

      • サーバ・ロケールに固定。
      • 例外メッセージ等も CurrentUICulture を使用する。
    • 書式などのフォーマット処理(CurrentCulture)
      以下の方式のうちから選択する。

      • クライアント・ロケールで選択可能にする。
      • アプリケーションで使用する単位は統一する(通貨、計測単位など)。
  • 各国にローカライズされるサーバ・アプリケーション

    • スタンドアロンと同じ。

補足(この整理は現在も有効): 原文の「ベターユース」は
実務的に妥当な整理であり、現在の設計指針としてもそのまま通用する。
補うとすれば、**「ログとユーザー向けメッセージを分ける」**という
実装上の作法である。

【問題】
  例外メッセージを CurrentUICulture で作ると、
  ユーザーの言語でログに残ってしまう

【現在の作法】
  ・例外には「識別できるコード + 英語の説明」を持たせる
  ・ユーザーに見せる文言は、UI 層で例外コードから引く
// ドメイン層:言語に依存しない
throw new BusinessException(ErrorCode.InsufficientStock, productId);

// UI 層:ここで初めてローカライズする
catch (BusinessException ex)
{
    ModelState.AddModelError("", _localizer[ex.Code.ToString(), ex.Args]);
}

例外の中で文言を作らない(コードとパラメータだけを持つ)ことで、

  • ログは常に一定(集計・アラートが機能する)
  • 表示はユーザーの言語
  • テストで文言に依存しない

という 3 つが同時に成立する。
DI / 依存性反転原則
観点でも、ドメイン層が IStringLocalizer に依存しないという
望ましい形になる。

業務系システムの国際化要件

  • 基本ソフト
  • Office ソフト
  • ビジネス・ソフト
  • ユーティリティ・ツール

などの場合は .NET Framework の国際化対応機能で十分であるが、
業務アプリケーションの場合は不十分である場合が多い。

事前に以下の業務システムにおける国際化の要件を確認しておくと良い。

ラベル等のサイズ

言語によって表示するラベル等のサイズの変更が必要か?不要か?
英語とマルチバイトは表示される文字列のサイズの差が大きい。

  • 不要の(英語表示が間延びしたように表示させてもかまわない)場合は、
    .NET の国際化対応機能で十分である。

  • 必要の(英語表示が間延びしたように表示させない)場合は、

    • 言語別に画面を作成する必要がある。

    • サイズ・位置などの情報をリソースファイルで管理する。
      (Windows Form のみ、この機能を実装している)

バイナリ変更+再配布の可 / 不可

表示される文字列を変更する場合、
バイナリ変更+再配布が可能か?不可能か?

  • 可能な場合、.NET のリソースファイルを使用できる。
    リソースファイルは、バイナリ(dll)に変換されるため、
    文字列を変更した時、スマートクライアント端末に再配布が必要になる。

  • 不可能な場合、独自機能で対応する必要がある。

    • 例えば、データベースに文字列情報を格納している場合、
      バイナリが変更されないため、スマートクライアント端末に再配布が不要になる。

リソース変更方法

バイナリ変更+再配布が不可の場合、
表示される文字列を誰が何時、どのような方法で変更するか?

  • エンドユーザか、運用者か、開発者かを考慮する。

  • Visual Studio(リソースファイル)を
    使用し変更しても良い場合、.NET の国際化対応機能で十分である。

  • 管理画面にて変更する場合、独自機能で対応する必要がある。

    • 例:管理画面でデータベースで管理されている文字列を変更する。

OS のロケール等依存の可 / 否

日時や日付が OS のロケール等に依存して良いか?

  • 日時や日付の表記が変更されてもかまわない場合、.NET の国際化対応機能で十分である。
  • 日時や日付の表記を一定に保つ場合、独自機能でロケール等を切り替える必要がある。

ユーザによる切り替えの要 / 否

画面のタイトルやラベル、ボタンに表示される文字列を
同一ロケールの中で動的に変更することがあるか?

  • 固定値の場合、.NET の国際化対応機能で十分である。

  • 可変値の場合、独自機能で対応する必要がある。

    • 例えば、業務アプリケーションでは
      画面のタイトルやラベルはプログラム上で変更することがある。

複数言語の混在

画面中に複数の言語(ex. 日本語と中国語)が混在することがあるか?

  • 一つの言語しか表示されない場合、.NET の国際化対応機能で十分である。
  • 複数の言語が同時に表示される場合、独自機能で対応する必要がある。

補足(この 6 つのチェック項目が本ページの実用的な核): 原文の
この節は、「標準機能で足りるか、独自機能が要るか」を
事前に切り分けるためのチェックリスト
として、現在も有用である。

判断を表にまとめておく。

要件 標準(.resx)で足りる 独自機能が要る
ラベルのサイズ調整 不要なら○ 必要なら(Web は CSS で可)
再配布の可否 可能なら○ 不可なら DB 方式
変更する人 開発者なら○ 運用者・エンドユーザなら管理画面
日時書式 OS 依存でよいなら○ 固定したいなら明示指定
実行中の動的変更 固定値なら○ 可変値なら
複数言語の同時表示 単一言語なら○ 混在するなら

「独自機能が要る」に該当する項目が 1 つでもあれば、
DB 方式(またはハイブリッド)を検討する
——というのが
本節の実践的な使い方になる
ASP.NET Web Formsの国際化対応 の DB 方式を参照)。

ASP.NET Core では IStringLocalizer という抽象があるため、
「まず .resx で作り、必要になったら実装だけ DB に差し替える」
という段階的な移行が可能である。
これは本節の判断を後回しにできる、という点で大きな進歩と言える。

その他

I/O、エンコーディング

I/O が発生する場合は、エンコーディングを意識する必要がある。

  • 日本語、英語以外の各国文字を扱う場合、
    文字セットは各国文字が利用可能な UTF-8, UTF-16 を使う。

  • 但し、最新の JIS 規格には Unicode にしか含まれない漢字も多く、
    日本語だけであっても Unicode 移行が必要になってきている。

詳細はこちらを参照のこと。

移行メモ(誤字): 原文の「Unicode 似しか含まれない」は
**「Unicode にしか含まれない」**の誤変換と判断し修正した。

ドキュメント

ドキュメントには以下の様な項目がある。

  • 取扱説明書
  • Readme
  • API リファレンス

各言語向けのものを静的に用意することで多言語対応を行う。

元号・時差の管理

元号・時差の管理について。

  • 元号・時差の情報のストアはレジストリとなっており、
    Windows Update により適用されるようになっている。

  • このため、レジストリ修正でもカスタマイズ可能であるが、
    管理上問題となるような場合は、独自部品を用意する必要がある。
    ただし、自前で管理する必要があるため、管理は煩雑になる。

レジストリ

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras]
"1868 01 01"="明治_明_Meiji_M"
"1912 07 30"="大正_大_Taisho_T"
"1926 12 25"="昭和_昭_Showa_S"
"1989 01 08"="平成_平_Heisei_HE"

アプリケーションで必要な正確なタイム ゾーン情報が、
次のような理由で特定のシステムに存在しない場合があります。

  • ローカル システムのレジストリでタイム ゾーンが定義されていない。
  • タイム ゾーンに関するデータがレジストリで変更または削除されている。
  • タイム ゾーンに、過去の特定の期間のタイム ゾーン調整に関する正確な情報がない。
  • サマータイム
    http://www.tohoho-web.com/ex/draft/sumtime.htm
    • タイムゾーンは、[コントロールパネル]→[日付と時刻]→[タイムゾーン]で選択可能。
    • tzedit.exe をダウンロードしてタイムゾーン情報を編集できる。
    • タイムゾーン情報は下記のレジストリに格納されている。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation

補足(Linux では tzdata): .NET Coreへの移行
伴い、元号・タイム ゾーンの情報源がプラットフォームで異なる点は
押さえておく必要がある。

Windows Linux / macOS
タイム ゾーン レジストリ(Windows Update) tzdata/usr/share/zoneinfo
元号 レジストリ(Windows Update) ICU
更新方法 Windows Update パッケージ更新apt upgrade tzdata

コンテナでは特に注意が要る

・イメージをビルドした時点の tzdata / ICU が固定される
  → サマータイム規則の変更に追随しない
  → 定期的にベース イメージを更新する必要がある

・そもそも tzdata / ICU が入っていないイメージがある
  → タイム ゾーン変換が例外になる
RUN apt-get update && apt-get install -y tzdata libicu-dev

「レジストリを修正すれば対応できる」という原文の記述は
Windows に限った話
であり、
クロスプラットフォームではランタイムとベース イメージの
更新方針
として管理する必要がある。

Windows Update

  • 元号
    Windows Update/Microsoft Update、毎月第二週の月例パッチ等で配布される予定であるが、
    平成以降、Windows Update/Microsoft Update で、元号が配信された実績はまだない。

  • サマータイム
    毎年、Windows Update/Microsoft Update で配付されている。

補足(令和では実際に配信された): 原文の
「平成以降、元号が配信された実績はまだない」は
本ページ執筆時点の記述である。
2019 年の令和改元では、実際に Windows Update / .NET の更新で
元号が配信された
(KB4469068 ほか)。

このため、「実行環境を更新すれば対応できる」という前提は
一度実証された
ことになる。
前述の新元号(令和)改元対応で述べた
**「自前で元号テーブルを持たない」**という指針の妥当性が
裏付けられた形である。

Win 7 以降 + .NET 4 以降なら、再ビルドなしで改元対応可能。
アプリをビルドした時の .NET バージョンではなく、
実行環境が Win 7 以降 & .NET 4 以降である事が条件。

  • e.g. : Win 10 + .NET 3.5 なら ×
  • e.g. : Win 7 + .NET 4 なら ○(アプリが .NET 2.0 でビルドされていても○)

改元関連

.NET Core

当たり前だが、Windows と仕組みが違うので対応も異なる。

補足(.NET Core 以降の全体像): 本ページ全体を通じて、
.NET Core 以降で変わった点を最後にまとめておく。

項目 .NET Framework .NET Core / .NET
国際化データの源 Windows(NLS) ICU(Windows 10 1903 以降も既定で ICU)
エンコーディング 多数を内蔵 CodePagesEncodingProvider文字コード
タイム ゾーン ID Windows 形式 IANA / Windows 両対応(.NET 6+)
元号 レジストリ ICU
カルチャの継承 4.6 以降で改善 既定で引き継がれるカルチャ
Invariant モード 無し InvariantGlobalization で ICU 無しにもできる

**最大の変化は「NLS から ICU へ」**である。

・並べ替え順・大文字小文字変換の結果が Windows と微妙に異なる場合がある
・ICU のバージョンによって結果が変わり得る
・コンテナに ICU が無いと Invariant で動く(静かに壊れる)

**移行時は「文字列の並べ替え・比較の結果が変わっていないか」**を
検証項目に入れておく必要がある
(特に、DB のソート結果と突き合わせている処理)。

// Windows 互換の NLS に戻す(.NET 5+、Windows のみ)
// runtimeconfig.json
// "System.Globalization.UseNls": true

Tags: 移行, その他、開発の色々, .NET開発, 国際化対応

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally