When to use this
- New UI text is being added to a codebase that supports (or plans to support) multiple locales.
- A translated locale renders broken layout or garbled text, and needs diagnosing.
- Not needed for a codebase with no i18n requirement at all and no plan for one.
The skill file
Copy this verbatim. It's written in the SKILL.md format (YAML frontmatter + markdown instructions) that Claude Code, and increasingly other agent tools, read directly.
---
name: i18n-checker
description: Check new UI code for hardcoded user-facing strings that bypass the i18n system, string concatenation that breaks in languages with different word order, and locale-unaware date/number/currency formatting. Use for codebases that support or plan to support multiple locales.
---
# Internationalization Checker
## Hardcoded strings
- Every user-facing string routed through the project's actual i18n
system (translation function/component), not a raw string literal in
markup or a template.
- Includes strings easy to miss: `alt` text, `aria-label`s, placeholder
text, error messages, toast/notification text, and pluralization
("1 item" vs "2 items" -- must use the i18n system's plural handling,
not string concatenation with a count).
## Concatenation breaks translation
- Building a sentence by concatenating translated fragments around a
variable (`t('Hello') + ', ' + name + t('!')`) breaks in languages with
different word order or grammar. Should use a single translation string
with an interpolation placeholder (`t('Hello, {name}!', { name })`)
instead.
## Locale-aware formatting
- Dates, times, numbers, and currency formatted via the platform's
locale-aware formatting (`Intl.*` APIs or the project's wrapper), not
hand-built strings that assume one locale's separator/order conventions.
- Sort order and string comparison using locale-aware comparison where
user-facing (`localeCompare`), not a naive byte/codepoint sort, if the
content can be non-ASCII.
## Layout resilience
- Does the layout tolerate text length roughly 30-50% longer than the
source language (common for translations) without breaking (truncation,
overflow, or awkward wrapping)?
## Output
Findings with the specific string/format call, what's wrong, and the
project's correct i18n API to use instead.
Installing it elsewhere
The frontmatter/body split above is Claude Code's convention. Here's how to carry the same instructions into other tools:
Save the file below verbatim (frontmatter included) at that path, project-local or in ~/.claude/skills/ for a user-level skill. Claude Code loads the name/description pair to decide when to pull it in, or you invoke it directly as /i18n-checker.
Convert the YAML frontmatter to Cursor's rule format (description, globs, alwaysApply: false) and keep the markdown body as the rule content. Cursor surfaces it by description match, same idea as Claude Code's auto-load.
Codex CLI (and increasingly other agentic CLIs) read AGENTS.md at the repo root as always-on instructions. Paste the markdown body under a heading like ## {title}; for GitHub Copilot's coding agent, the equivalent file is .github/copilot-instructions.md.
Append the markdown body to .windsurfrules at the repo root. Windsurf treats the whole file as always-on context, so keep only the instructions you want applied on every request.
- Missing strings in attributes (alt, aria-label, placeholder, title) that aren't in the main visible text flow.
- Approving string concatenation around a translated fragment because it "reads fine" in the source language.
- Hand-formatting a date/number instead of using the locale-aware API already available in the project.