アプリの言語設定を、アプリ内から操作したい
通常、アプリの言語設定はiOS本体の設定が反映されるようになっており、アプリから制御することはできません。
「OSの言語設定を日本語のままにしつつ、アプリ内の言語設定を英語表示にしたい」というようなニーズがあるのかどうか知りませんが、現状のiOSではできないのです。(なお、Androidでは余裕で可能)
アプリが参照する言語設定について
Launch Arguments & Environment Variables : NSHipster
という記事によれば、アプリの言語設定は アプリケーションの引数 として与えられて、NSUserDefault
へ格納され、その値が参照されるとのこと。
より厳密に言えば、NSUserDefault
のNSArgumentDomain
に格納されるみたいです。
UserDefaultのドメインの概念についてはClassReferenceに書いてあるのだけど、通常アプリがNSUserDefault
に書き込んだ場合、/Library/Preferences/com.example.Application.plist
というファイルに書き込まれます。
(アプリケーションのBundleIDがcom.example.Application
の場合)
一方で、アプリケーションの引数として与えられた言語や環境設定はNSArgumentDomain
に格納されます。その実体は/Library/Preferences/.GlobalPreferences.plist
であり、値の読み込み/書き込みに関する優先順位は最も高くなっています。
アプリの言語設定はAppleLanguages
というキーで格納されています。中身は@[@"en", @"ja", @"fr"]
といったような、言語コードの文字列の配列です。アプリは自身が対応する言語と、このAppleLanguages
配列の要素を、先頭から順に合致するものがないか走査し、一致したらその言語でアプリを表示する…という仕組みになっているのだと考えられます。
となれば、AppleLanguages
配列を書き換えてしまえば、アプリ自身によって言語設定を操作できるのではないか?と考えたわけです。
結論としては無理だったんだけど、一応失敗した方法を載せてみます。
アプリ言語設定上書き手法
この辺は趣味なんだけど、まずアプリ内言語設定用の列挙体を用意して、
/**
言語設定用の列挙体
*/
typedef NS_ENUM(NSUInteger, LaunageType){
kLanguageEnglish = 1000,
kLaungageJapanese = 1001,
};
言語切替用UIButton
のtag
に、対応する列挙体の値を設定しました。
あとはsender
から受け取ったtag
の値に応じて、.GlobalPreferences.plist
を書き換えるメソッドを作成するだけ。
/**
アプリの言語設定を切り替えます。
@param sender 言語設定切り替えボタン。
tagに対象言語を示す値(LanguageType列挙体の値)を設定する。
*/
- (IBAction)changeLanguage:(UIButton *)sender
{
NSString *targetLanguage = nil;
switch ((LaunageType)sender.tag) {
case kLanguageEnglish:
targetLanguage = @"en";
break;
case kLaungageJapanese:
default:
targetLanguage = @"ja";
break;
}
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSMutableArray *itsArray = [[userDefault valueForKey:@"AppleLanguages"] mutableCopy];
NSUInteger position = [itsArray indexOfObject:targetLanguage];
if(position == NSNotFound){
return;
}
id targetElement = [itsArray objectAtIndex:position];
[itsArray removeObjectAtIndex:position];
[itsArray insertObject:targetElement atIndex:0];
[userDefault setObject:[itsArray copy] forKey:@"AppleLanguages"];
[userDefault synchronize];
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *viewController = [storyBoard instantiateViewControllerWithIdentifier:@"HogeViewController"];
[self.navigationController setViewControllers:@[viewController] animated:NO];
}
sender
のtag
から選択された言語設定を判定し、言語コードの文字列にする。UserDefault
のAppleLanguages
の配列を取得する。この値はアプリをインストールした時点でOSが自動的に.GlobalPreferences.plist
に格納している。- 並び替えのために
mutableCopy
する。 - 目的の言語が先頭に来るように並び替えを行う(この辺もうちょい綺麗に書けるはず)。
AppleLanguages
を再設定する。先述した通り、NSArgumentDomain
の優先順位は最も高いため、.GlobalPreferences.plist
の値が上書きされる。- 何らかの方法で新しい言語設定の値で再描画する
残念ながら、6.の部分を実現する方法がありませんでした。
上のコードではUIViewController
を再生成してloadView
が呼び出されるようにすれば、新しい言語設定が反映されるのでは?と考えて書いたのですが、どうも.GlobalPreferences.plist
はアプリ起動中にはインメモリに保持されているみたいで、アプリ動作中に動的に変更するというのは不可能みたいでした。
一応、アプリをタスクからkillして、再起動させれば言語設定を反映させることができるのですが、タスクkillという操作をユーザーに行わせるというのは負けた気分…(そもそも申請通るのか謎ですけど)。
同じようなことを考えている人は世の中いるみたいで、stackOverflowを漁ってみましたが、どうも無理みたいですね。
現状での正答
もしOSの言語設定と、アプリの言語設定を別のものにしたい場合。
アプリ側で独自に言語設定を保持しておき、localizedStringForKey:value:table:
を利用して、対応する
stringsテーブルからローカライズ文字列を取り出すようにする、というのが現状の正答かなと思います。
ただしこの方法には、アプリ外が出す文字列までは制御できない(例えばStore Kit
が出すアラートダイアログなど)という致命的な欠陥があります。