🌗

アプリエンジニアがChatGPTにarbファイル分割管理の為のシェルスクリプト書いてもらった

2023/06/07に公開

こんにちは、スペースマーケットでモバイルエンジニアをしている村田です。

スペースマーケット外の私が関わっているプロジェクトですが、Flutterアプリ多言語対応するためにSDK標準パッケージのflutter_localizationsを使用しています。
flutter_localizationsには文言リソース管理ファイル(arbファイル)を分割する機能が備わっていない為、サービスが大きくなるにつれて1ファイルが肥大し続けてしまいます。その問題を解消すべく愚直な方法ではありますが、arbファイルを分割して管理するためのシェルスクリプトを作成しました。
普段シェルスクリプト書く機会があまりなく腰が重かったのですが、ChatGPTの力に頼ることで楽に作成できたのでその感想も交えつつ共有いたします。

プロジェクト概要

簡単に本プロジェクトのflutter_localizationsの設定とディレクトリ構成について触れます

flutter_localizations

プロジェクトルートへ設置する設定ファイル l10n.yaml についてです。

l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_ja.arb
output-localization-file: app_localizations.dart
output-class: L10n
  • arb-dir : 言語ファイル(XXX.arb)を設置するディレクトリを指定
  • template-arb-file : ベースとなる言語ファイルを指定

ディレクトリ構成

詳細は省きますがlib配下のディレクトリ構成についてです。
日本語と英語へ対応するため、l10n.yamlで指定したl10nディレクトリへ app_ja.arb app_en.arb ファイルを設置しています。presentationディレクトリ直下はドメイン毎のディレクトリ、その中へ画面毎にディレクトリを作成し、セットとなるWidgetファイル/ViewModelファイルを格納しています。

lib
├── l10n
│    ├── app_ja.arb
│    ├── app_en.arb
│    └── l10n.dart
├── presentation
│    ├── hoge
│    │    ├── hoge1
│    │    │    ├── widget
│    │    │    ├── hoge1.screen.dart
│    │    │    └── hoge1.viewModel.dart
│    │    └── hoge2
│    │         ├── widget
│    │         ├── hoge2.screen.dart
│    │         └── hoge2.viewModel.dart
│    ├── fuga
│    │    ├── fuga1
│    │    │    ├── widget
│    │    │    ├── fuga1.screen.dart
│    │    │    └── fuga1.viewModel.dart
│    │    └── fuga2
│    │         ├── widget
│    │         ├── fuga2.screen.dart
│    │         └── fuga2.viewModel.dart
│    ︙
│    └── root.screen.dart
︙
└── main.dart

理想の構成

arbファイルを分割し、以下のような構成にできればと考えました

  • hoge1画面が不要になった際にその画面で参照している文言もディレクトリごと削除可能(いちいちapp_ja/app_enからhoge1画面に関する文言を探し、削除するという手間が省ける)
  • 後から文言追加する際もapp_ja/app_enから該当画面の文言が記載されている位置を探し、ズレがないよう追記するストレスがなくなる
  • common_l10nを作成することで画面問わず利用する文言であることを強調可能(app_ja/app_enで1元管理だとネーミングで判断する必要があるかなと思います)
lib
├── l10n
│    ├── app_ja.arb
│    ├── app_en.arb
│    └── l10n.dart
├── presentation
│    ├── hoge
│    │    ├── hoge1
+    │    │    ├── hoge1_l10n
+    │    │    │    ├── hoge1_ja.arb
+    │    │    │    └── hoge1_en.arb
│    │    │    ├── widget
│    │    │    ├── hoge1.screen.dart
│    │    │    └── hoge1.viewModel.dart
│    │    └── hoge2
+    │         ├── hoge2_l10n
+    │         │    ├── hoge2_ja.arb
+    │         │    └── hoge2_en.arb
│    │         ├── widget
│    │         ├── hoge2.screen.dart
│    │         └── hoge2.viewModel.dart
│    ├── fuga
│    │    ├── fuga1
+    │    │    ├── fuga1_l10n
+    │    │    │    ├── fuga1_ja.arb
+    │    │    │    └── fuga1_en.arb
│    │    │    ├── widget
│    │    │    ├── fuga1.screen.dart
│    │    │    └── fuga1.viewModel.dart
│    │    └── fuga2
+    │         ├── fuga2_l10n
+    │         │    ├── fuga2_ja.arb
+    │         │    └── fuga2_en.arb
│    │         ├── widget
│    │         ├── fuga2.screen.dart
│    │         └── fuga2.viewModel.dart
+    ├── common_l10n
+    │    ├── common_ja.arb
+    │    └── common_en.arb
│    ︙
│    └── root.screen.dart
︙
└── main.dart

シェルスクリプト構成

ざっくりですが以下の流れで実現させます

  1. app_ja.arb app_en.arb を読み込み、ファイルを空にする
  2. ./lib/presentation 配下から *_ja.arb *_en.arb にヒットするファイルを探索し、それぞれ配列に格納
  3. 配列をループで1ファイルずつ取得
  4. ファイルを1行ずつ読み込み、必要な行のみそれぞれ app_ja.arb app_en.arb へ書き込み

シェルスクリプト作成

冒頭に記載した通り今回ChatGPTに全頼りでシェルスクリプトを作成しました。
全ては長過ぎて添付できない為、冒頭と要点のみ共有します。

まず最終的に仕上がったものを置いておきます👐

完成したスクリプト
function make_loalizations() {
  local readonly app_ja_file=./lib/l10n/app_ja.arb
  local readonly app_en_file=./lib/l10n/app_en.arb

  # ファイルを空にする
  : > $app_ja_file
  : > $app_en_file

  files_ja_path_array=`find ./lib/presentation -name *_ja.arb`
  files_en_path_array=`find ./lib/presentation -name *_en.arb`

  # JA
  log_title "Make app_ja.arb"
  make_app_localization $app_ja_file "${files_ja_path_array[@]}"
  # EN
  log_title "Make app_en.arb"
  make_app_localization $app_en_file "${files_en_path_array[@]}"
}

function make_app_localization() {
  local readonly app_arb_file=$1
  local readonly files_path_array=$2

  echo "{" >> $app_arb_file

  for file in $files_path_array; do
    log_green "💡 $file"
    while IFS= read -r file_line || [[ -n "$file_line" ]]; do
      # 先頭行/最終行を飛ばす
      if [ "$file_line" == "{" ] || [ "$file_line" == "}" ] ; then
        continue
      fi

      # 末尾が「"」または「  }」であれば「,」を追加
      if [ ${file_line: -1} == '"' ] || [ "$file_line" == "  }" ] ; then
        printf '%s\n' "$file_line," >> $app_arb_file
      else
        printf '%s\n' "$file_line" >> $app_arb_file
      fi
    done < $file
  done

  # 最終行の末尾「,」を削除
  sed -i '' '$s/,$//' $app_arb_file

  echo "}" >> $app_arb_file
}

function log_title() {
  local readonly message=$1
  log_green "/-----------------------------/"
  log_green "/  ⚡️  $message ⚡️"
  log_green "/-----------------------------/"
}

function log_green() {
  IFS=$'\n'
  printf '\033[32m%s\033[m\n' $1
}

make_loalizations

冒頭

腰が低い...!

確認の仕方までフォローしてくれて抜かりない

あーーーーん!そうだreadonlyだー!
Web検索でもヒットするとは思いますが、余計な情報に触れることなくコードに反映された状態で答えが即座に得られるのは嬉しいですね

ほうほう..

...と、ラリーを繰り返しまずは荒く骨組みを完成させました

要点

細かい箇所は別途質問をして詰めていきました。
慣れている方からしたらなんてことない問題かと思いますが、例えば while read でファイルを1行ずつ読み込む際に以下の考慮ができていませんでした。

  • インデント(行頭の空白)が無視されてしまう
  • 最終行が無視されてしまう
test.arb
{
  "test", "テスト",
  "test2", "テスト2"
}
test.sh
while read -r line; do
    echo "$line"
done < "test.arb"
出力
$ sh test.sh
{
"test", "テスト",
"test2", "テスト2"

インデント(行頭の空白)が無視されてしまう

言われるがまま IFS= を追加することで解消されました💡

test.sh
while IFS= read -r line; do
    echo "$line"
done < "test.arb"
出力
$ sh test.sh
{
  "test", "テスト",
  "test2", "テスト2"

最終行が無視されてしまう

こちらもその通りコード修正することで解消されました💡

test.sh
while IFS= read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "test.arb"
出力
$ sh test.sh
{
  "test", "テスト",
  "test2", "テスト2"
}

などなど細かい不具合も切り出して質問することで解消でき、例外的な考慮はできていませんが無事完成させる事ができました!

感想

シェルスクリプトの作成不慣れな自分ですが、ChatGPT協力のもと希望通り動くものを完成させることができました(完成度はともかく)。

AIの進化にビビり倒していたので頼らないぞ...という気持ちでしたが以下のツイートをみて「確かに!」と思い今回がっつり利用してみました。
(因みにヒカルの碁大好きで、実家に全巻あります。三谷の姉ちゃん好きだったなあ...)

「君が今僕を支えて 僕が今君を支える だから迷いながらも共に生きていこうよ 未来へと」

(出典:アニメ「ヒカルの碁」OP曲「Get Over」)

まさにこれ。敵じゃなく、師であり相棒のような、でも少しビビりつつ。
そんな気持ちでChatGPT(AI)と共に生きていきます。

最後に

スペースマーケットでは一緒に働く仲間を募集中です!
詳しくは以下採用ページをご確認ください。

https://spacemarket.co.jp/recruit/engineer/

https://www.wantedly.com/projects/1061116

https://www.wantedly.com/projects/1113570

https://www.wantedly.com/projects/1113544

GitHubで編集を提案
スペースマーケット Engineer Blog

Discussion