TL;DR
- 対象: 英語論文を日常的に読む研究者・学生
- 何をした: PDF を読みながら単語をその場で登録できる macOS アプリを SwiftUI + PDFKit で個人開発した
- 何が新しいか: 単語を「意味」だけでなく「どの論文のどこで出会ったか」ごと保存し、復習時に文脈へ戻れる
はじめに
英語論文を読むとき、GPT や NotebookLM に翻訳させれば内容を追うこと自体はできます。
ただ、その読み方を続けていると、自分で英文を読む力や語彙力が少しずつ削られていく感覚がありました。「理解の外注」はできても、「自分の力として残る読書」にはなりにくいからです。
では昔ながらに印刷して手書きでメモすればいいかというと、それもかなり面倒です。しかも後から「あの論文で見たあの単語、何だったっけ」と思っても、紙のメモでは辿りづらい。結局、同じ単語をまた調べることになってしまい、勉強としては二度手間になります。
そこで、論文を読みながら出会った単語をその文脈ごとデータとして保存し、あとで検索したり見返したりできる形にしたいと思い、macOS ネイティブアプリ English Paper Reader(配布物名: PapersApp)を作りました。
インストール方法
ワンライナーでインストールできるので、気になった方はぜひ試してみてください。
curl -fsSL https://raw.githubusercontent.com/mksmkss/English-Paper/main/install.sh | sh
この記事では、
- なぜ作ったのか(AI 翻訳時代の問題意識)
- 何ができるアプリなのか
- SwiftUI + PDFKit の橋渡しなど、実装でおもしろかった点
- 「語彙データだけを GitHub 同期する」という設計判断
を中心に紹介します。
何を解決したかったのか
整理するとこうなります。
| 課題 | これまでの選択肢 | 何が問題か |
|---|---|---|
| 単語の意味を知りたい | GPT/NotebookLM で全文翻訳 | 読んだ気になるだけで、語彙が自分に蓄積されない |
| 単語を記録したい | 紙にメモ/別の単語帳アプリ | PDF と切り離されるので、文脈を失う |
| あとで思い出したい | 検索やノートを漁る | どの論文のどこで出会ったかが追えない |
ここで一番もったいないのが、「同じ単語を何度も調べ直している」 ことだと思います。読んでいる際に「あ、これ前にも調べた気がする…」が起きるのは、知識が一回限りの消費で終わっている証拠です。
なので、このアプリでは次の体験を一画面に閉じ込めることを目標にしました。
- 読む(PDF ビューア)
- 拾う(その場で単語登録)
- 見返す(あとで文脈ごと再訪する)
English Paper Reader でできること
ざっくり言うと、英語論文 PDF を読みながら気になった単語をその場で登録し、意味・例文・出現箇所をあとから見返せる macOS アプリです。
主な機能:
- 📄 PDF を読みながら単語をドラッグ選択 → Quick Register で即登録
- 🟨 登録済み単語は PDF 上で自動ハイライト
- 💡 ハイライトにホバーすると、その単語の意味や登録済み例文をすぐ確認できる
- 🗂 フォルダで PDF を整理(rename、ネスト、ドラッグ移動対応)
- 🔎 単語・読み・Definition で単語一覧を検索
- 🔗 単語詳細から「論文中のその単語に出会った位置」へジャンプ
- ☁️ 語彙データだけを GitHub に同期
技術スタックは以下のとおりです。
| レイヤー | 採用技術 |
|---|---|
| UI | SwiftUI |
| PDF 表示 | PDFKit (NSViewRepresentable経由) |
| データ保存 | SQLite |
| バックアップ |
sqlite3 .dump で backup.sql を生成 |
| 同期 | Git リポジトリとして backup.sql だけを commit/push |
使い方
1. PDF を追加する
左サイドバー上部の Add PDF ボタンから論文 PDF を読み込みます。フォルダを切ってネストすることもできます。
2. 単語を登録する
PDF 上の単語や短いフレーズを選択すると Quick Register シートが立ち上がります。
- Definition を入力(必須)
- 必要なら
Pronunciation or kanaを追加 - 保存するとその箇所がハイライトされる
ここで重要なのは、登録動作を「選択してすぐ」に閉じ込めていることです。
別アプリへの切り替えも、テキストの再入力も発生しません。読書フローを一切壊さないことを優先しました。
3. 復習する
- 下部の単語一覧で見返す
- 右 Inspector で
meanings/examples/appearancesを編集 - appearance をクリックすると、論文中の出現位置に戻る
例文は自分で書き足せるようにしてあります。これは 意味の丸暗記ではなく、運用可能な知識として残すための工夫です。
4. GitHub に同期する
右上の GitHub アイコンから repository URL を設定すると、語彙データだけを push できます。
実装で面白かったところ
SwiftUI と PDFKit の橋渡し
PDFKit (PDFView) は AppKit のクラスなので、SwiftUI に載せるには NSViewRepresentable で包む必要があります。
下のコードはあくまで概念図として簡略化したものです。実コードでは、テキスト選択や hover を拾うために PDFView を直接返すのではなく、コンテナビュー (PDFInteractiveContainerView) と PDFView のサブクラス (PDFInteractivePDFView) を噛ませる構造にしています。
// 概念図(実装は PDFInteractiveContainerView + PDFInteractivePDFView を噛ませている)
struct PDFViewerRepresentable: NSViewRepresentable {
let document: PDFDocument
func makeNSView(context: Context) -> PDFView {
let view = PDFView()
view.document = document
view.autoScales = true
// 実コードでは、ここで mouseUp / hover を拾うサブクラスを使う
return view
}
func updateNSView(_ nsView: PDFView, context: Context) {
// SwiftUI 側の状態が変わったときの再描画
}
}
ここで地味にハマったのが テキスト選択のフックです。
PDFView は標準でドラッグ選択できますが、「選択が確定したタイミング」を SwiftUI 側に伝える素直な API がありません。
最終的には、PDFView のサブクラスで mouseUp をオーバーライドし、そこで currentSelection を取り出して Quick Register シートにつなぐ形に落ち着きました。hover の方も同じサブクラスで扱っています。
補足:Quick Register の選択長制限
読書フローを壊さないために、Quick Register は長すぎる選択や複数行をまたぐ選択を弾くようにしています。
論文 PDF はうっかりすると段落丸ごと選択してしまいがちですが、単語帳に登録したいのは大抵「単語または短いフレーズ」なので、ここをアプリ側で制限しておく方が体験が安定します。
「単語帳」ではなく「文脈つき単語帳」というデータモデル
このアプリの肝は、単語そのものではなく、出会った文脈まで保存することです。
Word ─ 単語本体
├ Meaning ─ 意味、品詞、補足
├ Example ─ 例文(自分で書ける)
└ Appearance ─ 論文内の出現位置(ページ・座標・周辺テキスト)
PDFRecord ─ 論文ファイル情報
Folder ─ PDF 整理用
Appearance には PDF 上の bounding box(座標)を保存しています。これによって、
- 後からハイライトを再描画できる
- 単語詳細から「この単語に出会った位置」へジャンプできる
- ホバーで該当 appearance を hit test して意味を出せる
という、復習のときに文脈へ戻れる体験が成立します。
座標を保存しておく、という小さな決定が、アプリ全体の体験を支えている部分でした。
設計判断:なぜ「データだけ」を GitHub 同期したのか
このアプリで一番議論の余地があるのが GitHub 同期の対象です。
私は「語彙データだけ」を同期する設計にしました。
同期する:
- 単語、meanings、examples、appearances
- フォルダ構造、PDF の所属情報
同期しない:
- PDF ファイル本体
- アプリのソースコード
理由はシンプルで、
- 論文 PDF は重い。リポジトリを汚す上、著作権上もばらまくのは望ましくない
- PDF の絶対パスは Mac ごとに違う。同期しても結局 relink が必要
- 守りたいのは「自分が積み上げた語彙」だけ。PDF はまた DL すれば手に入る
実装としては、~/Library/Application Support/EnglishPaperReader/ に app.db(普段使い)と backup.sql(sqlite3 .dump で生成した同期・復元用の SQL ダンプ)を置き、後者だけを git commit / push しています。
~/Library/Application Support/EnglishPaperReader/
├── app.db # 普段使う SQLite 本体
└── backup.sql # sqlite3 .dump の出力。これだけが git 管理
なぜ DB ファイルそのものを同期せず SQL dump にしているかというと、
- Git で差分が追いやすい(バイナリの SQLite ファイルと違い、テキスト diff になる)
-
復元手順が単純(
sqlite3 app.db < backup.sqlで完結する) - 壊れたときに中身を目視で確認できる
という3点が大きいです。
別の Mac でこのアプリを開いたときは、backup.sql があれば初回起動時に自動で app.db を復元します。つまり、新しい Mac で git clone → アプリ起動、で過去の語彙資産がそのまま戻ってくる設計です。
(ただし PDF 本体は Mac 間で絶対パスが変わるため、必要に応じて Relink が必要になります。)
ローカル repo がなければ git init、origin がなければ追加、初回 push 時は upstream を自動設定 — というあたりは、「ユーザーが Git の知識を持っていなくても動く」ことを優先して自動化しています。
GitHub を「コードの置き場」ではなく「個人の語彙データのバックアップ先」として使う、というのが今回いちばん気に入っている設計です。
苦労したところ
正直に書いておくと、地味にハマった箇所はけっこうあります。
- ハイライト再描画のタイミング — SwiftUI の状態と PDFKit のビューの整合性を取るのが面倒
- hover 判定の UX — appearance にカーソルが乗ったか判定するのは hit test だが、ポップアップが出るタイミングを早くしすぎると邪魔、遅すぎると鈍重に感じる
- SQLite と UI 状態の同期 — 単語追加 → 一覧更新 → ハイライト更新の経路が複数あり、整理に時間を使った
- Git 同期を「データ repo」として扱う設計 — 初回 push、認証エラー、conflict などケースが多く、エッジケースの潰し込みが必要だった
このあたりは、今後別記事で深掘りしたいと思っています。
今後やりたいこと
- 自動バックアップの改善(タイマー or イベント駆動)
- 辞書 API 連携で Definition の初期値を補完
- 単語の学習進捗管理(既知 / 学習中 / 未習)
- iCloud 同期や、PDF の相対パス管理
- 簡易的な単語テスト機能
まとめ
- AI 翻訳時代だからこそ、英語力を削らずに論文を読む仕組みが欲しかった
- 「読む・拾う・見返す」を一画面に閉じ込めた macOS ネイティブアプリを作った
- SwiftUI + PDFKit + SQLite で実装し、語彙データだけを GitHub 同期する設計にした
- 単語と一緒に 「どこで出会ったか」 を保存することで、復習時に文脈に戻れる
GPT に翻訳させて読むのも、単語帳アプリを別途立ち上げるのも、それぞれ便利です。
ただ、 読書という体験を分断させないこと自体に価値があると感じています。
同じように「AI 翻訳に頼りすぎている気がする」「単語帳が続かない」と思っている方の参考になれば嬉しいです。


