表情筋を鍛えるアプリをリリースした【Next.jsでの多言語対応＋動的OGP＋face-api.jsで表情認識】

先週[表情を鍛えることができるアプリを作った](https://qiita.com/yuikoito/items/a0c588e19999e50f5a0b)という記事を書きましたが、今週はそのときのアプリを大幅に改善してリリースしたのでその紹介をします。

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">表情筋を鍛えるアプリをリリースしました！<br>自粛期間中で死にがちな表情筋をゲームで鍛えましょう 👍<br>レベルが選べるようになってて、寿司打みたいにクリアしたらどんどん次のお題が出ます。<br> <br>面白いと思ったら拡散してもらえると嬉しいです😊<a href="https://t.co/VfgmdyWCdk">https://t.co/VfgmdyWCdk</a><a href="https://twitter.com/hashtag/%E8%A1%A8%E6%83%85%E7%AD%8B%E3%83%81%E3%83%A3%E3%83%AC%E3%83%B3%E3%82%B8?src=hash&amp;ref_src=twsrc%5Etfw">#表情筋チャレンジ</a> <a href="https://t.co/W8Qh1YXFZ8">pic.twitter.com/W8Qh1YXFZ8</a></p>&mdash; Yui ☁ Yuiko Ito (@yui_active) <a href="https://twitter.com/yui_active/status/1437600731356282885?ref_src=twsrc%5Etfw">September 14, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



アップデート内容は主に以下です。

- 多言語化対応
- 難しさを選択できるように
- 動的OGP対応
- 成功したら次に進めるように
- 全体的なデザインのアップデート


全体のコードは以下にあるので興味のある方は見てください。
https://github.com/yuikoito/face-expression-challenge

アプリのURL→https://face-expression-challenge.vercel.app/

## 難しさを選択できるようにした

毎回同じ難しさだと好ましくないなと思ったので、難しさを選べるようにしました。
簡単、普通、難しい、鬼で選べます。
それぞれで時間を変えた他、スレッショルドの値も変更して、判定自体厳しくしています。

表情を取得してる部分で以下のようにスレッショルドを利用。（今回からdetectSingleFaceにしました）

```react
      const detectionsWithExpression = await faceapi
        .detectSingleFace(video, new faceapi.TinyFaceDetectorOptions())
        .withFaceExpressions();
      if (detectionsWithExpression) {
        const Array = Object.entries(detectionsWithExpression.expressions);
        const scoresArray = Array.map((i) => i[1]);
        const expressionsArray = Array.map((i) => i[0]);
        const max = Math.max.apply(null, scoresArray);
        const index = scoresArray.findIndex((score) => score === max);
        const expression = expressionsArray[index];
        if (
          expression === subject &&
          // ここで指定のスレッショルド以上でないと判定しないようにしてる
          Array[index][1] >= levelConfig[level].threshold
        ) {
          clearInterval(intervalHandler);
          setIsMatch(true);
          setStage("result");
        }
      }
```

## 成功したら次にすすめるようにした

もともと、1.5秒間お題をだして、その後1.5秒後に判定としてましたが、寿司打を見習って、全体で決められた秒数の中でどれだけすすめるかをテーマにすることにしました。
そこで、1.5秒間お題を出したあとは3秒間の制限時間を加えて、その中でうまく表情が一致した場合は次のお題にどんどんすすめる感じです。

stageという単位で状態管理してるので、上記コードでsetStage("result")を指定することで、結果を出して次に進めるようにしています。

また、一致しなかった場合でも3秒以内に一致させることができなければ次に進むようにしています。

## 動的OGP対応

そして結果に関しては動的OGP化したので、シェアされた場合にわかりやすくなりました。
背景画像に文字を載せただけの簡単構成です。
背景画像の表示のためにcanvasの中からloadImageをインポートして使っています。

```react  
  const backgroundImage = await loadImage(
    path.resolve("./images/background.jpg")
  );
  ctx.drawImage(backgroundImage, 0, 0, WIDTH, HEIGHT);
```

## 多言語化対応

また、これはだいたい毎回やってるのですが多言語化しました。
Next.jsではv10からi18nが組み込まれたので何もインポートすることなく多言語化できます。

辞書ファイルはtsファイルで用意して、useTranslate.tsというファイルを作ってその中で言語によってどちらを読み込むかを判定しています。

```react
import { useRouter } from "next/router";
import { JaTexts } from "../locales/ja";
import { EnTexts } from "../locales/en";

const useTranlate = () => {
  const { locale } = useRouter();
  return locale === "ja" ? JaTexts : EnTexts;
};

export default useTranlate;
```

next.config.js内で認識する言語とデフォルト言語の設定もします。

```javascript
  i18n: {
    locales: ["en", "ja"],
    defaultLocale: "en",
  },
```

今回初めて気がついたのですが自動でその言語のファイルにロールバックされるのはトップページのみなんですね..
シェアURLから英語か日本語かによってOGP部分も多言語化したかったので、シェアする場合にロケールの文字を残した状態でシェアURLに飛ばすようにしました。（日本語ならシェアURLが`/ja/share...`になる）

あとは全体的にデザインを整えてリリースしました。

## あとがき

これで17週目の週イチ発信となりました。
もはや２週に一個のアプリを作るになってますが、楽しいので良しとしておきます。

良ければこれまでの週イチ発信も見て下さい！
ではでは〜。


- [【React + Typescriptで顔認識】tensorflowを使って画像にマスクをかけるアプリを作った](https://qiita.com/Yui_active/items/b8062b9f89f72c6865ed)
- [【React + Typescript】ボタン一つでコンポーネントのscssをコピーできるサイトを作った](https://qiita.com/Yui_active/items/1288fed0dcc24c71e159)
- [【アップデート】ui-componentsに18個のコンポーネントを追加した](https://qiita.com/yuiko_ito/items/c1d0599e1e7da7fa42e0)
- [【Nuxt.js × Tailwind CSS】ボタン一つで有名絵画風の画像にできるサービスをリリースした！](https://qiita.com/yuikoito/items/126b0e2d36a707bfd36e)
- [【GASでLINE Bot作成】現在地の近くのおすすめのごはん屋さんを教えてくれるLINE Botを作った](https://qiita.com/yuikoito/items/c1aef147cef4458f9d2e)
- [【動的OGP】Next.js + TypeScript + Vercelデプロイで動的OGPを実現する](https://qiita.com/yuikoito/items/619120c592d99f9d3053)
- [【LambdaでOpenCVを利用】AWSとOpenCVを利用してポケモン画像でアスキーアート風に変換するAPIを作った](https://qiita.com/yuikoito/items/7dcf1deeed46f46b2f2e)
- [ポケモン画像でアスキーアート風に変換するwebアプリを作った](https://qiita.com/yuikoito/items/da5954a5d78da9de6a4e)
- [ボタン一つで漫画風の画像にできるサービスを作った](https://qiita.com/yuikoito/items/a76ea5bf05f04a44df8d)
- [【Next.js + TensorFlowでweb cameraにバーチャル背景をつける】バーチャル旅行を体験できるアプリを作った](https://qiita.com/yuikoito/items/bb9c40bbb673e4f71abe)
- [オリンピックを盛り上げるためにピクトグラムさんになれるアプリを作った【Next.js+TypeScriptでTensorFlow.jsを使って姿勢検出】](https://qiita.com/yuikoito/items/274f926b5c5e59b43ce9)
- [毎週アプリをリリースして3ヶ月経ったので振り返る](https://qiita.com/yuikoito/items/eede538b17d72685ea02)
- [【canvasで塗り絵機能を実装】画像からオリジナル塗り絵を作って塗り絵ができるアプリを作った](https://qiita.com/yuikoito/items/28510dff877e3e222ba5)
- [AR.jsを使って狼を表示してみた【初めてのAR】](https://qiita.com/yuikoito/items/6e489874d589a7d53c80)
- [表情を鍛えることができるアプリを作った](https://qiita.com/yuikoito/items/a0c588e19999e50f5a0b)