Experiments Never Fail

Xamarin.Android でアプリの言語を動的に切り替える

複数言語のリソースを用意しておいて、システムの言語を変えると、アプリで使用される言語リソースも変わるわけですが、システム設定に関係なく、アプリ内で言語選択をしたい。

つまり、

これ。
Kotlin でもできたので、どうせならということで Xamarin.Android でもやってみました。

できあがり #

こんな感じの成果になります。

Untitled.gif

方法 #

1. 多言語用のリソースファイルを用意する #

Android の仕様に従って values/values-ja-rJP/String.xml を用意します。
ファイルを追加した後で、Build Action が「AndroidResource」になっている事を確認してください。

values/String.xml (英語ってかデフォルト):

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ResourceTest</string>

<string name="welcome">WELCOME</string>
<string name="to_japanese">To Japanese</string>
<string name="to_english">To English</string>
</resources>

values-ja-rJP/String.xml (日本語):

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ResourceTest</string>

<string name="welcome">ようこそ</string>
<string name="to_japanese">日本語にする</string>
<string name="to_english">英語にする</string>
</resources>

2. MainActivity を実装する #

とりあえずざっと。

MainActivity.cs:

using Android.App;
using Android.Widget;
using Android.OS;
using Android.Content;
using Android.Content.Res;
using Java.Util;
using System.Linq;
using System;

namespace ResourceTest
{
[Activity(Label = "ResourceTest", MainLauncher = true, Icon = "@mipmap/icon")]
public class MainActivity : Activity
{
protected override void AttachBaseContext(Context baseContext)
{
var pref = baseContext.GetSharedPreferences("mypref", FileCreationMode.Private);

var locale = pref.GetString("locale", string.Empty);
var newLocale = Locale.GetAvailableLocales().FirstOrDefault(
l => string.Equals(l.ToString(), locale, StringComparison.OrdinalIgnoreCase))
?? Locale.Default;

var configuration = baseContext.Resources.Configuration;
configuration.Locale = newLocale;
var newContext = new ContextWrapper(
baseContext.CreateConfigurationContext(configuration));


base.AttachBaseContext(newContext);
}

protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);

FindViewById<TextView>(Resource.Id.textWelcome).Text = Resources.GetString(Resource.String.welcome);

var pref = this.GetSharedPreferences("mypref", FileCreationMode.Private);

FindViewById<Button>(Resource.Id.buttonToEnglish).Click += (sender, e) =>
{
var editor = pref.Edit();
editor.PutString("locale", "en_US");
editor.Commit();
Restart();
};

FindViewById<Button>(Resource.Id.buttonToJapanese).Click += (sender, e) =>
{
var editor = pref.Edit();
editor.PutString("locale", "ja_JP");
editor.Commit();
Restart();
};
}

private void Restart()
{
var intent = new Intent(this, typeof(MainActivity));
this.Finish();
this.StartActivity(intent);
}
}
}

簡単に説明すると 「AttachBaseContext() を override して、そこで任意の Locale に変えた Context にすげ替え」ています。

「任意の Locale」は、2つのボタンを押したときにそれぞれ ja_JPen_US を SharedPreference に保存しておき、Activity を再起動します。

再起動直後に AttachBaseContext() が呼ばれるので、そこで SharedPreference に記憶された Locale を読み出しています。

おまけ #

1st try では、SharedPref を使うのを面倒くさがって、Application クラスに記憶させとく作戦でしたが、失敗しました。その原因は AttachBaseContext()OnCreate() よりも先に呼ばれ、さらに AttachBaseContext() の時点では Activity.Applicationnull になっているためでした。

published at tags: Android C# Xamarin