0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

vocab with context アプリ開発記録

Last updated at Posted at 2019-09-14

アプリの概要

  • 文章を読みながら外国語の語彙を増やす
  • アプリに外国語のテキストを貼り付ける → 覚えたい単語をハイライト → 単語の意味が表示される (現在は仏→英に対応)
  • Github: https://github.com/jesuissuyaa/tangochou

スクリーンショット

0914 vocab with context (仮).png
0914 vocab with context (仮) (1).png
0914 vocab with context (仮) (2).png
0914 vocab with context (仮) (3).png

仕様

構成図

0914 vocab with context (仮) (4).png

  • 画面
  • データベース: 以下のデータを保存
    • ユーザの入力した単語
    • ユーザがハイライトした単語
    • 外部サイトから取得した単語の意味
  • API: 外部の辞書サイトから単語の意味をスクレイピング→アプリに返す

ライブラリ & フレームワーク

アクティビティ図

tangochou.png

開発のポイント

APIを自分で書きました

経緯

最初は翻訳のライブラリかAPIを使うことを考えていましたが、

  • 一つの単語に対して複数の意味を返してくれるものが見つからない
  • APIキーの取得が大変
  • 有料の場合もある
  • 単語単位でなく文章単位の翻訳が想定されていることが多い

ということから、自分でAPIを書くことにしました

セットアップ

/ # ルートディレクトリ
 └ pages
    └ api
      └ # api用の.jsファイルを置く

/pages/apiに置いたAPIは、/api/*にアクセスすることで叩くことができます
例: /pages/api/dictionary.jslocalhost:3000/dictionaryにアクセスするとGETやPOSTができます

コード

下のサンプルコードでは、
urlで設定したサイトにアクセス
→ cheerioでh1タグをスクレイピング
→ h1タグのテキストをレスポンスで返す
ということをしています

my-api.js
import request from 'request';
import cheerio from 'cheerio';

export default ({ query: { id } }, res) => {
  request(url,
    (err, response, body) => {
      if (err) {
        console.error(err);
        res.status(500);
        res.end('server error');
      }

      const $ = cheerio.load(body);
      const foo = $('h1').text();
      res.status(200).json({ message: `scraped h1 tag and got ${foo} ` });
    },
  );
};

クエリパラメータ
APIのURLにはパラメータを設定することができます
サンプルコードの中では { query: { id } } となっている部分で、URLの?id=の値を取得しています
例えば localhost:3000/api/my-api?id=10 にアクセスすると、APIの中でidの値を使うことができます

cheerioでスクレイピング
サンプルコードの中では、以下の2行でcheerioを使っています

const $ = cheerio.load(body);
const foo = $('h1').text();

$ = cheerio.load(body)でレスポンスのbodyを$に入れることで、$('.myclass')$('#myid')のようにjQuery風の書き方でスクレイピングしたいサイトに要素を取得できます

responseを返す
エラーの場合は500を返します

res.status(500);
res.end('server error');

成功した場合は200とJSON形式のデータを返します

res.status(200).json({ message: `scraped h1 tag and got ${foo} ` });

json-serverでデータベースを実装しました

こちらの記事が大変参考になりました
https://qiita.com/t12u/items/2be73956b788c745048f

json-serverの使い方

  • json-serverをnpmでアプリに追加
  • db.jsonをルートディレクトリに用意 → データを書き込む
  • ターミナルからjson-serverを起動: npm run json-server
  • アプリからlocalhost:30001/<データベース名>にHTTPリクエストを投げることで読み書きする

json-serverでの読み書き

  • 新規作成: GET
  • 編集: PUT
  • 削除: DELETE

でそれぞれデータを操作できます

アプリのデータベース構成

texts
  + id: 1 (number) # データのID; json-serverによって自動で振られる
  + text: the quick brown fox jumps over the lazy dog (string) # ユーザの入力した文章
  + wordlist: ['fox', 'lazy'] (string[]) # 文章の中でハイライトされた単語

vocab
  + id: 1 (number)
  + word: lazy (string) # 単語
  + definitions: ['気だるい', '怠けている', 'ゆっくりとした'] (string[]) # 単語の意味の配列

コード

アプリの中ではfetchを使ってlocalhost:3001/textlocalhost:3001/vocabにHTTPリクエストを投げます
下の例ではvocabデータベースのすべてのエントリーのwordの値をとってきています

export async function getWords () {
  const res = await fetch('http://localhost:3001/vocab')
  const data = await res.json();
  return (<ul>{data.map(entry => <li>entry.word</li>)}</ul>)
};

<!-- getWords()の返り値の一例 -->
<ul>
  <li>lazy</li>
  <li>fox</li>
  <li>dog</li>
</ul>

新たに書き込みをするときは、このようなコードになります

fetch('http://localhost:3001/vocab', {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    word: 'hoge',
    definition: ['ほげ', 'ホゲホゲ']
  }),
}).then(getWords);

アクサンの処理が大変でした

経緯

冒頭の概要で書いたように、今回はフランス語の文章を対象にしました
フランス語の単語の意味をAPIでとってくるときは、例えば https://www.collinsdictionary.com/dictionary/french-english/vouloir のように、URLに調べたい単語を入れます
ところが、フランス語にはアクサン記号つきの文字 (e.g. î, é) が含まれるため、単語をそのままURLのパラメータにするとエラーになります
そこで、文字列を処理する必要があります

処理の流れ

  1. 単語がクリックされる → être
  2. 単語の文字列を取得 → être
  3. アクサン付きの文字をエンコーディング -> %EAtre
  4. URLにアクセス -> https://www.collinsdictionary.com/dictionary/french-english/%EAtre

コード

アクサンのエンコードをする関数は以下の通りです
何も考えずにアクサン文字を1つずつString.replace()で置換します

文字列の処理は、アクサン以外にもユーザが入力した文章をデータベースに保存するとき、'&をエスケープすることも行ったため、
関数をutils/strUtils.tsxの中にまとめて置きました
アプリ本体で使うときは import { encodeAccents } from ../utils/strUtils のようインポートします

utils/strUtils.tsx
export const encodeAccents: (str: string) => string = (str: string) =>
  str
    .toLowerCase()
    .replace(/ç/g, '%E7')
    .replace(/é/g, '%E9')
    .replace(/â/g, '%E2')
    .replace(/ê/g, '%EA')
    .replace(/î/g, '%EE')
    .replace(/ô/g, '%F4')
    .replace(/û/g, '%FB')
    .replace(/à/g, '%E0')
    .replace(/è/g, '%E8')
    .replace(/ù/g, '%F9')
    .replace(/ë/g, '%EB')
    .replace(/ï/g, '%EF')
    .replace(/ü/g, '%FC');

備考

文字列の処理について、アポストロフィには'の他にもが使われていることがあります

#まとめ

開発を通して、

  • Next.jsを使ったReactアプリケーションの開発
  • json-serverでのデータベース実装
  • APIの書き方
  • fetchの使い方

などを学びました

未解決な点としては、

  • Next.jsで.scssファイルを扱う方法
  • requestをネストする方法

などがあります

機能を実装していくにあたり、細かい点を決めるとき(単語リスト画面に単語の意味を表示するかどうかなど)にどうするべきかの判断基準がぶれていたので、次の開発では最初に想定ユーザ・想定シーンをより具体的に定めていきます
データベースも、とりあえずライブラリを入れてからデータ構造を設計しましたが、こちらはライブラリをある程度使わないと仕様がわからないことと、データ構造が比較的単純だったことがあり、今回のやり方でもあまり困りませんでした

今後の予定としては、しばらく自分でアプリを使ってみて、バグ取りや更にあったら便利な機能を実装していきます

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?