Claude Code mod: learn to say your daily work in the language you want to work in. Your prompts, the way a teammate would say them, right above the prompt…

English | 日本語
A Claude Code mod: learn to say your daily work in the language you want to work in.
You already describe your work to your coding agent all day, in your own language. nit-band shows each prompt the way a teammate would say it in the language you are learning, plus the one expression worth learning. Save the ones you like, and your phrasebook becomes the vocabulary of your own job. Japanese → English by default.
It stays out of the transcript, so your agent's context and replies are untouched.
You send:
昨日のデプロイ以降エラーが増えているので、原因と影響範囲を調べて
Above the prompt, while your agent works:
in English: "Errors have spiked since yesterday's deploy. Can you investigate the root cause and blast radius?" [Save] [Hide]
key: 影響範囲 → blast radius
~/.nit/phrasebook.md.Prompts already in English, slash commands, code and very short prompts are skipped.
Requires Claude Code 2.1.287 or later (mods).
/plugin marketplace add 6igtree/nit-band
/plugin install nit-band@nit-band
Japanese → English by default. Change both in /config:
Japanese).English).Pick from English, Japanese, Chinese, Korean, Spanish, French, German, Portuguese, Vietnamese and Hindi; for example English → Japanese for engineers abroad working with a team in Japan. Prompts already in the language you are learning are skipped.
Each prompt you type is sent to Haiku once, through your own Claude Code session and credentials. Nothing goes anywhere else. The call runs in the background and never delays your prompt.
nit: the skill version. Your agent replies as an English-speaking teammate and gently fixes the English you write. nit-band shares its phrasebook.
MIT
hooks/register.tsx 95 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Line } from '../types'
5
6const current = atom({ plugin: 'nit-band', key: 'line' } as const, null)
7
8export function system(speak: string, learn: string): string {
9 return `You help someone who works in ${speak} learn how people at work say the same things in ${learn}.
10You get one message they wrote to their coding agent; you never answer it. Code, commands, paths and error messages are not prose; ignore them.
11
12If the message is already in ${learn}, or has no prose, answer {"skip":true}.
13
14Otherwise give how a teammate at a ${learn}-speaking company would say the whole message: natural, short, workplace ${learn}.
15Also pick the ONE expression from the message most worth learning: workplace or engineering wording that does not translate word for word (for Japanese to English, for example: 影響範囲 → blast radius, 切り戻す → roll back). Put the words from their message in "from" and the ${learn} in "to". Leave "key" out when every word is basic.
16
17Answer with JSON only:
18{"text":"<the whole message in ${learn}>","key":{"from":"<words from their message>","to":"<in ${learn}>"}}`
19}
20
21// Fenced blocks and inline code are not prose.
22export function prose(text: string): string {
23 return text.replace(/```[\s\S]*?```/g, ' ').replace(/`[^`]*`/g, ' ').replace(/\s+/g, ' ').trim()
24}
25
26export function parse(reply: string): Line | null {
27 const json = reply.match(/\{[\s\S]*\}/)
28 if (!json) return null
29 try {
30 const r = JSON.parse(json[0])
31 if (typeof r.text !== 'string' || !r.text.trim()) return null
32 const from = typeof r.key?.from === 'string' ? r.key.from.trim() : ''
33 const to = typeof r.key?.to === 'string' ? r.key.to.trim() : ''
34 return { text: r.text.trim(), key: from && to ? { from, to } : null }
35 } catch {}
36 return null
37}
38
39export const register: Register = (on, options) => {
40 const speak = String(options.speak ?? '').trim() || 'Japanese'
41 const learn = String(options.learn ?? '').trim() || 'English'
42 let generation = 0
43
44 on('prompt.submit', ($, e, next) => {
45 const mine = ++generation
46 // ponytail: one Haiku call per prompt, never awaited, so the prompt is never delayed or blocked.
47 void (async () => {
48 await update($, current, () => null)
49 const text = prose(e.text)
50 if (e.origin.kind !== 'composer' || text.startsWith('/') || text.length < 6) return
51 const r = await $.model.complete({ model: 'haiku', system: system(speak, learn), prompt: text.slice(0, 2000), maxTokens: 400, effort: 'low' })
52 const line = r.isAnswered ? parse(r.text) : null
53 if (line && mine === generation) await update($, current, () => line)
54 })().catch(() => {})
55
56 return next(e)
57 })
58
59 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
60 const line = await read($, current)
61 if (line === null || e.props.hasSurvey) return next(e)
62
63 const { Box, Button, Text } = $.ui.resolve(e)
64
65 const save = async () => {
66 const home = await $.env.get('HOME')
67 const path = `${home}/.nit/phrasebook.md`
68 const day = new Date().toISOString().slice(0, 10)
69 const learned = line.key ? ` ${line.key.from} → ${line.key.to} ·` : ''
70 const pair = speak === 'Japanese' && learn === 'English' ? '' : ` ${speak} → ${learn} ·`
71 const old = (await $.fs.exists(path)) ? await $.fs.read(path) : ''
72 await $.fs.write(path, `${old}- ${day} ·${pair}${learned} "${line.text}"\n`)
73 $.ui.toast('Saved to ~/.nit/phrasebook.md')
74 await update($, current, () => null)
75 }
76
77 return (
78 <Box flexDirection="column">
79 <Box>
80 <Text dimColor>
81 in {learn}: "{line.text}"{' '}
82 </Text>
83 <Button key="save" label="Save" onPress={save} />
84 <Button key="hide" label="Hide" onPress={() => update($, current, () => null)} />
85 </Box>
86 {line.key ? (
87 <Text>
88 key: {line.key.from} → {line.key.to}
89 </Text>
90 ) : null}
91 </Box>
92 )
93 })
94}
95types/index.d.ts 8 lines1export type Line = { text: string; key: { from: string; to: string } | null }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'nit-band': { line: Line | null }
6 }
7}
8