Vue.jsと音声APIで世界のみんなにこんにちは！

## 外国人観光客に話しかけられたとき・・・
2020年から3年間続いたコロナ禍もようやく落ち着き、最近は街中で**外国人観光客**の姿を見ることが多くなりましたね。私は話しかけやすそうな見た目なのか、よく彼らに道を聞かれます。

道案内をするときは「**Where are you from**?」と聞くようにしていますが、**自分が前提知識を持っていない国から来た方**だったとき、「**Oh...**」しか言うことがなく、逆に気まずい感じになってしまいます:sweat:

せめて、「**こんにちは**」だけでも彼らの母語で言えたら・・・！

**ということで**！外国人観光客に道を聞かれたとき、**彼らの母語であいさつできるWebアプリ**を作りました:information_desk_person:

## この記事でわかること
- **Vue.js**と**axios**を使った**APIの呼び出し方**
- **Web Speech API**（音声読み上げ）で**多言語対応**する方法

## 世界の言葉でこんにちは！
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【世界の言葉でこんにちは！】<br>外国の方に母語であいさつできたら仲良くなれること間違いなし！外国人観光客に話しかけられたときに使ってね😉<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/VueJS?src=hash&amp;ref_src=twsrc%5Etfw">#VueJS</a> <a href="https://twitter.com/hashtag/axios?src=hash&amp;ref_src=twsrc%5Etfw">#axios</a> <a href="https://twitter.com/hashtag/%EF%BD%B2%EF%BD%AF%EF%BE%87?src=hash&amp;ref_src=twsrc%5Etfw">#ｲｯﾇ</a> <a href="https://t.co/alQ6avYJ6a">pic.twitter.com/alQ6avYJ6a</a></p>&mdash; 🐶ちーろってぃ🐶 (@chiiirotty) <a href="https://twitter.com/chiiirotty/status/1659522768394919940?ref_src=twsrc%5Etfw">May 19, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


アプリは**CodePen**で作成したものをエクスポートし、**Netlify**でデプロイしました！

https://transcendent-sunshine-37de4f.netlify.app/

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="RweejKp" data-user="blttsxag-the-solid" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/blttsxag-the-solid/pen/RweejKp">
  世界の言葉でこんにちは！</a> by 小島チヒロ (<a href="https://codepen.io/blttsxag-the-solid">@blttsxag-the-solid</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

APIはブラウザで音声を扱える[Web Speech API](https://developer.mozilla.org/ja/docs/Web/API/Web_Speech_API)と、世界の挨拶を取得できる[HelloSalut API](https://stefanbohacek.com/project/hellosalut-api/)を使いました！国は[入国者上位10か国](https://www.mofa.go.jp/mofaj/kids/ranking/rainichi.html)から選びましたが、ベトナムとフィリピン(タガログ語)はAPIで取得できませんでした:sweat_smile:

## 実装
### ①ソースコード
<details><summary>HTMLコード</summary>

```HTML: index.html
<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>say_hello</title>
  <meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="./style.css">

</head>
<body>
<!-- partial:index.partial.html -->
<html>

<head>
  <title>世界の言葉でこんにちは！</title>
</head>

<body>
  <h1>世界の言葉でこんにちは！</h1>
  <div id="app">
    <form>
      <select id="nation" v-model="nation" class="nation-select">
        <option value="">どこから来たの？</option>
        <option>de：ドイツ</option>
        <option>en：アメリカ</option>
        <option>es：スペイン</option>
        <option>fr：フランス</option>
        <option>hi：インド</option>
        <option>id：インドネシア</option>
        <option>ko：韓国</option>
        <option>zh：中国</option>
      </select>
    </form>
    <button id="btn" v-on:click="getSalut">あいさつ！</button>
    <p>{{ nation.split("：")[1] }}</p>
    <p id="salut">{{ salut }}</p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue2.6.10/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</body>

</html>
<!-- partial -->
  <script  src="./script.js"></script>

</body>
</html>
```
</details>

<details><summary>CSSコード</summary>

```CSS: style.css
body {
  background-color: rgb(153, 242, 179);
  text-align: center;
}

h1 {
  color: black;
}

.info {
  font-weight: bold;
  color: green;
}

#comingout {
  display: none;
  color: blue;
}
```
</details>

<details><summary>JavaScriptコード</summary>

```JavaScript: script.js
let app = new Vue({
  el: "#app",
  data: {
    nation: "",
    salut: ""
  },
  methods: {
    getSalut: async function () {
      let lang_code = this.nation.split("：")[0];
      let HelloSalutUrl =
        "https://hellosalut.stefanbohacek.dev/?lang=" + lang_code;
      await axios
        // HelloSalut API呼び出し
        .get(HelloSalutUrl)
        .then(function (response) {
          return response["data"];
        })
        .then(function (jsonData) {
          this.salut = jsonData["hello"];
          document.getElementById("salut").innerHTML = `${this.salut}`;
          readAloud(decNumRefToString(this.salut), lang_code);
        });
    }
  }
});
// 音声読み上げする関数
function readAloud(salut, lang_code) {
  // ブラウザにWeb Speech API Speech Synthesis機能があるか判定
  if ("speechSynthesis" in window) {
    // 発言を設定
    const uttr = new SpeechSynthesisUtterance();
    uttr.text = salut;
    uttr.lang = lang_code;
    // 発言を再生
    window.speechSynthesis.speak(uttr);
  } else {
    alert("このブラウザは音声合成に対応していません。");
  }
}
// 数値文字参照(10進数)を文字列に変換する関数
function decNumRefToString(decNumRef) {
  return decNumRef.replace(/&#(\d+);/gi, function (match, $1, idx, all) {
    return String.fromCharCode($1);
  });
}
```
</details>

### ②解説
実装に苦労した以下2か所について解説します。
時間の都合上詳細解説は割愛しますが、**コメントで質問いただければ対応します**:thumbsup:
- **Vue.js**と**axios**を使った**APIの呼び出し方**
- **Web Speech API**（音声読み上げ）で**多言語対応**する方法

#### ②-1：**Vue.js**と**axios**を使った**APIの呼び出し方**
**「あいさつ！」ボタンを押したらHelloSalut APIが呼ばれ、取得した結果を画面に表示する**というわりとありきたりな機能なのですが、すぐに使えるコードがなかなか見つからず:sweat: 試行錯誤した結果、以下のようなコードだとうまく動きました。ぜひお使いください！

```HTML:ボタンを押したら画面表示(HTML)
<button id="btn" v-on:click="getSalut">あいさつ！</button>
<p>{{ nation.split("：")[1] }}</p>
<p id="salut">{{ salut }}</p>
```

```JavaScript:axiosでAPI呼び出し
methods: {
    getSalut: async function () {
      let lang_code = this.nation.split("：")[0];
      let HelloSalutUrl =
        "https://hellosalut.stefanbohacek.dev/?lang=" + lang_code;
      await axios
        // HelloSalut API呼び出し
        .get(HelloSalutUrl)
        .then(function (response) {
          return response["data"];
        })
        .then(function (jsonData) {
          console.log(jsonData);
          this.salut = jsonData["hello"];
          document.getElementById("salut").innerHTML = `${this.salut}`;
          readAloud(decNumRefToString(this.salut), lang_code);
        });
    }
  }
```
axiosで「なんか取得できない！？」となったら、とりあえず呼び出し箇所に**await**が付いているかを確認しましょう:point_up:

#### ②-2：**Web Speech API**（音声読み上げ）で**多言語対応**する方法
##### つまづきポイント①：APIで取得できるあいさつが謎の数列
読み上げ機能の多言語対応は本当に大変でした・・・
中国語や韓国語など一部の言語について、APIで取得できるあいさつが**謎の数列**だったのです:scream:
```:こんなやつ
"&#20320;&#22909;"
```
調べてみると[文字参照](https://e-words.jp/w/%E6%96%87%E5%AD%97%E5%8F%82%E7%85%A7.html)というものらしいです。ブラウザ表示は自動で文字に変換して表示してくれたのですが、Web Speech APIは数字をそのまま読み上げてしまいました・・・

解決策としては、ありがたいことに[文字参照を文字列に変換するコード](https://gist.github.com/myaumyau/4975024)をGitHubにあげてくださっている方がいたので、使わせていただきました:pray:
```JavaScript:数値文字参照(10進数)を文字列に変換する関数
function decNumRefToString(decNumRef) {
  return decNumRef.replace(/&#(\d+);/gi, function (match, $1, idx, all) {
    return String.fromCharCode($1);
  });
}
```

##### つまづきポイント②：読み上げがカタコト
Web Speech APIは、**デフォルトでAPIを叩く環境（私の場合日本）に依存した設定**になるようです。なので、何もせずにHelloを読み上げさせると「はろー」とめちゃくちゃカタコトになってしまいました・・・
ちょうどAPI呼び出し用に取得していた[言語コード](https://www.benricho.org/translate/languagecode.html)を、**Web Speech APIの言語設定**にも使えたので流用しました！

```JavaScript:音声読み上げ
// ブラウザから「言語コード：国名」の形で取得しているため「：」前後で分割
let lang_code = this.nation.split("：")[0];
// 省略
readAloud(decNumRefToString(this.salut), lang_code);
// 省略
// 音声読み上げする関数
function readAloud(salut, lang_code) {
  // ブラウザにWeb Speech API Speech Synthesis機能があるか判定
  if ("speechSynthesis" in window) {
    // 発言を設定
    const uttr = new SpeechSynthesisUtterance();
    uttr.text = salut;
    uttr.lang = lang_code;
    // 発言を再生
    window.speechSynthesis.speak(uttr);
  } else {
    alert("このブラウザは音声合成に対応していません。");
  }
}
```


## 今後改良できる点
### ①スマホで使えるようにする
今回外国人観光客に話しかけられたときに使うことを目的にしていたのですが、肝心の**スマホでは何故か音声が流れず**・・・iPhoneだからか？と思いましたがそういうわけでもなさそう。
どなたか原因がわかる方がいらっしゃればぜひ**コメントで教えてください**:bow:

### ②おしゃれなデザインにする
今回は機能面を充実させることに注力したため、アプリのUIはイマイチになってしまいました:sweat_smile: JavaScriptにはUIに特化したライブラリがたくさんあるため、今後は積極的に使っていこうと思います！
- [jsDelivr](https://www.jsdelivr.com/)：様々なJSライブラリへのオンラインアクセスを提供してくれるサービス

### ③対応している国・地域・言語を増やす
**同じ国・地域で異なる言語を使っていたり**、逆に**同じ言語が複数の国で使われていたり**するので、すべてを網羅するのは難しいですが、できる限り増やしていきたいです！

## 参考にしたサイト
- [ウェブ音声 API](https://developer.mozilla.org/ja/docs/Web/API/Web_Speech_API)
- [Hello, Salut!](https://stefanbohacek.com/project/hellosalut-api/)
- [日本への入国者の多い国・地域（キッズ外務省）](https://www.mofa.go.jp/mofaj/kids/ranking/rainichi.html)
- [【Vue.js】Async/awaitとaxiosによる非同期処理](https://qiita.com/kbind/items/ea9d3dd7449f1b6f0fb0)
- [【JS】Web Speech APIの音声読み上げ機能の実装方法（日本語 / 英語読み上げ）](https://web-creates.com/code/js-web-speech-api/)
- [文字参照](https://e-words.jp/w/%E6%96%87%E5%AD%97%E5%8F%82%E7%85%A7.html)
- [言語コード一覧 / Language Codes ISO639で規定されている2文字言語コード](https://www.benricho.org/translate/languagecode.html)
- [[js]数値文字参照(16進数, 10進数)を文字列に変換](https://gist.github.com/myaumyau/4975024)

**この記事が参考になった！**
という方はぜひ、**いいね・ストック・コメント**や、**あなたのQiita記事にリンク**をつけてください:wink: 励みになります！