KilimcininKorOglu/claude-code-mods/tree/main/plugins/i18n-watch
i18n-watch
在调用翻译键的编辑后,命名 JSON、Laravel PHP、YAML 或 gettext 语言环境文件缺少的键以及缺少它们的语言。
关于这个 mod
#i18n-watch
该模型将 t('checkout.total') 添加到组件中,并且密钥不存在于区域设置文件中,或者仅以英语存在。然后,该页面会显示原始密钥,或者土耳其语页面上的英语,除非用户注意到,否则没有人会注意到。当编辑使用一个或多个语言环境文件缺少的翻译键时,此模式会告诉模型。该注释随编辑结果一起提供,因此模型会在同一轮中添加关键帧。默认情况下,什么都不停止;在 deny 模式下,当密钥丢失时,提交、推送和合并都会停止。
它的作用
1。该模组挂钩编辑和写入工具。成功调用源文件后(.ts、.tsx、.js、.jsx、.mjs、.cjs、.vue、.svelte、.astro、.php、.py、 .rb、.erb、.haml、.slim),它读取翻译调用添加的编辑:new_string中old_string没有的那些,或者每次调用Write。
2。读取的调用为 t、$t、i18n.t、__、trans、trans_choice、@lang、_、gettext 和 ngettext 以及带引号的第一个参数,也在之后this.、vm.、i18n.、$i18n.、I18n. 和 i18n.global.。变量参数、模板文字和 Rails 惰性键 (t('.title')) 将被跳过。
3。它读取会话目录的这些目录下的语言环境文件,最多 4 级别深度和 200 文件,跳过 node_modules 和 2 MB 上的任何文件:locales、lang、i18n、translations、locale、 t('checkout.total')Q、resources/lang、src/locales、src/i18n、public/locales。会话目录是会话启动的目录,在启动时读取一次,因为 Bash cd 会移动会话自己的目录。编辑的文件是根据会话启动的 git 存储库命名的,因此在 apps/web 中打开的会话将 apps/api 的文件命名为 apps/api/x.ts。在 git 存储库之外,文件是根据会话目录命名的。该根也会在会话开始时读取一次。
|格式|示例路径 |按键|
|---|---|---|
| JSON(i18next、vue-i18n、Laravel)| locales/tr.json、locales/tr/checkout.json、lang/tr.json |嵌套键作为点路径; item_one 还定义了 item |
| PHP 数组 (Laravel) | lang/tr/messages.php | messages.key,嵌套数组作为点路径 |
| YAML(Rails、Symfony)| config/locales/tr.yml、translations/messages.tr.yaml |虚线路径; Rails 顶部键 (tr:) 被遗漏 |
|获取文本 | locale/tr/LC_MESSAGES/django.po |每个msgid |
语言来自目录(tr/、en-US/)或来自文件名(tr.json、messages.tr.yaml)。命名空间文件中的键也算作 ns.key 和 denyQ。4。当一种语言缺少某个键,或者当没有一种语言拥有该键时,该键就丢失了。每个键都以调用它的行命名,因此您可以打开它。模型在编辑结果后读取此注释:
i18n-watch: this edit uses translation keys the locale files lack: checkout.total:42 (missing in tr, de) · checkout.vat:58 (missing in every locale). Add them to each locale file.
最多命名 10 键,其余的都算在内。仅报告此编辑添加的键;之前报告的一个关键留在了调查结果中,没有再次提及。 5。与此同时,一行到达了文字记录,因此您可以看到模型被告知的内容。该行保存文件及其密钥,无需指令。该文件之所以命名是因为模型看到了编辑,而您没有:
i18n-watch: keys srcQXZKQZXDAQ.vue uses that the locale files lack: checkout.total:42 (missing in tr, de) · checkout.vat:58 (missing in every locale)
注释和线条是独立的通道:模型从不读取线条,您也从不读取注释。
6。当 侧边栏 打开时,这些键会作为其流中的条目而转到那里:首先是文件,然后每个键一行,键为红色,其行号微弱,every locale 为红色,一些区域设置列表为黄色。成绩单保持干净。该条目会一直保留,直到新的条目将其从窗格中推出为止。在侧边栏关闭或未安装该模组的情况下,脚本行将如上所示。
7。发现从来都不是被记住的答案。它持有的密钥是一个声明,每个措施都会再次从磁盘读取源文件并删除它不再调用的密钥。因此,一个发现有两种方式,在每次编辑和写入之后、在每个主循环结束时以及在受保护的 git 命令之前进行测量:
- 每个地点都获得了钥匙;
- 代码停止调用它们,因为编辑删除了字符串,将其替换为另一个字符串或将其移动到另一个文件。一个文件消失了,它的发现也就结束了。
该条目已清除,绿色条目表示它是两个中的哪一个:
i18n-watch: every locale now has the keys srcQXZKQZXDCQ.vue lacked: checkout.total · checkout.vat
i18n-watch: index.php no longer uses: Unauthorized Access
当侧边栏关闭时,相同的文本是一个转录行。该模型没有读取任何内容:该发现由其自己的工作完成,因此注释只会重复它刚才所做的事情。存在但无法读取的文件会保留其发现,因为未读的文件无法证明任何内容。
8。在每个主循环结束时再次测量模型未关闭的发现,剩下的内容作为一个注释到达模型并带有下一个提示:
.tsxQ
每回合一个音符,而不是每个提示一个音符。如果没有这个,结果就会在编辑时说一次,然后站在窗格中,而模型却忘记了它。您没有读到任何新内容:该窗格已经包含相同的发现。9。在 deny 模式下,mod 还会停止 git commit、git push 和 git merge,而文件仍然使用语言环境文件缺少的密钥;带有 --dry-run、--help 或 -h 的命令不会停止。 git commit 仅对自己的文件负责:mod 读取索引 (git diff --cached --name-only -z),并在没有打开的文件时运行提交,并用一行告诉您还有多少个文件仍然存在。 push 和 merge 没有可供读取的索引,因此每个发现都在那里。没有旁路;只有您用 /i18n-watch mode note 关闭门。 note 模式是默认模式,不会停止任何操作,但它仍然会测量 git 命令的结果,因此确定的结果不会保留在窗格中。
语言环境文件在需要它们的轮次的第一次编辑时被读取,并且在编辑或写入语言环境文件之后再次被读取。没有这些目录的项目什么也得不到。每个会话将跳过并记录一次无法读取或解析的区域设置文件。
在实时检查中,模型将 t('cart.total') 添加到具有 locales/en.json 和 locales/tr.json 的项目文件中,阅读编辑后的注释,并逐字引用。
命令
QXZKQZXDDQ on or off, the mode, and the files still missing keys
QXZKQZXDEQ on | off on by default
QXZKQZXDFQ mode note note only; the default
QXZKQZXDGQ mode deny a commit, a push and a merge also stop while a key is missing
安装
claude plugin marketplace add KilimcininKorOgluQXZKQZXDHQ
claude plugin install i18n-watch@kilimcininkoroglu-mods
函数钩子是抢先体验的。 Claude Code 2.1.288 以及后来默认加载它们,所以没有什么可以打开的。
安装后
1。重新启动Claude Code。
它可以达到什么
在 Claude Code 2.1.283 上使用 claude plugin validate 进行验证:
❯ .QXZKQZXDIQ.ts hooks: session.start, command.run{command=i18n-watch}, turn.start, tool.call{tool=Bash}, turn.complete, prompt.submit, tool.call{tool=Edit}, tool.call{tool=Write}
❯ .QXZKQZXDJQ.ts calls: $.command.register, $.fs.exists (via isDir, usedNow), $.fs.list (via walkLocales), $.fs.read (via loadCatalog, usedNow), $.fs.stat (via isDir), $.process.run (via shownRootOf, stagedPaths), $.session.cwd, $.sidebar.clear (via dropEntry), $.sidebar.set (via toPerson), $.store.get (via readSettings), $.store.set (via runCommand, setMode), $.ui.log (via catalogOf, gate, toPerson)
到达L2,它运行git来读取索引。
1. Reads: the text of each Edit and Write call; the Bash command text; each reported source file again; the locale directories under the session directory and their files
2. Runs: git rev-parse --show-toplevel once at the session's start, to name files against the repository root; git rev-parse --show-toplevel and git diff --cached --name-only -z, at a guarded command in deny mode, to read which files the commit holds
3. Sends: a note to the model after an edit that uses missing keys, one more with the next prompt while a finding stands, and one line to the transcript; nothing leaves the machine
4. Persists: in $.store, the onQXZKQZXDKQ setting and the mode; the locale keys live in memory for one turn
5. Hostile input: locale files are only parsed as data (JSON.parse and line regexes), never run; PHP files are not executed
限制
- 仅读取会话目录下的语言环境目录。当会话在存储库根目录启动时,不会看到区域设置位于
apps/web/src/locales的 monorepo。 - Laravel PHP 文件是逐行读取的,而不是运行:在一行上打开和关闭的数组,看不到计算出的键和
included 数组。 - YAML 通过缩进读取。不遵循锚点、别名和流映射 (
{a: b})。 - 不检查运行时构建的密钥(
t(name)、t(`a.${b}`))。 - 语言代码是两个字母,带有可选区域或脚本(
tr、.tsQ、zh-Hant);无法识别fil等三字母代码。 - 不检查仅编辑移动的键(也在
old_string中),也不检查通过 Bash 进行的编辑。 deny模式没有旁路。当无法修复问题时,可以使用/i18n-watch mode note关闭门。- 门读取命令文本。通过隐藏
git commit的脚本或别名的提交不会停止。 git commit -a、-am和--之后带有路径规范的提交不会缩小到索引,因为它们提交索引尚未保存的文件。每一个公开的发现都代表着这些。- 在命令运行之前读取索引。文件在读取和运行之间发生变化的提交(同时另一个进程暂存)是根据读取时索引所保存的内容来衡量的。- 查找结果是通过文件进行的调用而不是区域设置文件自己的使用来关闭的。即使语言环境文件仍然缺少代码停止调用的键,也会从查找结果中删除该键,因为没有任何东西再调用它。
发展
make install # eslint, typescript-eslint, typescript
make lint # complexity limit 10, the build fails above it
make typecheck # needs .claudeQXZKQZXDLQ/ from QXZKQZXDMQ
make validate
make test # claude plugin test
安装
请先查看作者 README 确认 marketplace 和插件名称;命令可能随仓库结构改变。
claude plugin marketplace add KilimcininKorOglu/claude-code-mods claude plugin install i18n-watch
原文 / README
i18n-watch
The model adds t('checkout.total') to a component, and the key exists in no locale file, or in English only. The page then shows the raw key, or English on the Turkish page, and nobody notices until a user does. This mod tells the model when an edit uses translation keys that one or more locale files lack. The note comes with the Edit's result, so the model adds the keys in the same turn. By default nothing is stopped; in deny mode a commit, a push and a merge stop while a key is missing.
What it does
-
The mod hooks the Edit and Write tools. After a successful call on a source file (
.ts,.tsx,.js,.jsx,.mjs,.cjs,.vue,.svelte,.astro,.php,.py,.rb,.erb,.haml,.slim), it reads the translation calls the edit added: those innew_stringthatold_stringdoes not have, or every call of a Write. -
The calls read are
t,$t,i18n.t,__,trans,trans_choice,@lang,_,gettextandngettextwith a quoted first argument, also afterthis.,vm.,i18n.,$i18n.,I18n.andi18n.global.. A variable argument, a template literal and a Rails lazy key (t('.title')) are skipped. -
It reads the locale files under these directories of the session directory, at most 4 levels deep and 200 files, skipping
node_modulesand any file over 2 MB:locales,lang,i18n,translations,locale,config/locales,resources/lang,src/locales,src/i18n,public/locales. The session directory is the one the session started in, read once at its start, because a Bashcdmoves the session's own directory. The edited file is named against the git repository the session started in, so a session opened inapps/webnames a file ofapps/apiasapps/api/x.ts. Outside a git repository the file is named against the session directory. That root is read once at the session's start too.| Format | Example path | Keys | |---|---|---| | JSON (i18next, vue-i18n, Laravel) |
locales/tr.json,locales/tr/checkout.json,lang/tr.json| nested keys as dotted paths;item_onealso definesitem| | PHP array (Laravel) |lang/tr/messages.php|messages.key, nested arrays as dotted paths | | YAML (Rails, Symfony) |config/locales/tr.yml,translations/messages.tr.yaml| dotted paths; a Rails top key (tr:) is left out | | gettext |locale/tr/LC_MESSAGES/django.po| eachmsgid|The language comes from a directory (
tr/,en-US/) or from the file name (tr.json,messages.tr.yaml). A key in a namespace file also counts asns.keyandns:key. -
A key is missing when a language lacks it, or when no language has it. Each key is named with the line it is called on, so you can open it. The model reads this note after the Edit's result:
i18n-watch: this edit uses translation keys the locale files lack: checkout.total:42 (missing in tr, de) · checkout.vat:58 (missing in every locale). Add them to each locale file.At most 10 keys are named, the rest counted. Only the keys this edit added are reported; a key reported before stays in the finding without being said again.
-
At the same moment one line reaches the transcript, so you see what the model was told. The line holds the file and its keys, without the instruction. The file is named because the model saw the edit and you did not:
i18n-watch: keys src/Cart.vue uses that the locale files lack: checkout.total:42 (missing in tr, de) · checkout.vat:58 (missing in every locale)The note and the line are separate channels: the model never reads the line, and you never read the note.
-
While the sidebar is open, those keys go there instead, as an entry in its stream: the file first, then one line per key, with the key red, its line number faint,
every localered and a list of some locales yellow. The transcript stays clean. The entry stays until newer ones push it off the pane. With the sidebar closed, or without that mod installed, the transcript line is written as above. -
A finding is never a remembered answer. The keys it holds are a claim, and every measure reads the source file from disk again and drops the keys it no longer calls. So a finding closes two ways, measured after each Edit and Write, at the end of each main-loop turn, and before a guarded git command:
- every locale gained the keys;
- the code stopped calling them, because the edit deleted the string, replaced it with another one or moved it to another file. A file that is gone closes its finding too.
The entry is cleared and a green one says which of the two it was:
i18n-watch: every locale now has the keys src/Cart.vue lacked: checkout.total · checkout.vat i18n-watch: index.php no longer uses: Unauthorized AccessWith the sidebar closed the same text is one transcript line. The model reads nothing of this: the finding closed by its own work, so a note would only repeat what it just did. A file that is there and cannot be read keeps its finding, because an unread file proves nothing.
-
A finding the model did not close is measured again at the end of each main-loop turn, and what is left reaches the model as one note with its next prompt:
i18n-watch: 1 file(s) still use translation keys the locale files lack: index.php (Unauthorized Access:42). Add the keys to every locale file, or take the calls out.One note per turn, not one per prompt. Without this the finding would be said once, at the edit, and then stand in the pane while the model forgot it. You read nothing new: the pane already carries the same finding.
-
In
denymode the mod also stopsgit commit,git pushandgit mergewhile a file still uses keys the locale files lack; a command with--dry-run,--helpor-his not stopped. Agit commitanswers for its own files alone: the mod reads the index (git diff --cached --name-only -z) and lets the commit run when it holds none of the open files, with one line to you naming how many still stand. Apushand amergehold no index to read, so every finding stands there. There is no bypass; only you turn the gate off, with/i18n-watch mode note.notemode is the default and stops nothing, but it measures the findings at a git command all the same, so a settled one does not stay in the pane.
The locale files are read at the first edit of a turn that needs them, and again after an Edit or Write of a locale file. A project without these directories gets nothing. A locale file that cannot be read or parsed is skipped and logged once per session.
In the live check the model added t('cart.total') to a file of a project with locales/en.json and locales/tr.json, read the note after the Edit, and quoted it word for word.
Command
/i18n-watch on or off, the mode, and the files still missing keys
/i18n-watch on | off on by default
/i18n-watch mode note note only; the default
/i18n-watch mode deny a commit, a push and a merge also stop while a key is missing
Install
claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install i18n-watch@kilimcininkoroglu-mods
Function hooks are early access. Claude Code 2.1.288 and later load them by default, so there is nothing to switch on.
After installing
- Restart Claude Code.
What it can reach
Validated with claude plugin validate on Claude Code 2.1.283:
❯ ./register.ts hooks: session.start, command.run{command=i18n-watch}, turn.start, tool.call{tool=Bash}, turn.complete, prompt.submit, tool.call{tool=Edit}, tool.call{tool=Write}
❯ ./register.ts calls: $.command.register, $.fs.exists (via isDir, usedNow), $.fs.list (via walkLocales), $.fs.read (via loadCatalog, usedNow), $.fs.stat (via isDir), $.process.run (via shownRootOf, stagedPaths), $.session.cwd, $.sidebar.clear (via dropEntry), $.sidebar.set (via toPerson), $.store.get (via readSettings), $.store.set (via runCommand, setMode), $.ui.log (via catalogOf, gate, toPerson)
Reach L2, it runs git to read the index.
1. Reads: the text of each Edit and Write call; the Bash command text; each reported source file again; the locale directories under the session directory and their files
2. Runs: git rev-parse --show-toplevel once at the session's start, to name files against the repository root; git rev-parse --show-toplevel and git diff --cached --name-only -z, at a guarded command in deny mode, to read which files the commit holds
3. Sends: a note to the model after an edit that uses missing keys, one more with the next prompt while a finding stands, and one line to the transcript; nothing leaves the machine
4. Persists: in $.store, the on/off setting and the mode; the locale keys live in memory for one turn
5. Hostile input: locale files are only parsed as data (JSON.parse and line regexes), never run; PHP files are not executed
Limits
- Only the locale directories under the session directory are read. A monorepo whose locales sit in
apps/web/src/localesis not seen when the session starts at the repository root. - A Laravel PHP file is read line by line, not run: an array opened and closed on one line, a computed key and an
included array are not seen. - YAML is read by indent. Anchors, aliases and flow mappings (
{a: b}) are not followed. - A key built at run time (
t(name),t(`a.${b}`)) is not checked. - A language code is two letters with an optional region or script (
tr,pt_BR,zh-Hant); a three-letter code such asfilis not recognised. - A key the edit only moves (it was in
old_stringtoo) is not checked, and neither is an edit through Bash. - The
denymode has no bypass. When a finding cannot be fixed, you turn the gate off with/i18n-watch mode note. - The gate reads the command text. A commit through a script or an alias that hides
git commitis not stopped. - A
git commit -a, a-amand a commit with a pathspec after--are not narrowed to the index, because they commit files the index does not hold yet. Every open finding stands for those. - The index is read before the command runs. A commit whose files change between the read and the run (another process staging meanwhile) is measured against what the index held at the read.
- A finding is closed by the calls the file makes, not by the locale files' own use. A key the code stopped calling is dropped from the finding even when the locale files still lack it, because nothing calls it any more.
Development
make install # eslint, typescript-eslint, typescript
make lint # complexity limit 10, the build fails above it
make typecheck # needs .claude/types/ from /plugin-types
make validate
make test # claude plugin test

