LoginSignup
4
6

More than 5 years have passed since last update.

Xamarin.Forms Prismで動的な多言語対応をしてみた

Last updated at Posted at 2016-11-22

Xamarin.Forms - PrismでPCL内で完結できないかと試行錯誤しましたが、結局DependencyServiceを使いました。
なんか根本的に間違えていたら...と思いつつもやってみたことを公開してみます。
考え方としては、resxを動的に取得するプロパティをModelで管理して、それをVM-VとBindingする感じでやってみました。

スクリーンショット 2016-11-22 21.36.19.pngスクリーンショット 2016-11-22 21.36.34.png

どうやったか

0, 言語分resxファイル作成
1. resxのcultureを変更、resxのプロパティを取得するDependencyServiceをそれぞれ作成
2. modelで、resxを模倣したDictionaryを作成(PropertyChanged発行)
3. vm-vで、modelのDictionaryをReactivePropertyで監視、言語変更指示

1, resxのcultureを変更、resxのプロパティを取得するDependencyServiceをそれぞれ作成

基本、こちらの記事を参考にしました。
というか、 resxのcultureを変更 はまんまそれです。
上記に加えてresxのプロパティをmodelで変更通知を持ったプロパティとして存在しておいてほしいために、Reflectionでプロパティリストを返すDependencyServiceを作成しました。

ResourceInfo_Android.cs
[assembly: Dependency(typeof(ResourceInfo_Android))]
namespace UsingDependencyService.Android
{
    public class ResourceInfo_Android: IResourceInfo
    {

        public Dictionary<string, string> GetResourceProperties(Type val)
        {           
            var props = val.GetProperties(BindingFlags.Static | BindingFlags.Public)
                             .Where(x => x.PropertyType == typeof(string))
                             .Select(x => new KeyValuePair<string,string>(x.Name, x.GetValue(val, null) as string));

            Dictionary<string, string> rtDict = new Dictionary<string, string>();

            foreach (var item in props)
            {
                rtDict.Add(item.Key, item.Value);
            }

            return rtDict;
        }

    }
}

2, modelで、resxを模倣したDictionaryを作成(PropertyChanged発行)

resxで利用するのは、stringのみという程で。
vm-vはRescollectionを参照しておけば、自動更新されます。

LanguageModel.cs

    public class LanguageModel: BindableBase
    {

        public static LanguageModel Instance { get; } = new LanguageModel();

        // サポートされているカルチャのEnum
        public enum SUPPORT_LANG
        {
            [EnumText("日本語")]
            JA = 1,// Resources.resx
            [EnumText("English")]
            EN,
            [EnumText("中文(繁體字)")]
            TH,
            [EnumText("ไทย")]
            ZHTH,
        }

        // サポートされているカルチャのプレフィックス
        public Dictionary<SUPPORT_LANG, string> SupportLangPrefix = new Dictionary<SUPPORT_LANG, string>
        {
            {SUPPORT_LANG.JA, "ja-JP"},
            {SUPPORT_LANG.EN, "en"},
            {SUPPORT_LANG.TH, "th"},
            {SUPPORT_LANG.ZHTH, "zh-TW"},
        };

        // 現在の言語のresxファイルプロパティ達
        private Dictionary<string, string> resCollection;
        public Dictionary<string, string> Rescollection
        {
            get { return resCollection; }
            set
            {
                SetProperty(ref resCollection, value);
            }
        }

        // カルチャーを変更するためのDependencyService
        ICultureInfo cultureInfo;

        // resxのプロパティを取得するDependencyService
        IResourceInfo resService;

        public LanguageModel()
        {
            Rescollection = new Dictionary<string, string>();
            cultureInfo = DependencyService.Get<ICultureInfo>();
            resService = DependencyService.Get<IResourceInfo>();

            CreateResCollection();
        }

        // 言語を変更する
        public void ChangeLang(SUPPORT_LANG langval)
        {
            cultureInfo.CurrentUICulture = new System.Globalization.CultureInfo(SupportLangPrefix[langval]);
            CreateResCollection();
        }

        // 新しい言語を作成する
        void CreateResCollection()
        {
            Rescollection = resService.GetResourceProperties(typeof(AppResources));
        }
    }


Modelで持っているRescollection、PropertyChanged発行するために、毎回newされているのですがObservableなDictionaryでないのがいけてない感じですかね。。

3, vm-vで、modelのDictionaryをReactivePropertyで監視、言語変更指示

あとは、通常のBindablePropertyを扱う感覚でvm-vを実装します。

RegisterLanguagePageViewModel.cs
    public class RegisterLanguagePageViewModel : BindableBase, INavigationAware
    {
        private readonly INavigationService _navigationService;

        // view側からの言語変更指示
        public ReactiveCommand<LanguageModel.SUPPORT_LANG> RxToSelectLanguage { get; private set; }

        // modelの疑似resxプロパティ
        public ReactiveProperty<Dictionary<string, string>> RxKeyValue {get; private set;}

        // 言語表示用Dictionary
        public Dictionary<LanguageModel.SUPPORT_LANG, string> SelectLangDict { get; private set;}

        public RegisterLanguagePageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;

            // 言語を選択する
            RxToSelectLanguage = new ReactiveCommand<LanguageModel.SUPPORT_LANG>();
            RxToSelectLanguage.Subscribe(x => {
                LanguageModel.Instance.ChangeLang(x);
            });

            // 動的な疑似resxプロパティ
            RxKeyValue = LanguageModel.Instance.ToReactivePropertyAsSynchronized(x => x.Rescollection);

            // 言語選択用Dictionary
            SelectLangDict = new Dictionary<LanguageModel.SUPPORT_LANG, string>();
            foreach (LanguageModel.SUPPORT_LANG item in Enum.GetValues(typeof(LanguageModel.SUPPORT_LANG)))
            {
                // enumのattributeをstringに変更するDependencyService
                SelectLangDict.Add(item, DependencyService.Get<IEnumDescription>().GetDescription(item));
            }

        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }
RegisterLanguagePage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage 
        xmlns="http://xamarin.com/schemas/2014/forms" 
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
        xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" 
        prism:ViewModelLocator.AutowireViewModel="True" 
        x:Name="local"
        x:Class="RegisterLanguagePage">

        <StackLayout>
            <Label Text="{Binding RxKeyValue.Value[tx1]}" />

            <ListView ItemsSource="{Binding SelectLangDict}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout Padding="0, 10, 0, 10">
                                <Button Text="{Binding Value}" 
                                        Command="{Binding BindingContext.RxToSelectLanguage, Source={x:Reference local}}"
                                        CommandParameter="{Binding Key}"
                                        />
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>

            </ListView>
        </StackLayout>
</ContentPage>

いろいろ調べながらやってみて

最初、参考にあげているようなResource Resource = new Resource()のような形でやってみたのですが、なぜかResourceの値がとれなくてよくわからなかったのでこのような実装にしました。
サンプルはWPFだからなのかとか、ちょっとよくわからなかったので、パッと思いつくやりかたで実装してみました。
う〜んなんでとれなかったのだろう。。。
ひとまずこちらで問題なさそうなので、これで実装進めてみることにします。

その他

webの場合だとpoファイルがとても有効に動くのですが、C#でそれにあたるものが本当に見つからなかった。
具体的には、実装しながら文字列を入れていって、最終的にその文字列がキーになってそこから言語展開ファイルをつくっていくような逆算な感じの実装方法。
なので、resxではなくいい感じのソリューションがあれば是非教えていただきたいです。

参考

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6