Skip to content
Mobile
Skill

/find-missing-translations

Translation audits of `strings.xml` locales in `commonsUI/.../composeResources` and `amethyst/src/main/res`: missing or untranslated keys, CLDR plural categories, Crowdin sync rules, lint gates. Use before translating or seeding locale files.

From plugin
amethyst
1.6k12 skills

Claude Code

$ npx -y skills add vitorpamplona/amethyst --skill find-missing-translations --agent claude-code

SKILL.md

find-missing-translations.SKILL.md
name: find-missing-translations
description: Translation audits of `strings.xml` locales in `commonsUI/.../composeResources` and `amethyst/src/main/res`: missing or untranslated keys, CLDR plural categories, Crowdin sync rules, lint gates. Use before translating or seeding locale files.

Find missing translations

How to author a new string (where it goes, `Res.string`, positional args, Compose escaping) is in AGENTS.md "Strings" — not repeated here. This skill is the audit: diff locales against their default, fill the gaps, and pass the gates.

Two catalogs — scan both, report separately

Each is its own Crowdin source in `crowdin.yml`, so each count reconciles against its own Crowdin UI number.

| Catalog | Default file | Size | |--|--|--| | **commonsUI** (Compose resources, Android + Desktop) | `commonsUI/src/commonMain/composeResources/values/strings.xml` | ~6,155 `<string>` + ~198 `<plurals>` | | **amethyst** (Android `res/`) | `amethyst/src/main/res/values/strings.xml` | ~191 `<string>` + ~13 `<plurals>` — the synchronous-platform tier only |

  • New keys always go in commonsUI. A feature key found in the amethyst tree belongs in commonsUI: move it with `tools/strings-migrate/migrate.py <keys…>` (moves every locale byte-for-byte), don't translate it in place.
  • Locale dirs differ between the trees (56 vs 57) — enumerate `values-*` under each base, don't assume.
  • A key name present in both trees does **not** mean the same English. Copy an existing translation across trees only when the two English values are byte-identical; otherwise translate fresh (or copy from the key whose *value* matches).
  • The trees escape differently. commonsUI authors edge whitespace raw, never quote-wrapped, and `\'` renders literally there; copying Android-style `"replying to "` or `\'` into commonsUI is a bug. `tools/agent-hooks/compose_escaping_check.py` catches it (CI and pre-push).
  • Same English repeated under many keys (`Cancel`, `Save`, …) across the trees is expected. Don't dedupe during a translation pass.

Default target locales

`values-cs`, `values-de-rDE`, `values-sv-rSE`, `values-pt-rBR` (unless the user names others). Czech is on the base qualifier because `crowdin.yml` maps `cs: cs` (in both sources); there is no `values-cs-rCZ`.

Crowdin rules that decide what sticks

  • `.github/workflows/crowdin.yml` passes `import_eq_suggestions: true`, so the upload seeds Crowdin with whatever is in the repo locale files, values identical to the source included, as **pending** (unapproved) suggestions. So:
  • **Do copy the English verbatim** for keys a locale keeps in English (brand names like `Nowhere …`, loanwords like `Apps`/`Feed`, format-only values like `v%1$s`, `+%1$d`, example placeholders). Leaving them out keeps them untranslated in Crowdin forever.
  • **Never for `<plurals>`** — English `one`/`other` in cs/pl fails `MissingQuantity`.
  • **Not for words the locale really translates** (German "Arbeitsbereich", "Training"): list those for a human translator instead of seeding a wrong suggestion.
  • **A repo-side edit to a translated value only sticks where Crowdin's database doesn't contradict it.** Download replaces file content with Crowdin's export. Adding a key Crowdin holds nothing for sticks; overwriting a value Crowdin holds differently — including an *empty* value — is reverted on the next sync. Durable value fixes go in the Crowdin web UI. Edits to the **source** `values/strings.xml` do stick (it is Crowdin's input).
  • **`tools:ignore` goes on the source entry in `amethyst/src/main/res/values/strings.xml`, never on a locale file** — Crowdin propagates source attributes into every export and strips ones added only to a locale. Never add `tools:` attributes to the commonsUI catalog at all: its root doesn't declare the namespace, so the XML becomes malformed (the escaping check flags it). Before suppressing, `grep -rn "<key>" --include='*.kt'`: an unused key should be deleted instead.
  • Reading a Crowdin sync diff: compare key **sets** per file, not `-`/`+` lines; Crowdin reorders whole files.

1. Diff each locale, both resource types

Diff `<string>` **and** `<plurals>` separately (a missing `<plurals>` never shows in a `<string>` diff), and diff **every** target locale — files are asymmetric, so `cs` is not an upper bound.

keys() { grep "<$2 name=" "$1" | grep -v 'translatable="false"' | sed 's/.*name="\([^"]*\)".*/\1/' | sort -u; }
for base in commonsUI/src/commonMain/composeResources amethyst/src/main/res; do
  echo "## $base"
  for l in cs de-rDE sv-rSE pt-rBR; do
    for t in string plurals; do
      m=$(comm -23 <(keys $base/values/strings.xml $t) <(keys $base/values-$l/strings.xml $t))
      echo "$l $t: $(printf '%s' "$m" | grep -c .)"; printf '%s\n' "$m" | sed '/^$/d;s/^/    /'
    done
  done
done

The `strings + plurals` total per locale should match Crowdin's "N untranslated". Pull English values with `grep "name=\"$key\""` for strings; a `<plurals>` is a multi-line block (opening tag through `</plurals>`).

2. Audit before translating

Stop and ask the user about any missing English string that looks plural-shaped: a hardcoded `1` next to a noun (`"1 reply"`), or a count placeholder whose noun changes with the count (`"%1$d follower"`, thresholds like "more than %1$d hashtags"). It should become a `<plurals>` first; translating the wrong shape means fixing it in every locale. The pattern is noisy (`"Limits for kind %1$d"` isn't plural) — review by hand.

Also scan the trees you touch for:

  • `quantity="one"` items with a literal digit and no `%d`/`%1$d` placeholder.
  • `quantity="zero"` items outside `values-ar*`, `values-lv*` (and Welsh, `cy`, if a dir ever appears) — dead code: CLDR maps 0 to `other` there. For "no items" wording use a separate `<string>` and an `if (count == 0)` branch at the call site.
  • Empty items `<item quantity="…"></item>` — render as nothing, and Polish `many` is the common case.
  • Placeholder parity between l
Read more
@vitorpamplona
Ships withamethyst

Nostr client for Android

Stars on GitHub

Momentum

stars in total
1.6k
Star history of vitorpamplona/amethyst on GitHub, a chart by star-history.com

Other skills on amethyst.

amy-expert

amy-expert

amy CLI in cli/: adding an `amy <verb>` command, Main.kt dispatch, Output.emit/--json contract, exit codes 0/1/2/124, ~/.amy…

android-expert

android-expert

Android shim in `amethyst/`: MainActivity, AndroidManifest/intent filters, :napplet process, play/fdroid flavors, R8 +…

auth-signers

auth-signers

NostrSigner in Amethyst: NostrSignerInternal (local key), NostrSignerRemote (NIP-46 bunker), NostrSignerExternal (NIP-55 Android…

compose-expert

compose-expert

Shared Compose UI in `commonsUI/`: screens, components, the nav shell (`ScreenLayoutSpec`, `AppShellLayout`, Navigation 3 back…

desktop-expert

desktop-expert

Desktop JVM shim in desktopApp/: AmethystDesktop window, DesktopMenuBar, platform ports, OS keyring; :desktopApp:run, packaging.…

More skills from other plugins.

deep-code-review

deep-code-review

In-depth design-focused code review - understands codebase context before evaluating PR changes, posts structured feedback to…

expo3d53k
@expo@expoInstall Skill
modlens

modlens

Plug-in vision for text-only models. Hard rule: when a file path or URL with an image extension (.png, .jpg, .jpeg, .webp, .gif,…

modlens3w4.6k
@liustack@liustackInstall Skill
appllama-app-design-skill

appllama-app-design-sk…

Build native-feeling, benchmark-quality mobile app screens (Expo / React Native). Use when designing or implementing any mobile…

appllama2mo3.9k
@appllama@appllamaInstall Skill

Questions about find-missing-translations.

Which coding agents can use find-missing-translations?
find-missing-translations installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.