localeに繁體中文を設定してもvalues-zhが選択されない
今回もマニアックなトピックです。
タイトルの通り。ちょっと困ったので調べて見た。
何が起こったか
下記どちらかに当てはまる場合、
- 端末で繁體中文を言語設定で選択している
- アプリ独自のlocale設定で
zh_HK(中国語-香港)を設定している
res 下の values-zh/strings.xml が読まれず res/strings.xml が選択された。
例えば、values/strings.xml と、
<resources> <string name="test">Test</string> </resources>
values-zh/strings.xml がある場合、
<resources> <string name="test">測試</string> </resources>
言語設定を繁體中文にしていても測試ではなくTestが表示される。同じ中国語(zh)ならvalues-zhが優先されると思いきや、そんなことない。
ここでvalues-zh/strings.xmlをvalues-zh-rHK/strings.xml にすれば測試になってまあ当面困ることはないが釈然としない。
ちなみに確認環境はNexus 6P (OS 7.1.2) 実機。
N以降でおきてるっぽい
そういえばNからLocale周りで変更入ったな?🤔 というのを思い出した。
試しにEmulator (Nexus 5 / API 23) で動かして見たらvalues-zh/strings.xml が選択されて測試が表示された。
同じEmulator環境でもN系のものはだめ。(Nexus 6P / API 25とNexus 5X / API 23 で確認。)
一瞬Multi localeになった影響でEnglishがlocale listの二番目にきている影響か?と思ってEnglishを削除して見たけどだめ。
よく考えたらそもそもvalues-enを作ってないテスト環境でも再現したから影響しようがなかった。
Frameworkのコードを読んでみよう
N以降のFrameworkのコードを読んでみる。ResourcesImpl.java#320 あたりから見ていくと読みやすいかな。
この辺りのコードでbestLocaleを取ってきて設定しているっぽい。
320 public void updateConfiguration(Configuration config, DisplayMetrics metrics, 321 CompatibilityInfo compat) { 351 // If even after the update there are no Locales set, grab the default locales. 352 LocaleList locales = mConfiguration.getLocales(); 353 if (locales.isEmpty()) { 354 locales = LocaleList.getDefault(); 355 mConfiguration.setLocales(locales); 356 } 357 358 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) { 359 if (locales.size() > 1) { 360 // The LocaleList has changed. We must query the AssetManager's available 361 // Locales and figure out the best matching Locale in the new LocaleList. 362 String[] availableLocales = mAssets.getNonSystemLocales(); 363 if (LocaleList.isPseudoLocalesOnly(availableLocales)) { 364 // No app defined locales, so grab the system locales. 365 availableLocales = mAssets.getLocales(); 366 if (LocaleList.isPseudoLocalesOnly(availableLocales)) { 367 availableLocales = null; 368 } 369 } 370 371 if (availableLocales != null) { 372 final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( 373 availableLocales); 374 if (bestLocale != null && bestLocale != locales.get(0)) { 375 mConfiguration.setLocales(new LocaleList(bestLocale, locales)); 376 } 377 } 378 } 379 }
では「何がbestなのか」を評価するためのロジックはどうなっているかというと、LocaleList.java#302 あたり。
302 @IntRange(from=0, to=1) 303 private static int matchScore(Locale supported, Locale desired) { 304 if (supported.equals(desired)) { 305 return 1; // return early so we don't do unnecessary computation 306 } 307 if (!supported.getLanguage().equals(desired.getLanguage())) { 308 return 0; 309 } 310 if (isPseudoLocale(supported) || isPseudoLocale(desired)) { 311 // The locales are not the same, but the languages are the same, and one of the locales 312 // is a pseudo-locale. So this is not a match. 313 return 0; 314 } 315 final String supportedScr = getLikelyScript(supported); 316 if (supportedScr.isEmpty()) { 317 // If we can't guess a script, we don't know enough about the locales' language to find 318 // if the locales match. So we fall back to old behavior of matching, which considered 319 // locales with different regions different. 320 final String supportedRegion = supported.getCountry(); 321 return (supportedRegion.isEmpty() || 322 supportedRegion.equals(desired.getCountry())) 323 ? 1 : 0; 324 } 325 final String desiredScr = getLikelyScript(desired); 326 // There is no match if the two locales use different scripts. This will most imporantly 327 // take care of traditional vs simplified Chinese. 328 return supportedScr.equals(desiredScr) ? 1 : 0; 329 }
まず完全にLocaleが一致するか見ている。ここは一致しないはずなので次へ。
次にLonguageが一致しないか見ている。ここは一致するので次へ。
次にPseudo Localeか見ている。ここは当てはまらないので次へ。
最後ににscriptが一致するかを見ている。ISO15924で繁体字は Hant と定められていて、これがいわゆる中国語の簡体字Hansとは別扱いだった。
わざわざコメントでも書いてあった。
There is no match if the two locales use different scripts. This will most imporantly take care of traditional vs simplified Chinese.
まとめ
というわけで、values-zhは簡体字扱いなので、端末の設定を繁體中文(繁体字)にしても文字が違うということで選択されなかったという結論になった。スッキリ!
LocaleといえばLanguageとCountry (region) くらいしか気にしていなかったけれど、script(文字) も気にしないといけなかった。 Referenceにも当然だけどscriptのことが書いてあった。ただ、これだけだとscriptがmatchingのロジックにどう関わるかわからないんじゃないかなぁ… というわけでたまにはFrameworkのコード読むの大事!٩( ‘ω’ )و