From 7f54a2645f4c2cf8e7fbeb4fab75beb71f5285e6 Mon Sep 17 00:00:00 2001 From: davotoula Date: Sun, 10 May 2026 11:46:45 +0200 Subject: [PATCH] docs(skills): update skill to warn on plural-shaped strings in find-missing-translations --- .../skills/find-missing-translations/SKILL.md | 78 ++++++++++++++++++- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/.claude/skills/find-missing-translations/SKILL.md b/.claude/skills/find-missing-translations/SKILL.md index fe0163c5de..9f8c14109c 100644 --- a/.claude/skills/find-missing-translations/SKILL.md +++ b/.claude/skills/find-missing-translations/SKILL.md @@ -67,7 +67,62 @@ done < <(comm -23 \ | sed 's/.*name="\([^"]*\)".*/\1/' | sort)) ``` -### 4. Present results and ask to translate +### 4. Audit missing strings for plural-shaped patterns + +Before presenting results, **scan the missing English strings** for two red-flag patterns and warn the user about each match: + +1. **Hardcoded `"1"` next to a noun.** A new English string like `"1 reply"`, `"1 follower"`, or `"1 minute ago"` almost always belongs in a `` resource — not a ``. Hardcoding `1` in English forces every translator to either also hardcode `1` (breaking languages where the `one` category covers other numbers, e.g. some Slavic languages) or to silently change the meaning. +2. **A `%d` / `%1$d` placeholder in a clearly singular/plural sentence** (e.g. `"%1$d reply"`, `"%d follower"`). Even though the placeholder is parameterised, English-only `one`/`other` agreement won't survive translation into languages that need `few`/`many`. + +Also **audit existing `` resources** for the same anti-pattern — any locale's `quantity="one"` item that hardcodes the literal `1` (instead of using a `%d` / `%1$d` placeholder) is broken for languages where the `one` CLDR category covers more than just `n=1` (Russian, Ukrainian, Croatian, etc.). Flag and offer to fix: + +```bash +# Scan every locale's strings.xml for entries that +# hardcode "1" (or other literal digits) instead of using a placeholder. +# Looks at default + all values-* locales. +for f in amethyst/src/main/res/values/strings.xml amethyst/src/main/res/values-*/strings.xml; do + awk -v file="$f" ' + / and <) + text = $0; sub(/^[^>]*>/, "", text); sub(/<.*$/, "", text) + # Flag if it contains a digit AND no %d / %1$d placeholder + if (text ~ /[0-9]/ && text !~ /%[0-9]*\$?d/) { + print file ": one=\"" text "\"" + } + } + /<\/plurals>/ { in_plurals = 0 } + ' "$f" +done +``` + +Quick scan over the missing keys: + +```bash +# Flag missing English values that look like they should be +while IFS= read -r key; do + line=$(grep "name=\"$key\"" amethyst/src/main/res/values/strings.xml) + # Hardcoded standalone "1" (word-boundary), or a count placeholder followed by a likely-countable noun + if echo "$line" | grep -qE '>([^<]*\b1\b[^<]*|[^<]*%[0-9]*\$?d[^<]*)<'; then + echo "PLURAL CANDIDATE: $line" + fi +done < <(comm -23 \ + <(grep ' ⚠️ `notification_count` is `"1 new reply"` — this hardcodes `"1"` and should likely be a `` resource (e.g. `quantity="one"` → `"%d new reply"`, `quantity="other"` → `"%d new replies"`). Convert before translating? + +Do not silently translate plural-shaped `` entries; the wrong shape will then need to be fixed in every locale. + +### 5. Present results and ask to translate Output the missing entries as raw XML resource lines (copy-paste ready): @@ -79,9 +134,24 @@ Output the missing entries as raw XML resource lines (copy-paste ready): Also check `` and `` tags using the same approach if the project uses them. +#### Plurals: handle with care + +When adding or proposing **``** entries, follow these rules: + +- **Never hardcode `"1"`** in the English text of a `quantity="one"` item. Use the format placeholder (e.g. `%1$d` / `%d`) so the runtime substitutes the actual count. Hardcoding `"1"` breaks every language whose `one` category covers numbers other than 1 (e.g. some Slavic languages). +- **Don't assume `one` + `other` is enough.** CLDR plural categories vary by language: `zero`, `one`, `two`, `few`, `many`, `other`. Always include **every category the target language uses**, not just the categories present in English. Examples: + - English (`en`): `one`, `other` + - Czech (`cs`): `one`, `few`, `many`, `other` + - Polish (`pl`): `one`, `few`, `many`, `other` + - Russian (`ru`): `one`, `few`, `many`, `other` + - Arabic (`ar`): `zero`, `one`, `two`, `few`, `many`, `other` + - German / Swedish / Brazilian Portuguese: `one`, `other` +- When a missing string contains a count placeholder and is conceptually a singular/plural pair, **flag it before translating** — it may belong as a `` resource rather than a single ``. Surface this to the user before proposing translations. +- Reference: [Android `` docs](https://developer.android.com/guide/topics/resources/string-resource#Plurals) and [CLDR plural rules](https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html). + **Then ask the user:** "Would you like me to translate these missing strings into [list of target locales]?" -### 5. Adding translations (if approved) +### 6. Adding translations (if approved) When adding translated strings to locale files: @@ -93,4 +163,6 @@ When adding translated strings to locale files: - **Forgetting `translatable="false"`** — these should never appear in locale files - **Not checking string-arrays/plurals** — only checking `` misses other resource types - **Diffing each locale separately** — only diff against `cs-rCZ`; assume the same keys are missing everywhere -- **Inserting strings in a specific position** — always append at the bottom; ordering is handled separately \ No newline at end of file +- **Inserting strings in a specific position** — always append at the bottom; ordering is handled separately +- **Hardcoding `"1"` in a `` `quantity="one"` item** — always use the count placeholder; otherwise non-English `one` categories produce wrong text +- **Copying English's `one`/`other` set into every locale** — each language must include all CLDR plural categories it uses (e.g. Czech needs `one`, `few`, `many`, `other`) \ No newline at end of file