Frontend Development
OpenHands/OpenHands
This skill should be used when the user asks to "add UI copy", "add a translation", "optimize the frontend bundle", "change onboarding", "change conversation UI", "add a query key", "change MSW…
Audit localization / i18n coverage across the compose-resources surface (10 modules x 9 locales).
$ npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install garfiec/Librechat-Mobile audit-i18n --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/garfiec/Librechat-Mobile.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/audit-i18n .claude/skills/audit-i18n && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "audit-i18n" agent skill from https://github.com/garfiec/Librechat-Mobile/tree/develop/.claude/skills/audit-i18n into .claude/skills/audit-i18n/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "audit-i18n", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/garfiec/Librechat-Mobile/tree/develop/.claude/skills/audit-i18nType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install garfiec/Librechat-Mobile audit-i18n --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/garfiec/Librechat-Mobile.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/audit-i18n .agents/skills/audit-i18n && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "audit-i18n" agent skill from https://github.com/garfiec/Librechat-Mobile/tree/develop/.claude/skills/audit-i18n into .agents/skills/audit-i18n/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "audit-i18n", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install garfiec/Librechat-Mobile audit-i18n --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/garfiec/Librechat-Mobile.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/audit-i18n .cursor/skills/audit-i18n && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "audit-i18n" agent skill from https://github.com/garfiec/Librechat-Mobile/tree/develop/.claude/skills/audit-i18n into .cursor/skills/audit-i18n/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "audit-i18n", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/garfiec/Librechat-Mobile.git --path .claude/skills/audit-i18n--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install garfiec/Librechat-Mobile audit-i18n --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/garfiec/Librechat-Mobile.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/audit-i18n .gemini/skills/audit-i18n && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "audit-i18n" agent skill from https://github.com/garfiec/Librechat-Mobile/tree/develop/.claude/skills/audit-i18n into .gemini/skills/audit-i18n/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "audit-i18n", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install garfiec/Librechat-Mobile audit-i18nInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/garfiec/Librechat-Mobile.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/audit-i18n .github/skills/audit-i18n && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "audit-i18n" agent skill from https://github.com/garfiec/Librechat-Mobile/tree/develop/.claude/skills/audit-i18n into .github/skills/audit-i18n/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "audit-i18n", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install garfiec/Librechat-Mobile audit-i18n --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/garfiec/Librechat-Mobile.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/audit-i18n .opencode/skills/audit-i18n && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "audit-i18n" agent skill from https://github.com/garfiec/Librechat-Mobile/tree/develop/.claude/skills/audit-i18n into .opencode/skills/audit-i18n/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "audit-i18n", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
audit-i18nAudit localization / i18n coverage across the compose-resources surface (10 modules x 9 locales).
Audit I18n is an agent skill from garfiec/Librechat-Mobile. Audit localization / i18n coverage across the compose-resources surface (10 modules x 9 locales). Finds strings that exist in English but not in some or all languages, keys left as untranslated English stubs inside a translated file, stale keys the base dropped, and English literals that never reached a strings.xml at all. Use when asking "which features shipped English-only?", after landing a feature that added user-facing strings, before a release that claims multi-language support, or when a translator asks…
Its SKILL.md is about 7.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 1 other file (for example `REPORT-2026-09-30.md`).
It sits in Frontend & Design, covering Internationalization and Translation. It works with OpenAI. The repository describes itself as: Native Android & iOS client for LibreChat, built with Kotlin Multiplatform and Compose Multiplatform. The licence is MIT.
3 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit bf2a609. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
BashReadGlobGrepWriteWorkflowAskUserQuestionFrom allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
python3gitFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use git, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Audit I18n loads about 7.2k tokens when it runs. Until then it costs about 152 tokens; SKILL.md has 3,536 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
allowed-tools: Bash, Read, Glob, Grep, Write, Workflow, AskUserQuestionAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from garfiec/Librechat-Mobile at commit bf2a609, republished under its MIT licence (© garfiec). 3,536 words, ~7,163 tokens.
.claude/skills/audit-i18n/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.Audit the localization surface and report what is missing, in a form the user can act on and re-run later.
You are the lead. You do Phase 0 yourself with direct Bash, then hand the analysis to the
audit-i18n workflow, then independently re-assert what it claims. You do not attribute keys to
features, triage candidates, or write the report yourself — the workflow fans that out.
The deterministic checker is the source of truth. scripts/i18n-coverage.py is the only
thing that produces findings. The workflow explains findings that already exist; it never
discovers them.
The skill is audit-only. It ends at findings + a recommended fix order. It never writes a
translation, never edits a strings.xml, never adds a Gradle task or CI gate, never commits,
never opens a PR.
| Layer | Produces | Property |
|---|---|---|
scripts/i18n-coverage.py | What the findings are | Exact, byte-deterministic, diffable across months |
audit-i18n workflow | Why / when / whether it matters | Judgment, parallelised, not reproducible |
Keep the seam clean. The instant an agent re-derives findings by grepping, the audit stops being comparable to the last one and the determinism you paid for is gone. Every number in the final report traces to the JSON; the workflow's contribution is feature names, dates, verdicts and priority.
Never grep for missing strings, diff strings.xml files with shell tools, or count keys
yourself. Every number in your report must come out of the script.
The reason is not politeness, it is that a hand-rolled pass is not comparable to the next one.
The script is byte-deterministic — identical input produces identical stdout on every run and
every machine (no timestamps, no absolute paths, no set-iteration order). That is what makes a
report diffable against the report from six weeks ago, and what lets the user prove a fix
actually shrank the debt. An ad-hoc grep produces a number nobody can reproduce, and it
systematically misses the things the script handles carefully: both directions of parity drift
(a module with extras that cancel against its missing keys looks fine to a count comparison),
values-night being a theme qualifier and not a locale, and the Android res/ surface being a
separate thing that must not be folded into the parity math.
You may read source files to explain a finding the script already reported — naming the feature a key cluster belongs to, or triaging a heuristic candidate. You may not use reading to discover findings.
Run this first, always:
python3 scripts/i18n-coverage.py --summary; echo "exit=$?"Confirm three things before trusting anything downstream:
The header line reads allowlist: config/l10n/i18n-allowlist.txt. If it reads
allowlist: (none) you are running unsuppressed — see the blind spot below, this failure is
silent.
The module table lists 10 modules and loc is 9 on every row. Fewer means discovery
broke or a locale was dropped wholesale.
The exit code is below 16. 16 and above is a hard failure — the script could not find or
parse the surface. It shares no bits with the finding mask (1|2|4|8) so that "the check
broke" can never be misread as "the code is clean". If you see it, read the failure line, fix
the invocation, and re-run. Do not report anything from a hard-failed run.
An advisory (bit 8) is not a failure and must not be treated as one. It means the tool
measured something it wants a human to see — a locale directory missing wholesale, a new
<plurals>, a module added since EXPECTED_MODULES was last updated. Those are findings.
Carry them into the report; do not abort on them.
Phase 0 is yours and is not delegated: if the tool is broken, fanning out ten agents over its output just multiplies the wrong answer.
Resolve these yourself first, with Bash. The date matters: workflow scripts have no clock
(Date.now() throws inside one by design, so runs stay resumable), so the report date must be
computed here and passed in.
REPO="$(git rev-parse --show-toplevel)"
DATE="$(date -u +%F)"
SKILL_DIR="$REPO/.claude/skills/audit-i18n"
# Never overwrite an existing dated report. Two audits on the same UTC day collide on one
# filename, and the earlier report is the baseline the later one is meant to be diffed
# against — it is uncommitted at that moment, and artifacts/ is gitignored, so an overwrite
# is unrecoverable. Pick the first free suffix instead.
REPORT_PATH="$SKILL_DIR/REPORT-$DATE.md"
n=2
while [ -e "$REPORT_PATH" ]; do
REPORT_PATH="$SKILL_DIR/REPORT-$DATE.$n.md"
n=$((n + 1))
done
echo "$REPO" "$DATE" "$REPORT_PATH"Pass REPORT_PATH through verbatim — the workflow derives its artifact filenames from it, so
the raw JSON behind an earlier report is preserved too. Then:
Workflow({ name: "audit-i18n", args: {
repoRoot: "<REPO>",
skillDir: "<REPO>/.claude/skills/audit-i18n",
artifactsDir: "<REPO>/.claude/skills/audit-i18n/artifacts",
reportPath: "<REPORT_PATH from the snippet above — do not re-derive it>",
reportDate: "<DATE>",
scope: "full", // or "exact-only" to skip heuristic triage
attributeStale: true,
} })Pass scope: "exact-only" when the user wants a fast parity-and-stubs answer, or when the
heuristic candidates were triaged recently and have not moved. It drops the triage agents and
leaves [H] reported as raw counts.
What it does: measures (1 agent, writes the JSON), then fans out one attribution agent per module with drift plus a stub adjudicator plus up to three heuristic triagers — all concurrently — then synthesizes one report, then reconciles that report against the raw JSON with two independent agents that did not write it. Typically 10–13 agents.
It returns { reportPath, jsonPath, totals, featureClusters, modulesAttributed, modulesNotAttributed, unreconciledModules, stubVerdictCounts, heuristicSampled, advisories, reconcileVerdicts, confirmedDiscrepanciesFixed, reconcileFailedLenses, unconfirmedDiscrepancies, clean, reportMarkdown, summary }.
No agent writes the report file — the harness refuses report-file writes from subagents. The
workflow returns the reconciled report as reportMarkdown; you write it verbatim to
reportPath (re-check the path is still free first) before starting Phase 2. If reportMarkdown
is missing, the synthesize phase returned nothing: say so and do not hand-write a substitute.
If it returns { halted: true }, stop. That means the checker hard-failed (exit ≥ 16) — it
did not run at all. Report the reason to the user and fix the tool before auditing anything. A
report generated over a broken measurement reads as a clean bill of health, which is the worst
possible output. Advisories alone never halt the run; they are carried into the report.
Do not relay the workflow's self-report unchecked. It already reconciles itself, but it graded its own homework. Run these yourself:
Use the jsonPath the workflow returned — it is keyed to this run's report, not a fixed name.
# 1. The report's headline total must equal the JSON's — the single most load-bearing number.
python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print('missing',d['parity']['missing_total'],'stale',d['parity']['stale_total'],'struct',d['parity']['structural_total'],'stubs',d['stubs']['counts']['errors'],'heuristic',d['hardcoded']['total'])" <jsonPath>
grep -nE "^\| \*\*total\*\*|debt|missing" <reportPath> | head
# 2. The audit is reproducible: re-running the checker reproduces the same JSON.
python3 scripts/i18n-coverage.py --format json | shasum -a 256
shasum -a 256 <jsonPath>
# 3. Spot-check one feature attribution against git yourself. --reverse|head -1 gives the
# commit that INTRODUCED the key; `-1` alone gives the last commit that touched it, which
# is a different (and usually wrong) answer. The name="…" anchor stops a prefix-nested
# sibling key from matching.
git log --format='%h %ad %s' --date=short --reverse -S'name="<a key named in the report>"' \
-- <module>/src/commonMain/composeResources/values/strings.xml | head -1Then check the returned fields. clean: true is the only state you may summarize without
caveats. When it is false, one of these is non-empty, and each means something specific:
| Field | If non-empty |
|---|---|
modulesNotAttributed | an attribution cap bit — those modules' findings are in the report but unexplained |
unreconciledModules | a module's feature clusters do not account for all its keys |
reconcileFailedLenses | a reconcile lens returned FAIL — the report is not trustworthy as written |
unconfirmedDiscrepancies | a lens suspected a fabricated or dropped finding but did not mechanically reproduce it. Not auto-fixed. Verify each by hand against the JSON before you summarize |
advisories | the checker's own findings about the surface — these belong in your summary too |
Say so to the user in your summary. Those are the parts of the surface the audit did not fully cover, and they must not be presented as if they were.
If confirmedDiscrepanciesFixed is non-zero, mention it: the report needed correcting against
its own source data, which is worth knowing about the run.
All paths are repo-relative; run from the repo root.
# Full report, human-readable. The default.
python3 scripts/i18n-coverage.py
# Totals only — good for a before/after comparison or a quick status line.
python3 scripts/i18n-coverage.py --summary
# One detector at a time. Useful because the exit code then isolates that detector.
python3 scripts/i18n-coverage.py --detector parity
python3 scripts/i18n-coverage.py --detector stubs
python3 scripts/i18n-coverage.py --detector hardcoded
python3 scripts/i18n-coverage.py --detector all # same as omitting it
# Machine-readable. Use this when you need to group, sort, or cross-tabulate findings.
python3 scripts/i18n-coverage.py --json # shorthand for --format json
python3 scripts/i18n-coverage.py --format json
python3 scripts/i18n-coverage.py --format text # the default
# Show the raw, unsuppressed finding set — what the allowlist is currently hiding.
python3 scripts/i18n-coverage.py --no-allowlist
# Point at a different allowlist (must resolve INSIDE the repo).
python3 scripts/i18n-coverage.py --allowlist config/l10n/i18n-allowlist.txt
# Two opt-in low-precision stub tiers, both INFO, both off by default.
python3 scripts/i18n-coverage.py --include-latin # Latin-locale values identical to base
python3 scripts/i18n-coverage.py --near-identical # case/punctuation-only differences
# Override repo-root detection (normally found by walking up for settings.gradle.kts).
python3 scripts/i18n-coverage.py --repo-root .A useful pattern for grouping missing keys by feature — the highest-value analysis step — is to pull the distinct key lists out of the JSON rather than the text report:
python3 scripts/i18n-coverage.py --json > /tmp/i18n.json
python3 -c "
import json
d = json.load(open('/tmp/i18n.json'))
for m in d['parity']['per_module']:
if m['missing_total']:
print('===', m['module'], 'base', m['base_keys'], 'rows', m['missing_total'])
for k in m['distinct_missing_keys']: print(' ', k)
"A bitmask, so a caller can tell exact findings from heuristic ones:
| Code | Meaning | Trust |
|---|---|---|
0 | clean | — |
1 | parity findings | exact |
2 | stub findings, ERROR tier | exact |
4 | hardcoded candidates | heuristic |
8 | advisories (stale allowlist directives, module/locale-set drift, parser disagreement with StringResourceParityTest's regex) | exact |
16 | hard failure — layout unrecognized, 0 modules, a module with 0 base keys, an unreadable/non-UTF-8/unparseable resource file, bad CLI argument, a missing explicitly-passed --allowlist, unexpected exception | the run did not happen |
Codes combine: 7 = parity + stubs + hardcoded, which is the current state of develop.
Test hard failure with exit & ~15 (equivalently exit >= 16), never with a narrower mask.
The hard-failure code shares no bits with the finding mask, and that is the whole point — the
conventional 70 (0b1000110) sets both the stub bit and the hardcoded bit, so exit & 3 would
report a crashed run as "2 — exact stub findings". Anything at or above 16 means the check did
not run; anything below is a finding set.
A caller wanting proven defects only would check exit & 3 after ruling out hard failure.
Do not treat that as an invitation to add a gate — that is explicitly out of scope for this
skill.
The single most important thing you do is keep these three straight in the report. Two are exact set operations. One is a regex heuristic over a language with no type-level marking of user-facing strings. Blurring them destroys the report's credibility.
Set difference between each module's base values/strings.xml and each of its values-<loc>/
files, computed in both directions:
Also reported: uniform, meaning every locale in the module shares one identical missing set and
one identical stale set. When uniform is yes, the per-module distinct key list is lossless
and you can quote it directly. When it is no, per-locale sets diverge and you must go to the
JSON parity.findings rows before making per-locale claims.
Note that missing_total is a row count summed over locales, not a key count. 504 missing
rows in a 9-locale module is 56 distinct keys. State both in the report; the user sizes work in
keys and sizes debt in rows.
Parity is never allowlist-suppressible. A missing or stale key is not a judgment call.
A key that is present in a non-Latin-script locale but whose value is byte-identical to the English base and pure ASCII was never actually translated. The file passes parity and still ships English.
Two classes are exempted mechanically, with no allowlist entry needed: shared literals (a
single token no locale anywhere translated — Bearer, OAuth, JSON, MCP, SSE, Markdown,
mermaid, shadcn/ui) and letterless values (arrows, em dashes, pure format
strings).
What survives is tiered by cross-locale corroboration:
Off, Never) is still exempted as a
shared literal — indistinguishable from OAuth mechanically — so only the contiguous-run
corroborator can surface it.--include-latin, --near-identical) — off by default and near-100% /
1-in-3 precision respectively. Only surface these if the user asks for exhaustive output, and
label them INFO.Also reported: contiguous runs of ≥3 adjacent untranslated keys in one file. These are the
cheapest fixes in the whole report — one localized block of a file, usually a whole form that a
translator skipped in one go. Call them out by file:first_line-last_line.
Sink-anchored regex matching for English literals in .kt that never reached a strings.xml —
Text("…"), contentDescription = "…", ?: "…" error fallbacks, when branches returning
display strings, and so on, bucketed into rule families.
Language rules for this section, non-negotiable:
Triage priority, best-value first:
content_desc and text_call — unambiguously on screen, and content_desc is also an
accessibility defect. Smallest families, highest hit rate.ui_param — labels/descriptions passed to UI builders. Large, but usually dominated by a few
registry-shaped files where one file is one fix.when_branch and continuation — display-string mappings and multi-line concatenations.state_error and elvis_error — error fallbacks. Mixed: real user-facing error copy sits
next to developer diagnostics that merely leak.enum_label and sink_call — mostly wire tokens and non-UI helpers. Verify before touching.Sort candidates by module and by file, not by rule, when recommending work: a single file with 100 rows is one PR, and 100 rows spread over 40 files is not.
config/l10n/i18n-allowlist.txt. Suppresses individual [S] and [H] findings. It does not
touch [P].
Format is TAB-separated, exact string equality — no regex, no globs, no prefix matching. Read the
header comment in the file for the five directive forms (literal, key, pair, site,
file). It is shrink-only: a directive that suppresses nothing is surfaced as a stale
advisory (exit bit 8) and must be deleted, so debt can only go down.
Every entry needs a written justification comment above it. "The check was noisy" is not one.
Legitimate reasons to add an entry:
Basic as an HTTP auth scheme).Streamable HTTP as the MCP transport's name).lowercase-kebab-case).[H] site is genuinely not user-facing — a wire constant, an internal exception default.Illegitimate — this is hiding a real gap:
ar, ja and zh rendered it as prose, ko
leaving it English is a miss, not a convention. The script's REVIEW/ERROR tiering exists
precisely to make this visible; do not suppress across it.file directive used to quiet a noisy module. Prefer site.When the audit surfaces a suppression that looks wrong, report it as a finding against the allowlist. Do not edit the allowlist inside this skill — proposing an entry is in scope, writing it is a separate authorized change.
The workflow composes this, not you — its synthesize phase returns the markdown, its
reconcile phase checks it against the raw JSON, and you save the returned reportMarkdown to
.claude/skills/audit-i18n/REPORT-<YYYY-MM-DD>.md. This section documents the contract that report must satisfy, so you can tell whether
what came back is right; the shell snippets below are what the workflow's attribution agents run.
Never overwrite a prior dated report — the whole point of determinism is that two dated reports
diff cleanly. The raw JSON lands in artifacts/ (gitignored, regenerable); the dated report is
the durable artifact.
Required sections:
Headline number — total translation debt as a single figure (missing rows + stale rows +
ERROR stub rows), so the user can size the work in one glance. Exclude [H] candidates from
it and say that you did.
Per-module × per-locale parity table — real numbers from the script, base key count included so percentages are checkable.
Missing keys grouped by feature. This is the highest-value section. Key names are
prefix-clustered by design (project_*, context_usage_*, media_*), so group them and
name the feature. Then date a representative key per cluster:
git log -S'"context_usage_label"' --format='%h|%ad|%s' --date=short --reverse \
-- feature/chat/src/commonMain/composeResources/values/strings.xml | head -1and establish when localization itself landed:
git log --oneline --reverse --diff-filter=A -- '*/values-de/strings.xml'Cite the commit and PR for each cluster. A cluster whose keys postdate the i18n introduction is a feature that shipped English-only — that is the user's actual question, and counts alone do not answer it.
Stub findings — ERROR and REVIEW separated, per-locale counts, contiguous runs called out as the cheap wins.
Hardcoded candidates — labeled heuristic, with the triage ordering above.
Recommended fix order — biggest coverage win per unit of effort first, with rough entry counts per step so each step is schedulable. Deletions of stale keys and single-file registry extractions go early; they are near-zero-risk and shrink the number visibly.
State these in the report. A tool whose limits are undocumented gets over-trusted.
%2$s where base has
%1$s, or drops a %s entirely, passes every detector. This is a deliberate scope decision,
not an oversight, and it is the most likely source of a runtime crash in translated copy.--allowlist that does not
exist is a hard failure, but if the DEFAULT config/l10n/i18n-allowlist.txt is simply
missing, the run continues unsuppressed with an ALLOWLIST_ABSENT advisory. That is a real
state to notice: totals from such a run are not comparable with a run that had it. Always
verify the header line reads the path, not (none).--summary hides the INFO tiers. --summary --include-latin prints the same bytes as
--summary alone. Use the full text or JSON output when you want those counts.[H] scans .kt only, and not inside raw strings. English inside """…""" HTML/JS
templates is not scanned (there is no sink pattern inside an inline web document), and
android:label in AndroidManifest.xml is out of scope.[H] recall limits, per the script's own notes: concatenation fragments beyond a 4-line
window are reported once at the head line; a val message = "…" bound far from its sink is
missed; a spaceless all-lowercase word is dropped by the prose gate (admitting them measured
2 true positives against 48 false, so that recall is given up on purpose).res/ surface is reported but not scored. app/src/main/res/values/strings.xml
holds the platform strings and has no locale directories. Folding it into parity math would
fabricate phantom "entire locale missing" failures, so it is listed separately. Its gap is
real; it is just not part of the compose-resources numbers..strings / .stringsdict / .xcstrings exist, so there is nothing to compare
against on that side. If any are ever added, this tool will not see them.There is an overlapping JUnit test — StringResourceParityTest plus
config/l10n/strings-parity-baseline.txt (~1269 frozen entries, shrink-only) — on the unmerged
local branch chore/review-loop-protocol (commit 7f4f66e8). Read it with
git show chore/review-loop-protocol:<path>.
It is not on develop, so nothing currently enforces parity in CI. Do not assume its baseline
reflects the present state; the checker deliberately does not read or regenerate it.
Where the two differ:
shared + feature/*, so core/ui drift is invisible to it. This
checker discovers modules by sorted glob and includes core/ui.continues past a missing locale file, so a wholly absent locale is silently OK.
This checker scores an absent locale file as 100% missing and reports it as a structural defect.8) if its parser disagrees with the test's regex, so
the two can be reconciled rather than silently diverging.Treat the checker as the superset. If the test later lands on develop, that is a CI gate
decision for the user, not something this skill performs.
values-night trap, and the res/ separation.[H] into the debt total. Heuristic candidates and exact defects are different
kinds of claim. Keeping one number honest is worth more than a bigger number.missing_total as a key count. It is rows summed over 9 locales. Divide, and
show both.strings.xml, touch Gradle, or add a CI gate. All four are
out of scope. The skill stops at the report.8) — that is a
finding, and refusing to report it is its own kind of silence.modulesNotAttributed or unreconciledModules came back
non-empty, the audit covered less than it appears to. Say which parts, plainly.reportDate. The workflow cannot read a clock; whatever you pass becomes
the filename and the in-report date. Compute it fresh with date -u +%F each run.© garfiec, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in .claude/skills/audit-i18n of garfiec/Librechat-Mobile.
Open the folder on GitHubat commit bf2a609
Audit I18n next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Audit I18n this skillgarfiec/Librechat-Mobile | 111 | — | ~7.2k | Automated safety check: Notes | MIT | |
| Frontend DevelopmentOpenHands/OpenHands | 91k | — | ~324 | Automated safety check: Pass | MIT | |
| Jtackanner/jta | 133 | — | ~2.4k | Automated safety check: Notes | MIT | |
| Locale Tui LocalizationAyuilos/Miffan | 225 | — | ~1.1k | Automated safety check: Pass | AGPL-3.0 | |
| LobeHub Internationalizationlobehub/lobehub | 83k | — | ~885 | Automated safety check: Pass | Custom licence | |
| Chatbox i18n Translatorchatboxai/chatbox | 42k | — | ~508 | Automated safety check: Pass | GPL-3.0 |
OpenHands/OpenHands
This skill should be used when the user asks to "add UI copy", "add a translation", "optimize the frontend bundle", "change onboarding", "change conversation UI", "add a query key", "change MSW…
ckanner/jta
Translate JSON i18n files to multiple languages with AI-powered quality optimization.
Ayuilos/Miffan
A skill your agent uses when users request i18n/localization updates for Android string resources: adding localized keys, translating strings.xml, filling missing translations, or moving hardcoded…
lobehub/lobehub
Rules for adding and editing LobeHub's user-facing strings with react-i18next: flat dot-notation keys, the default locale folder, namespaces and when to run bun run i18n.
chatboxai/chatbox
Translates new or changed i18n keys from a Chatbox Pro diff, staged changes or a commit range, writing the locale JSON files directly with a built-in glossary.
iOfficeAI/AionUi
Standards for keeping all user-facing text translatable: read the i18n config first, use namespaced keys, reuse shared strings and follow the key naming rules.
garfiec/Librechat-Mobile
Add a hand-written Highlights section to a GitHub release whose notes were auto-generated, summarizing the release's PRs in user-facing language above the generated changelog.
garfiec/Librechat-Mobile
Audit open dependabot PRs in this repo. An agent skill from garfiec/Librechat-Mobile.
garfiec/Librechat-Mobile
Update the third-party JavaScript vendored into the app for the artifact, diagram, and math WebViews (KaTeX, mermaid, marked, highlight.js, Tailwind, Babel, React).
garfiec/Librechat-Mobile
Sync the Switchboard client with a newer official LibreChat server version — a stable release, a release candidate, or a PARTIAL sync up to an untagged upstream commit (e.g.
Works with
Categories
Audit localization / i18n coverage across the compose-resources surface (10 modules x 9 locales). Audit I18n is an agent skill from garfiec/Librechat-Mobile. Audit localization / i18n coverage across the compose-resources surface (10 modules x 9 locales).
Audit I18n fits situations like: asking which features shipped English-only?; after landing a feature that added user-facing strings; before a release that claims multi-language support; A translator asks what still needs doing.
Run `npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a claude-code`. Or copy the skill folder (.claude/skills/audit-i18n in garfiec/Librechat-Mobile) into .claude/skills/audit-i18n in your project. Claude Code loads it when a task matches its description.
Run `npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a codex`. Or copy the skill folder (.claude/skills/audit-i18n in garfiec/Librechat-Mobile) into .agents/skills/audit-i18n in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add garfiec/Librechat-Mobile --skill audit-i18n -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/audit-i18n, .gemini/skills/audit-i18n, .github/skills/audit-i18n and .opencode/skills/audit-i18n in your project.
Going by SKILL.md and its folder, Audit I18n needs the command-line tools its instructions call (python3 and git). Our summary lists: Python 3. Its frontmatter pre-approves these tools: Bash, Read, Glob, Grep, Write, Workflow, AskUserQuestion.
SKILL.md contains no URLs. Its commands use git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Audit I18n is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.2k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Audit I18n: Frontend Development (OpenHands/OpenHands, 91k stars), Jta (ckanner/jta, 133 stars), Locale Tui Localization (Ayuilos/Miffan, 225 stars) and LobeHub Internationalization (lobehub/lobehub, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
garfiec (a GitHub user) maintains it in garfiec/Librechat-Mobile, which has 111 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on October 10, 2026.
Source: garfiec/Librechat-Mobile on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.