WPFアプリケーションをローカライズするにはさまざまな方法があります。 最も単純で最も一般的なオプションは、Resxリソースファイルとそれらに対して自動的に生成されたDesignerクラスを使用することです。 ただし、この方法では、言語を変更するときに「オンザフライ」で値を変更することはできません。 これを行うには、ウィンドウを再度開くか、アプリケーションを再起動します。
この記事では、カルチャを瞬時に変更してWPFアプリケーションをローカライズする方法を紹介します。
問題の声明
解決する必要があるタスクを示します。
- ローカライズされた文字列のさまざまなプロバイダー(リソース、データベースなど)を使用する機能。
- ローカライズ用のキーを、文字列だけでなくバインディングを通じて指定する機能。
- ローカライズされた値がフォーマットされた文字列である場合、引数(引数バインディングを含む)を指定する機能。
- カルチャを変更するときのすべてのローカライズされたオブジェクトの即時更新。
実装
さまざまなローカリゼーションプロバイダーを使用する可能性を実装するために、
ILocalizationProviderインターフェイスを作成します。
public interface ILocalizationProvider { object Localize(string key); IEnumerable<CultureInfo> Cultures { get; } }
このインターフェイスには、キーとこの実装で利用可能なカルチャのリストによって直接ローカライズを実行するメソッドがあります。
リソース用のこのインターフェイスの
ResxLocalizationProvider実装は次のようになります。
public class ResxLocalizationProvider : ILocalizationProvider { private IEnumerable<CultureInfo> _cultures; public object Localize(string key) { return Strings.ResourceManager.GetObject(key); } public IEnumerable<CultureInfo> Cultures => _cultures ?? (_cultures = new List<CultureInfo> { new CultureInfo("ru-RU"), new CultureInfo("en-US"), }); }
また、文化とローカライズされた文字列プロバイダーの現在のインスタンスを使用したすべての操作が発生する、補助的な単一クラス
LocalizationManagerを作成します。
public class LocalizationManager { private LocalizationManager() { } private static LocalizationManager _localizationManager; public static LocalizationManager Instance => _localizationManager ?? (_localizationManager = new LocalizationManager()); public event EventHandler CultureChanged; public CultureInfo CurrentCulture { get { return Thread.CurrentThread.CurrentCulture; } set { if (Equals(value, Thread.CurrentThread.CurrentUICulture)) return; Thread.CurrentThread.CurrentCulture = value; Thread.CurrentThread.CurrentUICulture = value; CultureInfo.DefaultThreadCurrentCulture = value; CultureInfo.DefaultThreadCurrentUICulture = value; OnCultureChanged(); } } public IEnumerable<CultureInfo> Cultures => LocalizationProvider?.Cultures ?? Enumerable.Empty<CultureInfo>(); public ILocalizationProvider LocalizationProvider { get; set; } private void OnCultureChanged() { CultureChanged?.Invoke(this, EventArgs.Empty); } public object Localize(string key) { if (string.IsNullOrEmpty(key)) return "[NULL]"; var localizedValue = LocalizationProvider?.Localize(key); return localizedValue ?? $"[{key}]"; } }
また、このクラスは、CultureChangedイベントを通じてカルチャの変更を通知します。
ILocalizationProviderの実装は、OnStartupメソッドのApp.xaml.csで指定できます。
LocalizationManager.Instance.LocalizationProvider = new ResxLocalizationProvider();
カルチャの変更後にローカライズされたオブジェクトがどのように更新されるかを考えてみましょう。
最も簡単なオプションは、
Bindingを使用することです。 実際、UpdateSourceTriggerプロパティのバインディングで値「PropertyChanged」を指定し、INotifyPropertyChangedインターフェイスのPropertyChangedイベントを呼び出すと、バインディング式が更新されます。 バインディングのデータのソースは、
KeyLocalizationListenerカルチャ
変更のリスナーになり
ます 。
public class KeyLocalizationListener : INotifyPropertyChanged { public KeyLocalizationListener(string key, object[] args) { Key = key; Args = args; LocalizationManager.Instance.CultureChanged += OnCultureChanged; } private string Key { get; } private object[] Args { get; } public object Value { get { var value = LocalizationManager.Instance.Localize(Key); if (value is string && Args != null) value = string.Format((string)value, Args); return value; } } public event PropertyChangedEventHandler PropertyChanged; private void OnCultureChanged(object sender, EventArgs eventArgs) {
ローカライズされた値はValueプロパティにあるため、バインディングのPathプロパティには値「Value」が必要です。
しかし、キー値が定数ではなく、事前に知られていない場合はどうでしょうか? その後、キーはバインディングを介してのみ取得できます。 この場合、マルチバインディング(
MultiBinding )が役立ちます。これはバインディングのリストを取り、その中にキーのバインディングがあります。 このバインディングの使用は、ローカライズされたオブジェクトがフォーマットされた文字列である場合、引数を渡すのにも便利です。 値を更新するには、
MultiBindingExpression multi-bindingタイプのオブジェクトのUpdateTargetメソッドを呼び出す必要があります。 このMultiBindingExpressionオブジェクトは、
BindingLocalizationListenerリスナーに渡され
ます 。
public class BindingLocalizationListener { private BindingExpressionBase BindingExpression { get; set; } public BindingLocalizationListener() { LocalizationManager.Instance.CultureChanged += OnCultureChanged; } public void SetBinding(BindingExpressionBase bindingExpression) { BindingExpression = bindingExpression; } private void OnCultureChanged(object sender, EventArgs eventArgs) { try {
この場合、マルチバインディングには、キー(および引数)をローカライズされた値に変換するコンバーターが必要です。 このような
BindingLocalizationConverterコンバーターのソースコード:
public class BindingLocalizationConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values == null || values.Length < 2) return null; var key = System.Convert.ToString(values[1] ?? ""); var value = LocalizationManager.Instance.Localize(key); if (value is string) { var args = (parameter as IEnumerable<object> ?? values.Skip(2)).ToArray(); if (args.Length == 1 && !(args[0] is string) && args[0] is IEnumerable) args = ((IEnumerable) args[0]).Cast<object>().ToArray(); if (args.Any()) return string.Format(value.ToString(), args); } return value; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
XAMLでローカライズを使用するには、MarkupExtension
LocalizationExtensionマークアップ拡張機能を記述します。
[ContentProperty(nameof(ArgumentBindings))] public class LocalizationExtension : MarkupExtension { private Collection<BindingBase> _arguments; public LocalizationExtension() { } public LocalizationExtension(string key) { Key = key; }
マルチバインディングを使用する場合、BindingLocalizationListenerリスナーのバインディングも作成し、マルチバインディングBindingsに配置することに注意してください。 これは、ガベージコレクタがリスナーをメモリから削除しないようにするためです。 これが、BindingLocalizationConverterでnull値[0]要素が無視される理由です。
また、キーを使用する場合、ターゲットがDependencyObjectのDependencyPropertyプロパティである場合にのみバインディングを使用できることに注意してください。
更新:このバインディングはスタイルでも使用できるため、ターゲットをセッターにすることができます。
現在のLocalizationExtensionインスタンスがバインディングのソースである場合(およびバインディングがDependencyObjectではない場合)、新しいバインディングを作成する必要はありません。 したがって、PathおよびUpdateSourceTriggerバインディングを割り当てて、KeyLocalizationListenerリスナーを返すだけです。
以下は、XAMLでLocalizationExtension拡張機能を使用するためのオプションです。
キーのローカライズ:
<TextBlock Text="{l:Localization Key=SomeKey}" />
または
<TextBlock Text="{l:Localization SomeKey}" />
バインディングローカリゼーション:
<TextBlock Text="{l:Localization KeyBinding={Binding SomeProperty}}" />
バインディングのローカライズには多くのユースケースがあります。 たとえば、ドロップダウンリストに特定の列挙(Enum)のローカライズされた値を表示する必要がある場合。
静的引数を使用したローカライズ:
<TextBlock> <TextBlock.Text> <l:Localization Key="SomeKey" Arguments="{StaticResource SomeArray}" /> </TextBlock.Text> </TextBlock>
引数バインディングを使用したローカライズ:
<TextBlock> <TextBlock.Text> <l:Localization Key="SomeKey"> <Binding Source="{l:Localization SomeKey2}" /> <Binding Path="SomeProperty" /> </l:Localization> </TextBlock.Text> </TextBlock>
このローカライズオプションは、検証メッセージ(たとえば、入力フィールドの最小長に関するメッセージ)を表示するときに使用すると便利です。
プロジェクトのソースは
GitHubで取得できます。