Macの辞書アプリとObsidianをPythonで連携しましょう
について
Macのビルトイン辞書を昔からずっと使っていますが、段々とオンライン辞書やGoogle翻訳などのウェブサービスに頼ってしまい、ほとんど使わなくなりました。先日「他のアプリを連携することができるか」とよく考えてみると、perplexity.aiを教わって、 PyObjC というpython frameworkを紹介してもらいました。その裏に CoreServices framewrok [1] が利用ならAPI [2] で辞書は Obsidian と連携することが判明しました。
プレリクイジット・環境構築
関するアプリやパッケージがこちらです。バージョンが必ずしも同じだとは限らないですが、もし問題があったら、古くなったアプリを更新してみると、問題が消えていくかもしれません。
| アプリケーション | バージョン |
|---|---|
| macOS | 15.5 |
| Dictionary app | 2.3.0 (294) |
| Obsidian | 1.8.9 |
| Templater | 2.11.1 |
| Python | 3.13.3 |
| PyObjC | 11.1 |
私の環境構築
辞書の設定
辞書アプリで「辞書」>「設定」と選択します。選んだ辞書は二つ:
- スーパー大辞林(物書堂)
- ウィズダム英和辞典(三省堂)
様々な場合に応じて辞書は日本語でも英語でもOKです。
スクリプトを作る
始めての前に、まずは Terminal や WezTerm などの端末に、dependenciesの一つ PyObjC を pip3 でインストールします:
# 必要なframeworkのみ
pip3 install pyobjc-framework-CoreServices
# あるいは全てのframeworks
pip3 install pyobjc
このプログラムはターミナルで実行される必要がありますので、標準ライブラリの argparse [3] を利用することをおすすめです。上記の辞書は2冊とも解析の終わりに親項目と子項目が付けるときがありますので、 re (regular expression) [4] を利用して取り除かれます。
#!/usr/bin/env python3
from CoreServices import DCSCopyTextDefinition
import argparse
import re
def main():
parser = argparse.ArgumentParser(description='Mac辞書アプリから単語を調べる')
parser.add_argument('word', help='辞書で調べる単語')
args = parser.parse_args()
word = args.word
definition = DCSCopyTextDefinition(None, word, (0, len(word)))
if definition:
# '〈子項目〉' と '〈親項目〉' を取り除く
cleaned = re.split(r'〈子項目〉|〈親項目〉', definition)[0].strip()
# 最初の '】' の後ろに改行を挿入する
cleaned = re.sub(r'】', '】\n', cleaned, count=1)
print(cleaned)
else:
print(f'「{word}」の定義が見つからない') # プロンプトメッセージ
if __name__ == '__main__':
main()
引数 help を付いてシープルヘルプメッセージを呼び出して:
python3 mac_dict_app.py --help
簡略な説明を示します。
usage: mac_dict_app.py [-h] word
Mac辞書アプリから単語を調べる
positional arguments:
word 辞書で調べる単語
options:
-h, --help show this help message and exit
プログラムがターミナルから実行できるかを確認しておいてください。
python3 mac_dict_app.py 昭和
出力された結果:
しょうわ せうわ 01【昭和】
年号(1926年12月25日〜1989年1月7日)。大正の後,平成の前。昭和天皇の代。
定義が見つからなかったら、このプロンプトメッセージが返されるはず。例えば:
python3 mac_dict_app.py zenn
見つからない場合:
「zenn」の定義が見つからない
プロンプトメッセージは後のObsidian templateと関係がありますので、そのままにしておいた方がいい。詳しく後述します。
Pythonインタプリタの実行ファイルの絶対パス
さらに python3 の絶対パス (absolute path) [5] を探しておいてください。端末に:
# python3の実行ファイルのフルパスを表示する
python -c 'import sys; print(sys.executable)'
# もしpyenvを使っているなら
pyenv which python3
# 仮想環境の場合
env | grep -i python3
設定によってパスが異なる場合があります。ご参考までに私の環境にパスがこれです:
/Users/shinei/.pyenv/versions/3.13.3/bin/python3
オブシディアンとTemplater
もちろん Obsidian をインストールしておいて、 Plugins のページに Templater を探してください。
Templaterをインストールする
オブシディアンで Templater [6] というcommunity pluginをインストールすることは必要があります。このプラグインはテンプレートが作成されるだけではなく、 システムコマンドを実行した結果を取得することができます。 [7]
Templateを構築する
Templates という名前のフォルダーを作成して、後は裏に mac_dict_app という名前の新規ノート(.mdファイル)を作成します。
.
└── Templates
└── mac_dict_app.md
作成したTemplateの内容
内容はこちらです。辞書の結果がcallout [8] に包まれるため、出力の最初のラインの行頭 >[!dict] と、次第のラインの行頭にも > を挿入することは必要があります。全てのスクリプトは:
<%*
const selection = tp.file.selection();
if (!selection) {
const notice = new tp.obsidian.Notice("単語を選んでください");
setTimeout(() => notice.hide(), 3000);
} else {
const word = selection;
const definition = await tp.user.mac_dict_app({ word });
// 次の文字列定数がmac_dict_app.pyのプロンプトメッセージと
// 全く同じでなければいけません
const notFoundMsg = `「${word}」の定義が見つからない`;
if (definition === notFoundMsg) {
tR += word;
const notice = new tp.obsidian.Notice(notFoundMsg);
setTimeout(() => notice.hide(), 3000);
} else {
// 定義を行に分割する
const lines = definition.split("\n");
// Callout 構文を追加する
let callout = `>[!dict]+ ${lines[0]}\n`;
for (let i = 1; i < lines.length; i++) {
callout += `> ${lines[i]}\n`;
}
tR += callout.trim() + "\n";
}
}
-%>
オブシディアンとTemplaterの設定
-
「設定」>「コミュニティプラグイン」に「制限モード」の 無効化 を
オンにして、「インストールされたプラグイン」に Templater もオンにする -
「設定」>「コミュニティプラグイン Templater」>「User system command functions」に Enable user system command functions を
オンにする -
ページを下ろして、 User function n゜# に、このファンクションを追加する:
-
mac_dict_app(左) -
zsh -c '/Users/shinei/.pyenv/versions/3.13.3/bin/python3 /Users/shinei/Documents/mac_dict_app.py "$word"'(※右)
-
-
トップに Template folder location 設定する:
Templates -
「設定」>「コミュニティプラグイン Templater」>「Template hotkeys」:
Add new hotkey for template
「ホットキー」に Templater: Insert Templates/mac_dict_app.md を定義します。例えば C-d や C-f 。システムのショートカットと衝突しないように、なるべく コマンドキー を使用しないでください。
CSS Snippetsの設定
ここまでは充分ですけど、デフォストたらcalloutのアイコンはなんてったって見舞わないと思いますので、ついCSS Snippets [9] を自定義してみます。公式ヘルプページに CSS snippets: Adding a snippet をご覧ください。
他のアイコンが Lucide に自ら選ばれまして、私にとってあの book-a はオリジナルと比べて一番似ているのと思いますね。アイコンの色は firebrick あるいは indianred がそっくり似ています。
ディレクトリの例示:
.
└── .obsidian
└── snippets
└── snippet_callout.css
CSSファイルの例示:
.callout[data-callout="custom-question-type"] {
--callout-color: 0, 0, 0;
--callout-icon: lucide-alert-circle;
}
.callout[data-callout="dict"] {
--callout-color: 205, 92, 92; /* IndianRed */
--callout-icon: book-a;
}
出力の実演
オブシディアンに単語を選んでホットキーを押して、結果は:
単語:令和
>[!dict]+ れいわ 【令和】
> 年号(2019年5月1日〜 )。平成の後。今上天皇の時代。
単語:中途半端
>[!dict]+ ちゅうとはんぱ 4【中途半端】
> (名•形動)文ナリ 物事が完成していないこと。また,徹底していないで,どっちつかずなさま。「―な態度」
choicesを増える
(更新:[2025-10-14 Tue])
時々言葉の読み方が知りたいだけですので、出力の一部を控えるため、オプションの選択肢二つ --output default と --output less を追加しています。さらにオプションによりまして出力の結果が変更させるように、 if definition: の部分も修正しました。
diff --git a/Python/mac_dict_app.py b/Python/mac_dict_app.py
index c0daedb..294c7bd 100644
--- a/Python/mac_dict_app.py
+++ b/Python/mac_dict_app.py
@@ -1,26 +1,61 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
-from CoreServices import DCSCopyTextDefinition
+from CoreServices import DCSCopyTextDefinition # type: ignore
import argparse
import re
+import sys
def main():
- parser = argparse.ArgumentParser(description='Mac辞書アプリから単語を調べる')
+ parser = argparse.ArgumentParser(
+ prog='mac_dict_app',
+ add_help=False,
+ formatter_class=argparse.RawTextHelpFormatter,
+ description='Mac辞書アプリから言葉を調べる',
+ epilog='''\
+使い方:
+ %(prog)s 'word' --output default
+ %(prog)s 'word' --output less
+
+ 👇 詳細
+ https://zenn.dev/shinei/articles/06a9b9ec558bde
+ '''
+ )
parser.add_argument('word', help='辞書で調べる単語')
+ parser.add_argument(
+ '-h', '--help',
+ action='help',
+ default=argparse.SUPPRESS,
+ help='このヘルプメッセージを表示して終了する'
+ )
+ parser.add_argument(
+ '-o', '--output',
+ choices=['default', 'less'],
+ default='default',
+ help='''\
+default # 読み方と意味を表示する
+less # 読み方だけを表示する
+ '''
+ )
args = parser.parse_args()
+ if not args.word:
+ parser.error('単語を指定してください')
+
word = args.word
definition = DCSCopyTextDefinition(None, word, (0, len(word)))
if definition:
- # '〈子項目〉' と '〈親項目〉' を取り除く
- cleaned = re.split(r'〈子項目〉|〈親項目〉', definition)[0].strip()
- # 最初の '】' の後ろに改行を挿入する
- cleaned = re.sub(r'】', '】\n', cleaned, count=1)
- print(cleaned)
+ punctuation = '】' if '】' in definition else ')'
+ if args.output == 'less':
+ result = re.split(punctuation, definition)[0].strip() + punctuation
+ else:
+ # '〈子項目〉' と '〈親項目〉' を取り除く
+ result = re.split(r'〈子項目〉|〈親項目〉', definition)[0].strip()
+ # 最初の '】' の後ろに改行を挿入する
+ result = re.sub(punctuation, punctuation + '\n', result, count=1)
+ print(result)
else:
- print(f'「{word}」の定義が見つからない')
+ print(f'「{word}」の定義が見つからない', file=sys.stderr)
if __name__ == '__main__':
main()
-
使い方:
# helpページを引き出す
mac_dict_app --help
# 読み方だけの結果
mac_dict_app 'word' --output less
# 全ての結果
mac_dict_app 'word' --output default
# デフォルトだからオプションの無しが構わない
mac_dict_app 'word'
まとめ
日本語を勉強しているわたしにとって、言語を学びたちにとってこの関数が役に立つと思います。 最後まで読んでいただきありがとうございました。不備がありましたら、ご指導いただけますと嬉しいです。
Discussion