【.Net/Xamarin】I18N-Portableによる多言語対応


## I18N-Portable
https://github.com/xleon/I18N-Portable

Xamarinと.Netに対応していて、UWPアプリでも利用できます。

## i18nの初期化

アプリリソース内からローカリゼーションデータのテキストリソースを巡回するため、初期化にはAppクラスのAssemblyが必要です。

```csharp
I18NPortable.I18N.Current
    .Init(GetType().Assembly);
```

## Localesフォルダに翻訳データを配置

デフォルトではKeyValuePairの形式で翻訳テキストを記述します。

* 翻訳データファイルはファイルのプロパティから**ビルドアクションを「埋め込みリソース**」に指定する
* ファイル名は[言語コード](https://msdn.microsoft.com/en-us/library/ee825488%28v=cs.20%29.aspx)（ja-JPやen-USなど）である必要があります。

```text:Locales/ja-JP.txt

# key = value (the key will be the same across locales)
one = uno
two = dos
three = tres 
four = cuatro
five = cinco
  
# Enums are supported
Animals.Dog = Perro
Animals.Cat = Gato
Animals.Rat = Rata
Animals.Tiger = Tigre
Animals.Monkey = Mono
 
# Support for string.Format()
stars.count = Tienes {0} estrellas
 
TextWithLineBreakCharacters = Line One\nLine Two\r\nLine Three
 
Multiline = Line One
    Line Two
    Line Three

```

Jsonで扱いたい場合は別途Readerの追加指定が必要
https://github.com/xleon/I18N-Portable#supported-formats




## 使用例：コードからローカライズ

```Locales/ja-JP.txt
YourName = 君の名は
```

```csharp
var localizedText = "YourName".Translate();
```

## 使用例：Xamlでローカライズ

### MarkupExtensionを利用する場合

```csharp
using System;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Markup;

namespace I18NPortable.Xaml.Extensions 
{
    [MarkupExtensionReturnType(ReturnType = typeof(string))]
    public class LocalizeExtension : MarkupExtension
    {
        public object Key { get; set; }

        public object[] Parameters { get; set; }

        protected override object ProvideValue()
        {
            if (Key is Enum enumValue)
            {
                return enumValue.Translate();
            }
            else if (Key is string keyStr)
            {
                if (Parameters is null)
                {
                    return I18N.Current.Translate(keyStr);
                }
                else
                {
                    return I18N.Current.Translate(keyStr, Parameters);
                }
            }
            else
            {
                throw new NotSupportedException("LocalizeExtension not supported Type: " + Key?.GetType().Name);
            }
        }
    }
}
```

```xml
<Page xmlns:i18nExt="using:I18NPortable.Xaml.Extensions">
  <Text Text="{i18nExt:Localize Key=YourName}" />
</Page>
```

### ValueConverterを利用する場合

```csharp:LocalizeConverter.cs

using System;
using Windows.UI.Xaml.Data;
using I18NPortable;

namespace MyApp.Views.Conveters
{
    public sealed class LocalizeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is Enum enumValue)
            {
                return enumValue.Translate();
            }
            else if (value is string stringValue)
            {
                if (parameter is string str)
                {
                    return stringValue.Translate(str);
                }
                else if (parameter is object[] p)
                {
                    return stringValue.Translate(p);
                }
                else
                {
                    return stringValue.Translate();
                }
            }
            else
            {
                throw new ArgumentException("Failed localize : " + value?.ToString());
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}


```


```Page.xaml
<Page xmlns:localization="using:MyApp.Views.Conveters">
  <prism:PrismApplication.Resources>
    <localization:LocalizeConverter x:Key="LocalizeConverter" />
  </prism:PrismApplication.Resources>

  <Grid>
    <TextBlock Text="{Binding Source=YourName, Converter={StaticResource LocalizeConverter}}" />
  </Grid>
</prism:PrismApplication>
```

### Resourcesにi18n.Currentを配置する場合

```csharp:App.xaml
Resources["Strings"] = I18NPortable.I18N.Current;
```

```
Text="{Binding Source={StaticResource Strings}, Path=[YourName]}"
```

（UWPでチェックしただけなのでWPFで動作するかはわかりません）

## MarkupExtensionとValueConverterの使い分けについて

Bindingが絡む動的な値に対応したいところではConverterを使い、それ以外の固定テキストのローカライズにはMarkupExtension（i18nExt:Localize）や Resourcesにi18n.Currentを配置する方の方法を使用してます。

本当は`Text="i18nExt:Localize Key={Binding SomeValue}"`みたいな書き方がしたいんですが、MarkupExtensionクラスがDependencyObjectを継承していないため無理な模様。（WPF/UWPの両方で無理）

MarkupExtension with binding parameters - stack overflow
https://stackoverflow.com/questions/10328802/markupextension-with-binding-parameters/10328974

また、MarkupExtensionの方について、WPFの場合は引数付きコンストラクタを定義すると`Key=`を省いて記述出来ます。UWPではおそらく非対応。

https://social.msdn.microsoft.com/Forums/vstudio/en-US/59be22e4-b766-48d2-9326-b829b0fafeef/uwpvbxamlcustom-markupextension-not-working-with-positional-arguments?forum=wpdevelop

## 注意点

本家で紹介されているViewModelからi18N.Currentをプロパティとして公開して`{Binding Strings[key]}`とする方法でなければ言語変更に動的に対応することが出来ません。

ここで紹介したConveterを使った方法でアプリ内から言語を変更する場合には、ページのキャッシュを破棄してロードし直すか、言語変更後にアプリ再起動を促すUIを追加し再起動して貰う、などする必要があります。