Zubora Code

Flutterでアイコンをタップしたら音が出るシンプルなアプリを開発してリリースする 2/n 多言語対応

Flutterでアイコンをタップしたら音が出るシンプルなアプリを開発してリリースするまでをまとめるシリーズの2記事目です

Published: 2 August, 2023
Revised: 2 August, 2023

概要

現在1歳1ヶ月の息子向けに、動物と乗り物のアイコンをタップすると音が鳴るシンプルなアプリを作っています。これをiOSとAndroid両方でリリースすることをゴールとしています。

前回まで

記事はこちらのタグに紐づけています。

https://zubora-code.net/ja/tags/first_flutter_tutorial

今回のゴール

前回で機能自体の実装は完了しました。今回の記事では、日本語と英語の両方に対応する方法を簡単に整理して書きます。完成系はこちらです。


実装

ライブラリのインストール

いくつか多言語対応用のライブラリがあるようですが、迷ったら公式ですね。flutter_localizations をインストールします。

https://docs.flutter.dev/accessibility-and-localization/internationalization

$ flutter pub add flutter_localizations --sdk=flutter
$ flutter pub add intl:any

MaterialAppのdelegateと対応localesを設定

 import 'package:flutter/material.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
 import 'Sound.dart';
 
 void main() {
@@ -12,6 +13,15 @@ class MyApp extends StatelessWidget {
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'ぴよぴよサウンドパーク',
+      localizationsDelegates: const [
+        GlobalMaterialLocalizations.delegate,
+        GlobalWidgetsLocalizations.delegate,
+        GlobalCupertinoLocalizations.delegate,
+      ],
+      supportedLocales: const [
+        Locale('en'),
+        Locale('ja'),
+      ],
       theme: ThemeData(
         colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
         useMaterial3: true,

pubspec.yamlにgenerate flag: trueを追加

ライブラリインストール時に多言語対応のファイルが自動生成される設定を追加します。

 flutter:
-
+  generate: true

l10n.yaml の追加

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

lib/l10n/app_en.arb, lib/l10n/app_ja.arb の追加

{
  "appTitle": "PiyoPiyo Sound Park",
  "@appTitle": {
    "description": "The app title"
  },
  "animal": "Animal",
  "@animal": {
    "description": "Animal"
  },
  "vehicle": "Vehicle",
  "@vehicle": {
    "description": "Vehicle"
  }
}

{
    "appTitle": "ぴよぴよサウンドパーク",
    "animal": "どうぶつ",
    "vehicle": "のりもの"
}


app_en.arbをテンプレートファイルに指定しています。app_ja.arbは翻訳ファイルです。

flutter run コマンドを実行して .dart_tool/gen_l10n を自動生成

$ flutter run

main.dartからcontextに応じた値を取り出す

 git diff lib/main.dart 
diff --git a/lib/main.dart b/lib/main.dart
index 3b9397a..f3414a3 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'Sound.dart';
 
 void main() {
@@ -12,29 +13,25 @@ class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: "ぴよぴよサウンドパーク",
       localizationsDelegates: const [
+        AppLocalizations.delegate,
         GlobalMaterialLocalizations.delegate,
         GlobalWidgetsLocalizations.delegate,
         GlobalCupertinoLocalizations.delegate,
       ],
-      supportedLocales: const [
-        Locale('en'),
-        Locale('ja'),
-      ],
+      supportedLocales: AppLocalizations.supportedLocales,
       theme: ThemeData(
         colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
         useMaterial3: true,
       ),
-      home: const MyHomePage(title: 'ぴよぴよサウンドパーク'),
+      home: MyHomePage(),
     );
   }
 }
 
 class MyHomePage extends StatefulWidget {
-  const MyHomePage({super.key, required this.title});
-
-  final String title;
+  const MyHomePage({super.key});
 
   @override
   State<MyHomePage> createState() => _MyHomePageState();
@@ -141,23 +138,24 @@ class _MyHomePageState extends State<MyHomePage> {
 
   @override
   Widget build(BuildContext context) {
+    final l10n = AppLocalizations.of(context)!;
     return Scaffold(
       appBar: AppBar(
         backgroundColor: Theme.of(context).colorScheme.inversePrimary,
-        title: Text(widget.title),
+        title: Text(l10n.appTitle),
       ),
       body: Center(
         child: _widgetOptions.elementAt(_selectedIndex),
       ),
       bottomNavigationBar: BottomNavigationBar(
-        items: const <BottomNavigationBarItem>[
+        items: <BottomNavigationBarItem>[
           BottomNavigationBarItem(
-            icon: Icon(Icons.pets),
-            label: 'どうぶつ',
+            icon: const Icon(Icons.pets),
+            label: l10n.animal,
           ),
           BottomNavigationBarItem(
-            icon: Icon(Icons.directions_car),
-            label: 'のりもの',
+            icon: const Icon(Icons.directions_car),
+            label: l10n.vehicle,
           ),
         ],
         currentIndex: _selectedIndex,

最後に

肌感覚としてはちょっと面倒でしたが、ルーティングとかがない分Web FrontEndよりは楽でした。せっかく世界に向けて公開できるのに日本語だけに閉じてしまうのはもったいないので、今後の開発でも多言語対応は積極的に行なっていきたいですね。


今回の修正は以下のPull Requestです。
https://github.com/tkugimot/touch_and_hear/pull/2?w=1


あと残りやりたいことは以下です。

  • Crashlyticsの導入 (監視のため)
  • AdMobの導入 (広告のため)
  • アイコン/フィーチャーグラフィックの作成・設定
  • サポートサイト/プライバシーポリシーサイトの作成
  • Drawerの追加(アプリの使い方/お問い合わせ/プライバシーポリシーへのリンク)
  • Androidへのリリース
  • iOSへのリリース

次の記事ではCrashlyticsを導入しようと思います。

Toshimitsu Kugimoto

Software Engineer

仕事では決済やメディアのWebやスマホアプリのBE開発、WebのFE開発をやっています。 JavaとTypeScriptをよく使います。プライベートではFlutterでのアプリ開発にも挑戦中です。