The command took $1/$2/$3 off the front of whatever was typed, so it only got
the right section and key when called in exactly the documented shape. Called
with a sentence - which is the natural way to ask for this - it silently
produced three arbitrary words, and a paragraph warning about that is a poor
substitute for not doing it. It now gets $ARGUMENTS whole and works the three
values out, which is the part that needed a model anyway.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The command splits $1/$2/$3 off the invocation positionally and then states them
as fact, so calling it with a sentence rather than `/add-string <Section> "<Key>"`
yields three arbitrary words - and nothing downstream notices. Ask for them to be
checked against en_US.ini first, and to re-derive the real section and key from
the request if they don't hold up.
Also spell out, in both the command and docs/translations.md, that the en_US line
in the scratch file overwrites en_US.ini like any other language, so for an
existing key it has to match the current English text exactly - otherwise it
quietly rewords the string every other language was translated from.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup
Plenty of strings are legitimately identical in the target language - "Vsync",
"Status", "Ad Hoc multiplayer" - but the only test we had for "is this
translated" is "does it differ from English", so those got sent to the AI again
on every single run of finish-language-with-ai, costing tokens and giving it
another chance to translate something that shouldn't be.
They now get a "# same as English" comment and are skipped. finish-language-with-ai
writes it when the AI hands the English string back unchanged (which the
validation added earlier was throwing away as an error), and import-single writes
it when a language's line matches the en_US one. Deleting the comment puts the
key back in play.
Also stop comparing values with their comments still attached while working out
what's untranslated, which was leaking "# AI translated" into the context block
of the prompt.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_0164UKoXpz9r155TRfQsoc3H
It skipped the reference file, so adding a new string meant running
add-new-key-value first just to get the key into en_US.ini (and, as a side
effect, the English text into all 46 other files whether they were about to be
translated or not). Now an en_US line in the imported file is treated like any
other language, minus the "AI translated" comment, since that one is the string
the others were translated from rather than a translation.
So the workflow is one command plus validate. Languages left out of the import
simply don't get the key, and fall back to the English string at runtime -
copy-missing-lines is still there if you want the placeholder written out.
Instructions in AGENTS.md and the /add-string command updated to match.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_0164UKoXpz9r155TRfQsoc3H
Translating a UI string well means knowing what it does - what widget it is,
what the placeholders hold, how the neighbouring strings in that language are
phrased. langtool's AI commands can't know any of that, which is why their
prompt has a hand-maintained glossary that grows every time someone spots a bad
translation. An agent working in the repo can just go look.
So: AGENTS.md now describes doing the translating that way and letting langtool
do the file surgery (add-new-key-value, import-single, validate) instead of
hand-editing 47 files, and .claude/commands/add-string.md wraps it as a slash
command. Both say to skip a language rather than guess at it - the English
fallback is fine, a confident wrong translation nobody can proofread is not.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_0164UKoXpz9r155TRfQsoc3H