From 10bfda59ee87fbdebf9479b306c998203ce500e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Aug 2026 00:28:19 +0200 Subject: [PATCH] Document the translation workflow, and add an /add-string command for it 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) Claude-Session: https://claude.ai/code/session_0164UKoXpz9r155TRfQsoc3H --- .claude/commands/add-string.md | 57 ++++++++++++++++++++++++++++++++++ AGENTS.md | 30 ++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 .claude/commands/add-string.md diff --git a/.claude/commands/add-string.md b/.claude/commands/add-string.md new file mode 100644 index 0000000000..10323b0f70 --- /dev/null +++ b/.claude/commands/add-string.md @@ -0,0 +1,57 @@ +--- +description: Translate a UI string into all the languages in assets/lang, using Tools/langtool +argument-hint:
"" [""] +--- + +Add and/or translate a PPSSPP UI string. + +- Section: `$1` +- Key: `$2` +- English string: `$3` - if this is empty, the key already exists in `assets/lang/en_US.ini` and + you're only filling in the languages where it's still untranslated. + +Follow the workflow in AGENTS.md under "Translated UI strings (assets/lang)". Run langtool from +`Tools/langtool`: + +1. If an English string was given above: + `cargo run -- add-new-key-value "$1" "$2" "$3"` + That adds the key to every language file with the English text as a placeholder. + +2. **Work out what the string actually means before translating it.** This is the part the tool's + own AI commands can't do, and the whole reason you're doing this instead of them: + - Grep the C++ for the key to find the call site. What widget is it? A button, a checkbox + label, a tooltip, a error message? + - What do any `%1` / `%d` placeholders get substituted with at that call site? + - How much room does the UI give it - is a long translation going to be clipped? + - How are neighbouring keys in the same section already phrased in each language? That's your + style guide, use it. Formality, terminology, whether English technical terms are kept or + translated - each language file has already made those choices, so follow them. + + Say briefly what you found before you start translating. + +3. Write the translations to a scratch file outside the repo, in this shape: + + ```ini + [Single] + sv_SE = Teststräng + lt-LT = Testeilutė + ``` + + One line per language, named after the ini file minus the extension (`lt-LT`, `he_IL_invert`, + `zh_TW`, ...). No trailing `# comments` on those lines, they'd end up inside the translation. + Placeholders like `%1` and `%d` have to appear verbatim in the translation, in whatever position + the target language needs them. + + **If you don't know a language well enough to be confident, leave it out.** The English string + stays as the fallback, which is normal and fine - much better than a confident guess that nobody + in the project can read well enough to catch. + +4. `cargo run -- import-single "$1" "$2"` + + Note this overwrites any existing value for that key, so if the key already had human + translations, check what you're about to replace first. + +5. `cargo run -- validate` - always, at the end. It must print `Found 0 problems.` + +Finally, report which languages you translated and which you skipped and why, and leave the changes +uncommitted for review unless asked otherwise. diff --git a/AGENTS.md b/AGENTS.md index aea49c81b1..87b4d072bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -182,6 +182,36 @@ writing a new one (e.g. there is an LZRC decompressor in Core/FileSystems/tlzrc. For string sanitation, we already have SanitizeString in StringUtils.cpp - add new modes if needed. +## Translated UI strings (assets/lang) + +One .ini file per language, keyed by section and key against `assets/lang/en_US.ini`. **Don't hand-edit +the ~47 files**, and don't run the AI commands in `Tools/langtool` either - you can read the call site, +which its fixed prompt can't. Do the translating yourself and let the tool do the file surgery. Run it +from `Tools/langtool`: + +1. `cargo run -- add-new-key-value
"" ""` adds the key to every file, + English everywhere. (Skip if the key already exists and you're only filling in translations.) +2. Work out what the string actually means before translating it: find where it's used in the C++, + what any `%1`/`%d` placeholders get substituted with, how long it can be without breaking the + layout, and how neighboring keys are already phrased in each language (that's your style guide). + Then write a scratch file, one line per language, named after the ini file minus the extension: + ```ini + [Single] + sv_SE = Teststräng + lt-LT = Testeilutė + ``` + No trailing `# comments` on those lines, they'd end up inside the translation. Placeholders have to + survive verbatim. If you don't know a language well enough, leave it out - it keeps the English + string, which is the normal fallback, and that's much better than a confident guess. +3. `cargo run -- import-single
""` writes them in, tagged + `# AI translated`. Note that it overwrites existing values for that key, so take care with keys + that already have human translations. +4. `cargo run -- validate` at the end, always. It checks that placeholders survived and exits + non-zero if anything is off. + +The other mechanical jobs (renaming and moving keys, sorting sections, copying missing lines to all +files) are langtool commands too - prefer them over editing the ini files by hand. + ## Headless and unittest builds We have additional PPSSPPHeadless and unit test builds (/headless and /unittest), that have their own separate