DIを使いながら、国際化対応(多言語対応)をしよう
今やグローバル時代!世界を相手にしなければ勝てない!
そう、東京弁だけでなく大阪弁、名古屋弁、博多弁など…ごめんなさい調子に乗りました、単純に「日本語」「英語」「ロシア語」対応について語ります。(天丼)
今回は総集編として カップ麺シリーズ:一から学べる、WPFでの多言語対応方法 を、DI込みの記事として公開します。
最初にプロジェクトを作ろう
今回は「WPFアプリケーション」で DIMultiLanguageTest
というプロジェクトを作りましょう。フレームワークは「.NET 9.0」を利用します。
プロジェクトに Views
と ViewModel
フォルダを作ります。
そして開かれた MainWindow.xaml
を Views
フォルダに移動して、以下のようにします。
<Window
x:Class="DIMultiLanguageTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DIMultiLanguageTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="DIMultiLanguageTest"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="Language">
<MenuItem Command="{Binding ToJapaneseCommand}" Header="Japanese" />
<MenuItem Command="{Binding ToEnglishCommand}" Header="English" />
<MenuItem Command="{Binding ToRussianCommand}" Header="Russian" />
</MenuItem>
</Menu>
<Button
Grid.Row="1"
Margin="5"
Command="{Binding ExecuteGreetingsCommand}"
Content="{Binding Greetings}" />
</Grid>
</Window>
MainWindow.xaml
のフォルダを移動したので、このままでは例外で落ちるため、App.xaml
を以下のように書き換えます。
<Application
x:Class="DIMultiLanguageTest.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DIMultiLanguageTest"
StartupUri="Views/MainWindow.xaml">
<Application.Resources />
</Application>
わかると思いますが、MainWindow.xaml
を移動した Views\
を StartupUri
に書き加えたわけですね。
そして、CommunityToolkit.Mvvm を NuGet で導入しましょう。 分かる人は当然 dotnet コマンドからでいいですよ?
次に、Microsoft.Extensions.DependencyInjection も NuGetで導入しましょう。分かる人は(略)
CommunityToolkit.Mvvm でDIの設定をしよう
App.xaml.cs に以下のコードを適切にコピペしましょう、これでDIの最低限の準備ができます。
ちなみに DI のテンプレートとしてどこかに保存しておくのがいいでしょう。定型文くらいの立ち位置ですから。
public partial class App : Application
{
/// <summary>
/// サービスの登録をします
/// </summary>
public App()
{
Services = ConfigureServices();
Ioc.Default.ConfigureServices(Services);
}
/// <summary>
/// 現在の App インスタンスを取得します
/// </summary>
public new static App Current => (App)Application.Current;
/// <summary>
/// サービスプロバイダです
/// </summary>
public IServiceProvider Services { get; }
/// <summary>
/// サービスを登録します
/// </summary>
/// <returns></returns>
private static ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<IMessenger, WeakReferenceMessenger>();
services.AddSingleton<IMainWindowViewModel, MainWindowViewModel>();
return services.BuildServiceProvider();
}
}
ここで必要なディレクティブ
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
が追加されていると思いますが、無いのであれば自分で書き加えましょう。
この時点では IMainWindowViewModel
と MainWindowViewModel
にエラーが出ます。
MainWindow に依存性注入
Views\MainWindow.xaml.cs のコンストラクタを以下のように書き換えます。
public MainWindow()
{
InitializeComponent();
DataContext = Ioc.Default.GetService<IMainWindowViewModel>();
}
次に IMainWindowViewModel
と MainWindowViewModel
を作ります。
ViewModelフォルダに MainWindowViewModel.cs を作成して以下のようにします。
using CommunityToolkit.Mvvm.ComponentModel;
namespace DIMultiLanguageTest.ViewModels
{
public interface IMainWindowViewModel { }
public class MainWindowViewModel : ObservableObject, IMainWindowViewModel
{
/// <summary>
/// 挨拶をするボタンのテキスト
/// </summary>
public string Greetings
{ get => "あいさつ"; }
}
}
※ここで static
にマークできるというメッセージは無視してください。
そして、App.xaml.cs と MainWindow.xaml.cs に以下のディレクティブを追記します。
using DIMultiLanguageTest.ViewModels;
これで、DI を使うための最低限の準備が整いました。
リソースファイルの作成
ここでは、 英語リソースファイルを Resource.resx、 日本語リソースファイルを Resource.ja.resx、 ロシア語リソースファイルを Resource.ru.resx とします。
この ja
や ru
がカルチャーコードで、リソースファイルの命名規則になっています。
プロジェクト直下に Resources フォルダを作り、 「追加」-「新しい項目」-「リソースファイル」を選び、この三つのファイルを作成します。
リソースエディタの細かい使い方は カップ麺シリーズ:一から学べる、WPFでの多言語対応方法 を参考にしてください。
名前には「Greetings」、ja
には「あいさつ」、ru
には「Приветствие」とすると、リソースエディタは以下のようになるかと思います。
国際化対応(多言語対応)の準備
プロジェクト直下に Services フォルダを作り、その中に ResourceService.cs を作成します。
内容は以下の通りとなります。
using System.Globalization;
using System.Resources;
namespace DIMultiLanguageTest.Services
{
public static class ResourceService
{
private static readonly ResourceManager resourceManager = new("DIMultiLanguageTest.Resources.Resource", typeof(ResourceService).Assembly);
/// <summary>
/// 言語を変更します。
/// </summary>
/// <param name="cultureCode">変更するカルチャーコード</param>
public static void ChangeCulture(string cultureCode)
{
var culture = new CultureInfo(cultureCode);
CultureInfo.CurrentUICulture = culture;
}
/// <summary>
/// リソースファイルから名前をキーに、カルチャーコードに基づいた文字列を取得します。
/// </summary>
/// <param name="key">文字列を取得するリソースファイルキー</param>
/// <returns>取得したい文字列</returns>
public static string GetString(string key)
{
return resourceManager.GetString(key, CultureInfo.CurrentUICulture) ?? string.Empty;
}
}
}
DIMultiLanguageTest.Resources.Resource
は「プロジェクト名 - フォルダ名 - リソースファイル名」になっています。
国際化対応(多言語対応)の開始
ここは一気に行きます。MainWindowViewModel.cs を以下のようにしてください。
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DIMultiLanguageTest.Services;
namespace DIMultiLanguageTest.ViewModels
{
public interface IMainWindowViewModel { }
public partial class MainWindowViewModel : ObservableObject, IMainWindowViewModel
{
/// <summary>
/// 挨拶をするボタンのテキスト
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression")]
public string Greetings
{ get => ResourceService.GetString("Greetings"); }
/// <summary>
/// 挨拶のためのカルチャーコードを変更します。
/// </summary>
/// <param name="cultureCode">カルチャーコード</param>
private void ChangeGreetingsCulture(string cultureCode)
{
ResourceService.ChangeCulture(cultureCode);
OnPropertyChanged(nameof(Greetings));
}
[RelayCommand]
private void ToEnglish()
{
ChangeGreetingsCulture("en");
}
[RelayCommand]
private void ToJapanese()
{
ChangeGreetingsCulture("ja");
}
[RelayCommand]
private void ToRussian()
{
ChangeGreetingsCulture("ru");
}
}
}
この部分はメッセージ抑制に過ぎないですが、エラー一覧でうるさくなると思うので追記しています。
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression")]
補足しますと、XAMLで設定した ToEnglishCommand
に対応するのが ToEnglish()
となっています、他も同様です。
[RelaCommand]
を使うと、末尾に Command
を加えて ToEnglish()
が ToEnglishCommand()
としてバインディングされます。
おわりに
この通りに作成すれば、メニュー Language から表示画面が切り替わります。
元記事と同様に…今回は名前が変更されていますが ExecuteGreetingsCommand
が実装されていません。
宿題として、英語では「Hello」、日本語では「こんにちは」、ロシア語では「Здравствуйте」と、メッセージボックスを表示するコマンドを実装してくださいね。
CommunityToolkit.MvvmのDIと組み合わせた国際化対応(多言語対応)は、こんな感じになりますよという話でした。
「Services」フォルダはあまり聞き慣れないかもしれませんが、 例えば、ViewとViewModel、あるいはModelのどこに置けば適切か迷うような、層を横断するようなクラスの置き場とでも考えてください。
宿題に手をつけられる、完成品はGitHub に載せています。