多言語化、或いはローカライズ(弐)

多言語化、或いはローカライズ(壱)の続き。まずは、ほぼそのまま作ってみる。
ITranslationProviderインターフェース
using System;
using System.Collections.Generic;
using System.Globalization;

namespace TawamureDays {

/// <summary>
/// 文字列置き換えプロバイダインターフェース
/// </summary>
/// <remarks>
/// 指定した文字列をリソース内のオブジェクトに置き換えるクラスが
/// 提供するプロパティ、メソッドが定義されているインターフェースです。<br/>
/// </remarks>
public interface ITranslationProvider {

#region publicメソッド

/// <summary>
/// 指定のキーから置き換えられるオブジェクトを取得します。
/// </summary>
/// <param name="key">キー文字列</param>
/// <returns>置き換えられるオブジェクト</returns>
object Translate(string key);

#endregion
}
}
サポートする言語を取得する為のLanguagesプロパティを削除した。英語と日本語限定でも良いだろし。
このインターフェースを実装し、リソースファイル(resx)からリソースを取得するクラスを作成する。
using System;
using System.Collections.Generic;
using System.Resources;
using System.Reflection;
using System.Globalization;

namespace TawamureDays {

/// <summary>
/// リソースファイル用文字列置き換えクラス
/// </summary>
/// <remarks>
/// 指定した文字列(キー)をリソースファイルの内容(オブジェクト)に置き換えるクラス<br/>
/// 検索元となるリソースファイルは、コンストラクタで指定されます。<br/>
/// </remarks>
public class ResxTranslationProvider : ITranslationProvider {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="baseName">リソースのルート名(言語やresxな拡張子は不要)</param>
/// <param name="assembly">アセンブリ</param>
public ResxTranslationProvider(string baseName, Assembly assembly) {
this.resourceManager_ = new ResourceManager(baseName, assembly);
return;
}

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="resourceType">リソースクラスのタイプ</param>
public ResxTranslationProvider(Type resourceType) {
this.resourceManager_ = new ResourceManager(resourceType);
return;
}

#endregion

#region ITranslationProvider メンバー

/// <summary>
/// 指定のキーから置き換えられるオブジェクト(文字列)を取得します。
/// </summary>
/// <param name="key">キー文字列</param>
/// <returns>置き換えられるオブジェクト</returns>
public object Translate(string key) {
return this.resourceManager_.GetString(key,
System.Threading.Thread.CurrentThread.CurrentUICulture);
}

#endregion

#region 内部変数

/// <summary>リソースアクセス用マネージャ</summary>
private readonly ResourceManager resourceManager_;

#endregion
}
}
このクラスがリソースファイルへアクセスして、指定キーのリソースを返す役割となる。
次、言語変更用イベントマネージャクラス
using System;
using System.Windows;

namespace TawamureDays {

/// <summary>
/// 言語変更イベント管理用マネージャクラス。
/// </summary>
public class LanguageChangedEventManager : System.Windows.WeakEventManager {

#region publicメソッド(static)

/// <summary>
/// 言語管理用マネージャにリスナを追加します。
/// </summary>
/// <param name="source">言語管理用マネージャ</param>
/// <param name="newListener">追加対象となるリスナ</param>
public static void AddListener(
TranslationManager source, IWeakEventListener newListener) {
CurrentManager.ProtectedAddListener(source, newListener);
return;
}

/// <summary>
/// 言語管理用マネージャトからリスナを追加します。
/// </summary>
/// <param name="source">言語管理用マネージャ</param>
/// <param name="listener">削除対象となるリスナ</param>
public static void RemoveListener(
TranslationManager source, IWeakEventListener listener) {
CurrentManager.ProtectedRemoveListener(source, listener);
return;
}

#endregion

#region WeakEventManager メンバー

/// <summary>
/// 指定されたソースで管理対象のイベントのリッスンを開始します
/// </summary>
/// <param name="source">指定されたソース</param>
protected override void StartListening(object source) {
var manager = source as TranslationManager;

if (manager != null) {
manager.LanguageChanged += this.OnLanguageChanged;
}
return;
}

/// <summary>
/// 指定されたソースで管理対象のイベントのリッスンを停止します
/// </summary>
/// <param name="source">指定されたソース</param>
protected override void StopListening(object source) {
var manager = source as TranslationManager;

if (manager != null) {
manager.LanguageChanged -= this.OnLanguageChanged;
}
}

#endregion

#region コンストラクタ(private)

/// <summary>
/// コンストラクタ(private)
/// </summary>
private LanguageChangedEventManager() {
return;
}

#endregion

#region 内部プロパティ

/// <summary>
/// 現在のイベントマネージャインスタンスを取得します。
/// </summary>
private static LanguageChangedEventManager CurrentManager {

get {
Type managerType = typeof(LanguageChangedEventManager);
var manager = (LanguageChangedEventManager)GetCurrentManager(managerType);

if (manager == null) {
manager = new LanguageChangedEventManager();
SetCurrentManager(managerType, manager);
}
return manager;
}
}

#endregion

#region privateメソッド

/// <summary>
/// TranslationManager LanguageChangedイベントハンドラ用メソッド
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベントデータ</param>
private void OnLanguageChanged(object sender, EventArgs e) {
//リスナに通知します。
this.DeliverEvent(sender, e);
return;
}

#endregion
}
}
EventManagerは、イベントを発生する側(イベントソース)とそれを受けとる側(リスナ)を仲介するクラス。従来のイベント登録(+=で結ぶ奴)で起こる「いつまでも参照しあって、GCされない」という問題を解消するためのクラスだったかな。
次、文字列置き換えを管理するためのマネージャクラス
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;

namespace TawamureDays {

/// <summary>
/// 文字列置き換え管理クラス
/// </summary>
/// <remarks>
/// UIカルチャ固有のリソース情報をXAML上で使用する為の管理クラス<br/>
/// 指定キーから、UIカルチャ情報に従った内容を呼び出し元に渡します。<br/>
/// 実行中に切り替えられるようにもなっています。<br/>
/// </remarks>
public class TranslationManager {

#region イベント

/// <summary>言語切替えイベント用ハンドラ</summary>
public EventHandler languageChangedHandler_;

/// <summary>
/// 言語変更イベントへハンドラを追加|削除します。
/// </summary>
public event EventHandler LanguageChanged {
add {this.languageChangedHandler_ += value;}
remove {this.languageChangedHandler_ -= value;}
}

#endregion

#region publicクラス変数

/// <summary>
/// インスタンスを取得|設定します。
/// </summary>
public static TranslationManager Instance {
get { return instance_; }
}

#endregion

#region プロパティ

/// <summary>
/// 現在の言語(UICulture)を取得|設定します。
/// </summary>
public CultureInfo CurrentLanguage {
get {return System.Threading.Thread.CurrentThread.CurrentUICulture;}
set {
if (value == null) {
return;//nullになんて設定できない。
}
if (value != System.Threading.Thread.CurrentThread.CurrentUICulture) {
System.Threading.Thread.CurrentThread.CurrentUICulture = value;
this.OnLanguageChanged();
}
}
}

#endregion

#region publicメソッド

/// <summary>
/// 指定キーで置き換えられるオブジェクトを取得します。
/// </summary>
/// <param name="key">指定キー</param>
/// <returns>置き換えられるオブジェクト</returns>
public object Translate(string key) {

if (this.TranslationProvider != null) {
var val = this.TranslationProvider.Translate(key);

if (val != null) {
return val;
}
}

//取得に失敗している事を見た目でわかるようにする。
return string.Format("!{0}!", key);
}

#endregion

#region コンストラクタ(private)

/// <summary>
/// コンストラクタ(private)
/// </summary>
private TranslationManager() {
}

#endregion

#region 内部クラス変数

/// <summary>インスタンス</summary>
private static TranslationManager instance_ = new TranslationManager();

#endregion

#region 内部変数

/// <summary>
/// 置き換え情報提供オブジェクトを取得|設定します。
/// </summary>
public ITranslationProvider TranslationProvider {
get; set;
}

#endregion

#region 内部メソッド

/// <summary>
/// 言語が切り替えられた事を通知します。
/// </summary>
private void OnLanguageChanged() {

if (this.languageChangedHandler_ != null) {
this.languageChangedHandler_(this, System.EventArgs.Empty);
}
}

#endregion
}
}
言語切替は、このManagerクラスを通して行う。そうすることで、イベントソースとして機能する。
次、バインドソースとなるデータクラス
using System;
using System.ComponentModel;
using System.Windows;

namespace TawamureWorks.Containers {

/// <summary>
/// 文字列置き換え用バインドソースクラス
/// </summary>
public class TranslationData : DisposableObject, IWeakEventListener, INotifyPropertyChanged {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="key">キー文字列</param>
public TranslationData(string key) : base() {
this.key_ = key;
LanguageChangedEventManager.AddListener(TranslationManager.Instance, this);
return;
}

#endregion

#region プロパティ

/// <summary>
/// キー文字列に対応した(置き換えられる)オブジェクトを取得します。
/// </summary>
public object Value {
get {
return TranslationManager.Instance.Translate(this.key_);
}
}

#endregion

#region INotifyPropertyChanged メンバー

/// <summary>
/// プロパティ変更イベント
/// </summary>
public event PropertyChangedEventHandler PropertyChanged {
add {this.propertyChangedHandler_ += value;}
remove {this.propertyChangedHandler_ -= value;}
}

#endregion

#region IWeakEventListener メンバー

/// <summary>
/// 中央のイベント マネージャーからイベントを受信します
/// </summary>
/// <param name="managerType">このメソッドを呼び出すWeakEventManagerのタイプ情報</param>
/// <param name="sender">イベントを発生させたオブジェクト。</param>
/// <param name="e">イベントデータ</param>
/// <returns>true:リスナーがイベントを処理した, false:処理していない</returns>
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
if (managerType == typeof(LanguageChangedEventManager)) {
//言語変更に伴う処理(Valueプロパティ変更通知)
this.OnLanguageChanged(sender, e);
return true;
}
return false;
}

#endregion

#region DisposableObject メンバー

/// <summary>
/// マネージドリソースを破棄します。
/// </summary>
///<param name="disposing">true:マネージドリソースのみ開放</param>
protected override void OnDispose(bool disposing) {
if (IsDisposed) {
return;
}

if (disposing) {

this.key_ = null;
LanguageChangedEventManager.RemoveListener(TranslationManager.Instance, this);

if (this.propertyChangedHandler_ != null) {
foreach (PropertyChangedEventHandler handler in
this.propertyChangedHandler_.GetInvocationList()) {
this.propertyChangedHandler_ -= handler;
}
}
}

base.OnDispose(disposing);
return;
}

#endregion

#region 内部クラス変数

/// <summary>Valueプロパティ変更イベントデータ</summary>
private static readonly PropertyChangedEventArgs valuePropertyChanged_ =
new PropertyChangedEventArgs("Value");

#endregion

#region 内部変数

/// <summary>キー文字列</summary>
private string key_;

/// <summary>プロパティ変更イベント用ハンドラ</summary>
private PropertyChangedEventHandler propertyChangedHandler_;

#endregion

#region 内部メソッド

/// <summary>
/// 言語変更に伴う処理を実行します。<br/>
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベントデータ</param>
private void OnLanguageChanged(object sender, EventArgs e) {
//イベント通知
if (this.propertyChangedHandler_ != null) {
this.propertyChangedHandler_(this, valuePropertyChanged_);
}
}

#endregion
}
}
・Valueプロパティが、コンストラクタで指定したキーで取得したリソースとなる。
・INotifyPropertyChangedを実装しているので、バインドソースとなりうる。
・EventManagerのリスナとなるIWeakEventListenerインターフェースを持っている。つまり、言語(カルチャ)変更の通知を受けるクラスでもある。
・言語(カルチャ)変更のイベントが通知されると、Valueプロパティの変更を通知する。

最後、マークアップクラス
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace TawamureDays.Markup {

using TawamureDays.Containers;

/// <summary>
/// 文字列置き換え用マークアップクラス
/// </summary>
/// <remarks>
/// 指定キーでリソースファイル内を検索し、対応するオブジェクトに置き換える
/// 為のマークアップクラスです。<br/>
/// </remarks>
public class TransExtension : MarkupExtension {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TransExtension() {
return;
}

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="key">キー文字列</param>
/// <param name="fallback">指定キーがない時の代替文字列</param>
public TransExtension(string key, string fallback) {
this.Key = key;
this.Default = fallback;
return;
}

#endregion

#region プロパティ

/// <summary>
/// キーを取得|設定します。
/// </summary>
[ConstructorArgument("Key")]
public string Key {
get; set;
}

/// <summary>
/// デフォルト文字列を取得|設定します。
/// </summary>
[ConstructorArgument("Default")]
public string Default {
get; set;
}

#endregion

#region MarkupExtension メンバー

/// <summary>
/// このマークアップ拡張機能で使用するターゲットプロパティに設定されるオブジェクトを返します。
/// </summary>
/// <param name="serviceProvider">マークアップ拡張機能のサービスを提供できるサービスプロバイダーヘルパー</param>
/// <returns>拡張機能が適用されたプロパティで設定するオブジェクトの値</returns>
public override object ProvideValue(IServiceProvider serviceProvider) {

if (Utils.IsEmptyOrWhiteSpace(this.Key)) {
//キーが空であるときは、デフォルト文字列を返す。
return this.Default;
}

var binding = new Binding("Value") {
Source = new TranslationData(this.Key)
};

return binding.ProvideValue(serviceProvider);
}

#endregion
}
}

続く。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m