Frontend & Accessibility

Internationalization Checker

Find hardcoded user-facing strings, string concatenation that breaks in other languages, and formatting that assumes one locale's conventions.

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.

SKILL.md
---
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:

Claude Code
.claude/skills/i18n-checker/SKILL.md

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.

Cursor
.cursor/rules/i18n-checker.mdc

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 / Copilot
AGENTS.md

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.

Windsurf
.windsurfrules

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.

Where this goes wrong
  • 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.