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