くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Flutterで端末の言語コードを取得するあれこれ(Localizations/PlatformDispatcher)

Flutterで多言語化したいなと思い、いろいろ調べてみたときの備忘録(*´ω`*)

よくみるのはこんなコードだけど、

Locale locale = Localizations.localeOf(context);

日本語と英語のみをサポートしているときに、

  • 簡体中文(デフォルト)
  • 日本語
  • English

みたいな感じの並びで、デフォルトが中国語の場合だと、
英語で表示してほしいけど、日本語が採用されてしまう。。

Localizations版の動き

いろいろ試してみるとこんな感じっぽい

  • Localizations.localeOf()はアプリでの表示言語を取得
  • MaterialApplocaleで設定したものが最優先
  • システムの言語設定にある一覧の上から順番に試し、サポートされてる言語が採用
  • 一致する言語がない場合、サポート言語の1つ目が採用
MaterialApp.router(
  // ...
  locale: Locale("en", "US"),
  // ...
),

なので、

  • 簡体中文(デフォルト)
  • 日本語
  • English

の場合は、日本語が採用され、

  • 簡体中文(デフォルト)
  • English
  • 日本語

の場合は、英語が採用され、

  • 簡体中文(デフォルト)

の場合は、サポート言語の並び順が、
英語→日本語の場合は、英語が採用される

デフォルトのLocale解決アルゴリズムは、
このあたりに書かれているっぽい。

Locale解決アルゴリズムのカスタマイズ

このアルゴリズムは、localeListResolutionCallback
カスタマイズできるっぽい

たとえば、以下の並びで、

  • 簡体中文(デフォルト)
  • 日本語
  • English

サポートしてない言語のときには、
必ず英語で表示してほしい場合は、こんな感じ

MaterialApp.router(
  // ...
  localeListResolutionCallback: (locales, supportedLocales) {
    // デバイスで設定されているロケールの取得
    final currentLocale = PlatformDispatcher.instance.locale; 
    debugPrint('current=$currentLocale device=$locales supported=$supportedLocales');
    // 取得できない場合は英語を返す
    if (locales == null) return const Locale('en', 'US');
    
    for (var locale in supportedLocales) {
      // サポートされてる言語の場合は、その言語を返す
      if (currentLocale.languageCode == locale.languageCode) {
        return locale;
      }
    }
    // それ以外は、英語を返す
    return const Locale('en', 'US');
  },
  // ...
),

PlatformDispatcher版

上記で出てくるPlatformDispatcher.instance.localeで、
バイスに設定されてる言語情報を取得できるっぽい

// デフォルトの言語
Locale locale = PlatformDispatcher.instance.locale;

// 言語の一覧
List<Locale> locales = PlatformDispatcher.instance.locales;

BuildContextを使わないので、便利っぽいけど、
非サポート言語のfallbackはないので、Localizations.localeOf()のほうが安全。

ただ、runApp前にも使えるので、
riverpodを使った言語切替機能などのときは便利。

@Riverpod(keepAlive: true)
Locale currentLocale(CurrentLocaleRef ref) {
  throw UnimplementedError();
}

FutureOr<void> main() async {
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  
  // 端末の言語を取得する
  final currentLocale = PlatformDispatcher.instance.locale;
  // TODO: フォールバック処理は適宜
  
  runApp(ProviderScope(
    overrides: [
      // 取得したcurrentLocaleで上書き
      currentLocaleProvider.overrideWithValue(currentLocale),
    ],
    child: const App(),
  ));
};

class App extends HookConsumerWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Providerから現在の言語を取得
    final locale = ref.watch(currentLocaleProvider);

    return MaterialApp.router(
      // ...
      // 現在の言語を設定
      locale: locale,
      // ...
    );
  }
}

以上!! なんとなくわかった気がする(*´ω`*)

参考にしたサイトさま