返答が英語で書き出されたら知らせる。設定で日本語での書き直しを頼める

English | 日本語
A marketplace of mods for Claude Code. A mod is a plugin that adds features to Claude Code through function hooks.
| Mod | What it does |
|---|---|
| branch-watch | Stops commits on a branch another session switched to, and lists each session's worktree and branch |
| push-gate | Holds git push and similar commands until a person approves |
| auto-worktree | Creates a worktree on the first prompt and starts the work there |
| session-diff | Shows the files this session edited and their diffs |
| pr-pane | Shows open pull requests and their CI results |
| side-question | /btw asks a side question without stopping the work |
| done-chime | Plays a sound when a long turn ends and when Claude waits for permission |
| ja-check | Tells you when a reply starts in English |
| usage-meter | Shows each turn's token counts and prompt cache rate |
claude plugin marketplace add yut0takagi/mods
claude plugin install branch-watch@yut0takagi-mods # one line per mod you want
Developed with Claude Code 2.1.289. Mods are an early access feature, so a Claude Code update may break them. The VS Code and Cursor extension draws no mod UI, so some mods are used differently there.
To change a mod, read CONTRIBUTING. Changes are listed in the CHANGELOG. Report security problems as described in SECURITY.
hooks/register.ts 37 lines1import type { Register } from 'claude-code'
2
3import { openingOf } from './lang'
4
5const REWRITE = '直前の返答が英語で書き出されていました。同じ内容を日本語で書き直してください。'
6
7export const register: Register = (on, options) => {
8 const mode = options.mode === 'rewrite' ? 'rewrite' : 'toast'
9 // 書き直しを頼んだ直後の返答も英語なら、もう頼まずに知らせるだけにする(頼み続けて回らないように)
10 let hasAskedRewrite = false
11
12 on('turn.complete', async ($, e, next) => {
13 const done = await next(e)
14
15 if (e.agentId !== undefined || e.reason !== 'answer') {
16 return done
17 }
18
19 const canAskRewrite = mode === 'rewrite' && !hasAskedRewrite
20 hasAskedRewrite = false
21
22 if (openingOf(e.answer) !== 'en') {
23 return done
24 }
25
26 if (canAskRewrite) {
27 hasAskedRewrite = true
28 // 待つとこのターンの終わりを待つことになるので、積むだけにする
29 void $.prompt.submit({ text: REWRITE })
30 } else {
31 $.ui.toast('ja-check: 返答が英語で書き出されています', { timeoutMs: 8_000 })
32 }
33
34 return done
35 })
36}
37hooks/lang.ts 64 lines1/** 返答の書き出しが日本語かどうか。$ に触れない部分。 */
2
3export type Opening = 'ja' | 'en' | 'unknown'
4
5const JA = /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u
6const WORD = /[A-Za-z]{2,}/g
7const FENCE = /^\s*(```|~~~)/
8
9// 英語と決めるのに要る英単語の数。`OK` やパスだけの行では決めない
10const MIN_WORDS = 3
11// 言語の決まらない行をいくつまで読み進めるか
12const MAX_LINES = 3
13
14/** 行から Markdown の記号・インラインコード・URL・パスを除いた地の文。見出しと表の行は空にする。 */
15function proseOf(line: string): string {
16 const text = line.trim()
17
18 if (text.startsWith('#') || text.startsWith('|')) {
19 return ''
20 }
21
22 return text
23 .replace(/`[^`]*`/g, ' ')
24 .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
25 .replace(/\S*\/\S*/g, ' ')
26 .replace(/^(?:[>*+-]|\d+\.)\s+/, '')
27 .trim()
28}
29
30/** 書き出しの言語。コードブロック・見出し・表を飛ばし、言語の決まる最初の行で決める。 */
31export function openingOf(answer: string): Opening {
32 let isInFence = false
33 let undecided = 0
34
35 for (const line of answer.split('\n')) {
36 if (FENCE.test(line)) {
37 isInFence = !isInFence
38 continue
39 }
40
41 const prose = isInFence ? '' : proseOf(line)
42
43 if (prose === '') {
44 continue
45 }
46
47 if (JA.test(prose)) {
48 return 'ja'
49 }
50
51 if ((prose.match(WORD) ?? []).length >= MIN_WORDS) {
52 return 'en'
53 }
54
55 undecided += 1
56
57 if (undecided >= MAX_LINES) {
58 break
59 }
60 }
61
62 return 'unknown'
63}
64