2
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?

【個人開発】Macで英語PDFやターミナルを0.3秒でローカルOCR翻訳するツールを作った話

2
Last updated at Posted at 2026-10-31

はじめに:なぜこのツールを開発したのか?

エンジニアとして日々の開発業務や技術リサーチ、海外の論文・GitHub Issue・公式ドキュメントを読んでいると、以下のような「細かな翻訳・テキスト読み取りのストレス」によく直面します。

  1. コピー禁止や選択しづらい英文PDF論文・スライドをその場でサッと読みたい
  2. ターミナルに出力された英語のビルドエラーや例外スタックトレースを瞬時に把握したい
  3. 海外ドキュメントを読むとき、ウィンドウを切り替えずに最前面にピン留めして原文と訳文を並べて読みたい
  4. 既存のスクショ翻訳アプリは起動が重くメモリを消費する、あるいはキャプチャ画像がクラウドサーバーに送信されるため社内コードや機密情報には使いづらい

これらをすべて解消するため、Swift + AppKit + Apple Vision フレームワークを用いて、**0.3秒で起動し完全ローカルOCRで動作するMac専用の超軽量翻訳ツール「Zipp」**を個人開発しました。


実際の動作デモ(15秒)

キーボードの ⌃S (Control + S) を押すと即座に画面キャプチャ選択モードに入り、英語のテキスト範囲をドラッグで囲むだけで、0.3秒後にローカルOCRと翻訳が完了してフロート表示されます。


技術的な設計と実装のポイント

1. Apple Vision フレームワークによる 100% ローカル OCR 実装

スクショ翻訳ツールを開発するにあたり、「画面のキャプチャ画像が外部サーバーにアップロードされないこと」を最重要要件としました。

macOS 標準の Vision.framework (VNRecognizeTextRequest) を使用することで、すべてのテキスト認識処理をローカルの Neural Engine / CPU / GPU 内で完結させています。

VisionOCRService.swift
import Foundation
import Vision
import AppKit

final class VisionOCRService {
    /// 画面キャプチャ画像からローカルOCRを実行する
    func recognizeText(from image: CGImage) async throws -> String {
        let request = VNRecognizeTextRequest()
        // 精度重視モード(MacのNeural Engineを活用)
        request.recognitionLevel = .accurate
        request.recognitionLanguages = ["en-US", "ja-JP"]
        request.usesLanguageCorrection = true
        
        let handler = VNImageRequestHandler(cgImage: image, options: [:])
        try handler.perform([request])
        
        guard let observations = request.results else { return "" }
        let rawLines = observations.compactMap { $0.topCandidates(1).first?.string }
        return sanitizeExtractedText(rawLines.joined(separator: "\n"))
    }
}

2. PDF特有の「中途半端な改行」を自動補正するプリプロセッサ

PDF論文やターミナルのログをそのままOCRにかけると、文の途中に余計な改行(Line Break)が入ってしまい、翻訳エンジンが文脈を取り違えて誤訳を起こす問題があります。

そこで、行末がピリオドやコロンでない場合に自動でスペース結合するテキストサニタイズ処理を実装しました。

TextSanitizer.swift
extension VisionOCRService {
    /// PDFやカラム表示特有の不要な改行をマージする
    private func sanitizeExtractedText(_ text: String) -> String {
        let lines = text.components(separatedBy: .newlines)
        var merged: [String] = []
        var currentParagraph = ""
        
        for line in lines {
            let trimmed = line.trimmingCharacters(in: .whitespaces)
            guard !trimmed.isEmpty else {
                if !currentParagraph.isEmpty {
                    merged.append(currentParagraph)
                    currentParagraph = ""
                }
                continue
            }
            
            if currentParagraph.isEmpty {
                currentParagraph = trimmed
            } else if trimmed.hasPrefix("-") || trimmed.hasPrefix("•") || trimmed.hasPrefix("*") {
                // リスト項目の場合は改行を維持
                merged.append(currentParagraph)
                currentParagraph = trimmed
            } else {
                // 英文の途中の改行はスペースで連結
                currentParagraph += " " + trimmed
            }
        }
        
        if !currentParagraph.isEmpty {
            merged.append(currentParagraph)
        }
        
        return merged.joined(separator: "\n\n")
    }
}

3. 最前面ピン留め(Always on Top)と非同期フローティングパネル

ブラウザやPDFリーダーをスクロールしながら参照できるように、AppKit の NSPanel (.floating レベル) を採用しています。

FloatingPanel.swift
import AppKit

final class FloatingTranslationPanel: NSPanel {
    init() {
        super.init(
            contentRect: NSRect(x: 0, y: 0, width: 420, height: 280),
            styleMask: [.nonactivatingPanel, .titled, .closable, .resizable],
            backing: .buffered,
            defer: false
        )
        self.level = .floating // 最前面フロート
        self.isFloatingPanel = true
        self.hidesOnDeactivate = false // 固定時は他アプリ操作中も隠れない
        self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
    }
}


30秒初期セットアップ&使い方

ショートカット操作

  • スクショOCR翻訳:⌃S (Control + S)
  • 選択テキスト翻訳:⌥G (Option + G)
  • 初回のみ、macOS の「システム設定 > プライバシーとセキュリティ > 画面収録」でアプリの権限をONにしてください(※画面上の選択範囲をローカルOCRで認識するためにのみ使用されます)。

おわりに

「毎日何十回も使う小さなユーティリティだからこそ、メモリ消費を極限まで抑え、0.3秒でサクッと動くことに徹底的にこだわる」という思想でSwiftネイティブ開発を行いました。

詳しい仕様やデモ動画、最新のアップデート情報は X(@dayu510)にて発信していますので、興味のある方はぜひお気軽にフィードバックをいただけると嬉しいです!

2
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
2
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?