Java Tips | 文字列処理:i18n文字列取得

Java Java
スポンサーリンク

i18n文字列取得は「画面の言葉をコードから切り離す」技術

i18n(internationalization)は「多言語対応」のことです。
i18n文字列取得は、「日本語・英語・中国語…など、ユーザーの言語に合わせた文言を取り出す仕組み」を指します。

業務システムでは、画面メッセージやエラーメッセージを
ソースコードにベタ書きしてしまうと、言語追加や文言修正のたびにビルドが必要になります。

そこで登場するのが「プロパティファイル+キー+Locale+ユーティリティ」という構成です。
文言はファイルに、ロジックはコードに分けることで、
多言語対応も文言変更も、ずっと楽になります。


基本の仕組み:ResourceBundle と properties ファイル

プロパティファイルで「キー→文言」を定義する

まずは、言語ごとのメッセージを *.properties ファイルに書きます。
例えば、クラスパス上に messages.properties(デフォルト)、messages_ja.properties(日本語)、messages_en.properties(英語)を用意します。

messages_ja.properties:

greeting=こんにちは {0} さん
error.notfound=データが見つかりませんでした

messages_en.properties:

greeting=Hello {0}
error.notfound=Data not found

ここで大事なのは、「キー(greeting, error.notfound)で文言を管理する」という発想です。
画面やコードでは「キー」だけを知っていればよく、実際の文言はファイル側で差し替えられます。

ResourceBundle で言語ごとのメッセージを取得する

Java標準の ResourceBundle を使うと、Locale に応じて適切なファイルから文言を取得できます。

import java.util.Locale;
import java.util.ResourceBundle;

public class I18nSample {

    public static void main(String[] args) {
        Locale ja = Locale.JAPANESE;
        Locale en = Locale.ENGLISH;

        ResourceBundle bundleJa = ResourceBundle.getBundle("messages", ja);
        ResourceBundle bundleEn = ResourceBundle.getBundle("messages", en);

        System.out.println(bundleJa.getString("error.notfound")); // データが見つかりませんでした
        System.out.println(bundleEn.getString("error.notfound")); // Data not found
    }
}
Java

ここでの重要ポイントは、getBundle("messages", locale) とするだけで、
messages_ja.propertiesmessages_en.properties を自動で選んでくれることです。
ファイル名のルール(ベース名+_言語コード)さえ守れば、Javaがよしなに切り替えてくれます。


プレースホルダ付きメッセージを扱う(MessageFormat)

{0} などのプレースホルダに値を埋め込む

多くのメッセージは、固定文言だけでなく「名前」「日付」「数値」などを埋め込みたいはずです。
そのときに使うのが MessageFormat です。

先ほどの greeting を使ってみます。

import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;

public class I18nGreeting {

    public static void main(String[] args) {
        Locale ja = Locale.JAPANESE;
        Locale en = Locale.ENGLISH;

        ResourceBundle bundleJa = ResourceBundle.getBundle("messages", ja);
        ResourceBundle bundleEn = ResourceBundle.getBundle("messages", en);

        String templateJa = bundleJa.getString("greeting");
        String templateEn = bundleEn.getString("greeting");

        String msgJa = MessageFormat.format(templateJa, "山田");
        String msgEn = MessageFormat.format(templateEn, "Yamada");

        System.out.println(msgJa); // こんにちは 山田 さん
        System.out.println(msgEn); // Hello Yamada
    }
}
Java

ここで深掘りしたいポイントは二つです。

一つ目は、「テンプレート側に {0} という“位置指定の穴”をあけておく」という考え方です。
{0} に1番目の引数、{1} に2番目の引数…というルールで埋め込まれます。

二つ目は、「言語ごとに文言の順番が変わっても、テンプレート側で吸収できる」ことです。
例えば、英語では "Hello {0}"、別の言語では "{0} さん、こんにちは" のように順番が違っても、
コード側は MessageFormat.format(template, name) のままでOKです。


実務で使いやすくするための i18n ユーティリティクラス

ResourceBundle と MessageFormat をまとめてラップする

毎回 ResourceBundle.getBundleMessageFormat.format を書くのは面倒なので、
よく使うパターンをユーティリティにまとめてしまいます。

import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

public final class I18n {

    private static final String BUNDLE_NAME = "messages";

    private I18n() {}

    public static String get(Locale locale, String key, Object... args) {
        if (locale == null) {
            locale = Locale.getDefault();
        }
        try {
            ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
            String template = bundle.getString(key);
            if (args == null || args.length == 0) {
                return template;
            }
            return MessageFormat.format(template, args);
        } catch (MissingResourceException e) {
            // キーが見つからない場合のフォールバック
            return "!" + key + "!";
        }
    }
}
Java

使い方はとてもシンプルになります。

Locale ja = Locale.JAPANESE;
Locale en = Locale.ENGLISH;

System.out.println(I18n.get(ja, "greeting", "山田"));
System.out.println(I18n.get(en, "greeting", "Yamada"));

System.out.println(I18n.get(ja, "error.notfound"));
System.out.println(I18n.get(en, "error.notfound"));
Java

ここでの重要ポイントは三つです。

一つ目は、「Locale・キー・可変長引数(プレースホルダ用)」というインターフェースにしていることです。
これだけ渡せば、言語切り替えもプレースホルダ展開も一発でできます。

二つ目は、「キーが見つからない場合のフォールバック」を決めていることです。
ここでは "!key!" のように目立つ形で返しています。
これにより、設定漏れやタイプミスにすぐ気づけます。

三つ目は、「Locale が null のときは Locale.getDefault() を使う」というポリシーです。
これにより、「特に指定がなければサーバのデフォルトロケールを使う」という自然な挙動になります。


フォールバックとデフォルト言語の考え方

指定言語がないとき、どの言語を使うか

ResourceBundle は、指定した Locale に対応するファイルがない場合、
より一般的なもの(言語だけ、さらにデフォルト)へとフォールバックしていきます。

例えば、Locale("ja", "JP")messages_ja_JP.properties がなければ、
messages_ja.propertiesmessages.properties の順に探します。

この仕組みを前提に、
「最低限 messages.properties(デフォルト言語)だけは必ず用意しておく」
というルールにしておくと、
どの言語でも必ず何かしらのメッセージが表示されるようになります。

実務では、まず日本語だけで messages.properties を作り、
あとから messages_en.properties を追加する、という進め方もよくあります。


実務での i18n文字列取得の使いどころ

画面メッセージ・エラーメッセージをすべてキー管理にする

例えば、コントローラやサービスの中で、
こんなコードを書いていたとします。

throw new IllegalStateException("データが見つかりませんでした");
Java

これを i18n 対応すると、こうなります。

throw new IllegalStateException(I18n.get(locale, "error.notfound"));
Java

画面側でも同様に、
「日本語の文言を直接書く」のではなく「キーで取得する」スタイルに統一します。

これにより、
「文言の変更」=「propertiesファイルの編集」
「言語追加」=「新しいpropertiesファイルの追加」
という形に整理され、コード側はほとんど触らなくて済むようになります。


まとめ:i18n文字列取得ユーティリティで身につけたい感覚

i18n文字列取得は、「文言をコードから切り離し、キーとLocaleで管理する」ための仕組みです。

押さえておきたいのは次の感覚です。

文言は *.properties に「キー→文言」として定義する
ResourceBundle.getBundle("basename", locale) で言語ごとのファイルを自動選択できる
MessageFormat{0} などのプレースホルダに値を埋め込める
ユーティリティクラス(I18n.get(locale, key, args…))にまとめると、業務コードがすっきりする
キーが見つからないとき・Locale がないときのフォールバックルールを決めておく

もしあなたのコードのどこかに、
日本語メッセージをそのままベタ書きしている行があったら、
それを「キー+i18nユーティリティ」に置き換えてみてください。

その小さな一歩が、
「多言語対応を前提にした、長く保守しやすいシステム」を作れるエンジニアへの、確かなステップになります。

タイトルとURLをコピーしました