Skip to content
Development
Skill

/find-missing-translations

Use when comparing Android strings.xml locale files to find untranslated string resources, missing translation keys, or preparing translation work for a specific language

From plugin
amethyst
1.6k30 skills3 commands
Install
$ npx -y skills add vitorpamplona/amethyst --skill find-missing-translations --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/find-missing-translations

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when comparing Android strings.xml locale files to find untranslated string resources, missing translation keys, or preparing translation work for a specific language

SKILL.md

find-missing-translations.SKILL.md
name: find-missing-translations
description: Use when comparing Android strings.xml locale files to find untranslated string resources, missing translation keys, or preparing translation work for a specific language

Find Missing Translations

Overview

Extract string resource keys from a default `values/strings.xml` that are absent in a target locale's `strings.xml`, excluding non-translatable entries. Outputs missing keys and offers to translate them.

The repo now has **two independent Crowdin-managed resource trees** — you must scan **both** (see "Resource trees" below).

When to Use

  • Need to find untranslated strings for a specific locale
  • Preparing a batch of strings for a translator
  • Checking translation coverage after adding new features

Resource trees (scan BOTH)

There are two separate `strings.xml` trees, each with its own default `values/` and per-locale `values-<locale>/` files, each wired into `crowdin.yml` independently:

| Tree | Default file | Per-locale file | |------|--------------|-----------------| | **amethyst** (Android app) | `amethyst/src/main/res/values/strings.xml` | `amethyst/src/main/res/values-<locale>/strings.xml` | | **commons** (KMP Compose resources, shared by Android + Desktop) | `commons/src/commonMain/composeResources/values/strings.xml` | `commons/src/commonMain/composeResources/values-<locale>/strings.xml` |

The `commons` tree appeared when shared event-renderer composables were extracted out of `amethyst/` into `commons/` (Compose Multiplatform `stringResource`). It is **not** a copy of the amethyst tree — the vast majority of its keys are commons-only; only a small handful overlap. Every diff/count/translate command below works on either tree by swapping the base path — **run the whole technique once per tree** and report them separately (each maps to its own Crowdin file, so the counts should reconcile against two different Crowdin UI numbers).

**Locale-qualifier caveat:** `commons` uses the same region-qualified locale dirs as amethyst for our four targets (`values-cs`, `values-de-rDE`, `values-sv-rSE`, `values-pt-rBR`), but the *full* set of locale dirs differs between trees. Enumerate `values-*` under each tree's own base rather than assuming they match.

**Overlap (copy — but only after checking the English matches):** a few `commons` keys share a *name* with a key in the amethyst tree. For such a key already translated in the amethyst locale file you may **copy the existing approved translation verbatim** — but **only if the two English source values are byte-identical.** A shared key name does **not** guarantee a shared meaning.

> ⚠️ **Mistake we actually made (2026-07-18):** `napplet_card_permissions` exists in *both* trees with the *same key name* but *different English* — commons = `"What it can access"`, amethyst = `"Permissions:"`. Copying the amethyst translation by key name produced the wrong string in commons (it said "Permissions:" where the UI reads "What it can access"). **Always diff the English values, not just the key names.** When the English differs, translate the commons value fresh — or, better, find the amethyst key whose *value* matches (here `favorite_app_access_show` = "What it can access") and copy *that* approved translation.

Detect name-overlap **and flag value mismatches** in one pass:

cdef=commons/src/commonMain/composeResources/values/strings.xml
adef=amethyst/src/main/res/values/strings.xml
comm -12 \
  <(grep '<string name=' "$cdef" | sed 's/.*name="\([^"]*\)".*/\1/' | sort -u) \
  <(grep '<string name=' "$adef" | grep -v 'translatable="false"' | sed 's/.*name="\([^"]*\)".*/\1/' | sort -u) \
| while read -r k; do
    cv=$(grep -m1 "name=\"$k\"" "$cdef" | sed 's/.*>\(.*\)<\/string>/\1/')
    av=$(grep -m1 "name=\"$k\"" "$adef" | sed 's/.*>\(.*\)<\/string>/\1/')
    [ "$cv" = "$av" ] && echo "SAFE-COPY   $k" || echo "VALUE-DIFFERS $k  commons=\"$cv\"  amethyst=\"$av\""
  done

Only `SAFE-COPY` keys may be copied verbatim. For `VALUE-DIFFERS`, translate the commons English fresh (or copy from the amethyst key that has the *matching value*).

**Whitespace-quote convention differs between trees.** Android string resources use surrounding double-quotes to preserve leading/trailing whitespace (`"replying to "`). The **commons Compose-resources tree does NOT use this convention** — it authors trailing/leading spaces raw and unquoted (`replying to `). So when copying/translating a commons string with edge whitespace, **match the commons source: raw spaces, no wrapping quotes.** (Mistake we made: we copied amethyst's quoted `"replying to "` into commons, where the quotes would render literally.) A quick check for stray quote-wrapping you introduced:

grep -nE '<string name="[^"]*">"' commons/src/commonMain/composeResources/values-*/strings.xml
# The commons English tree has zero quote-wrapped values — any hit in a locale file is almost certainly a bad copy from amethyst.

**Why two catalogs exist — the duplication is NOT a bug to "fix" (don't ask again).** You will see the same English text (`Cancel`, `Save`, `Delete`, `Open`, …) defined *many* times across the amethyst tree under per-feature keys **and** once more in commons under generic keys (`action_cancel`, `action_save`, …). This is **required architecture, not an error:**

  • The two trees are **different resource systems**: amethyst uses Android `R.string`; commons uses Compose-Multiplatform `Res.string` (`com.vitorpamplona.amethyst.commons.resources.Res`).
  • **`commons` cannot depend on `amethyst`** (amethyst depends on commons — the reverse would be circular). So a composable extracted *into* commons physically cannot reference `R.string.cancel`; it needs its own string, hence the generic `action_*` keys. That is the only way an extracted shared composable can render "Cancel."
  • The scattered amethyst per-feature duplicates (`nip46_signer_cancel`, `nest_create_cancel`, …) are **pre-existing tech debt**; the co
Read more
Ships withamethyst

Nostr client for Android

Get the whole plugin

Other skills on amethyst.