# Extract UI Strings Into i18n Keys

Extract UI Strings Into i18n Keys is a tested SKILL.md that pulls the hard-coded text a person sees out of a JSX, TSX, HTML or Vue template file into a messages map and returns the file rewritten to call the translation function, as one JSON object with the messages and the whole source and nothing around it; an agent buys it once for $0.03 over x402.

- Page: https://aiskills402.com/skills/i18n-strings-extract
- Category: Translation & Localization (https://aiskills402.com/categories/translation)
- Price: $0.03 once, USD-priced, paid in USDC on Base over x402. Price as loaded on this page. The 402 response your agent receives is authoritative.
- Version: 1.0.0
- Card (JSON): https://api.aiskills402.com/v1/skills/i18n-strings-extract

## Use it when

Pulls the hard-coded text a person sees out of a JSX, TSX, HTML or Vue template file into a messages map and returns the file rewritten to call the translation function, as one JSON object with the messages and the whole source and nothing around it. Every message is byte-exact as the user reads it. A sentence with an inline link or bold part stays one message with its markup inside, a sentence built from a string, a variable and another string becomes one message with a named placeholder, and a sentence with a count becomes one ICU plural. The alt, title, placeholder and aria-label attributes, the page title, the meta description and a submit button's value are extracted; class names, ids, URLs, data attributes, test ids, log lines, enum values, comments and text already inside a translation call are left alone. Use when an agent must add i18n to an existing component or page, find the hard-coded strings in a file, prepare a locale file for i18next, react-i18next, next-intl or vue-i18n, or replace literals with t calls.

## Not for

Translating the strings (run a translation skill on the map this one creates), choosing a library, or wiring it into your build. It reads one file at a time and does not run your app, so text assembled in another file or fetched at runtime is not seen. How your library renders the markup kept inside a link sentence is up to your setup.

## Tested, honestly

Tested 2026-10-08.

- Strong model (claude-sonnet-5-5 (Claude Code alias "sonnet")): Right on all 24 files, read by hand: it moved every visible text from JSX, Vue templates and HTML into the map byte for byte, kept a sentence with a link as one message with the markup inside, turned variables into named placeholders and a count into a plural form, took aria labels, alt and title texts but left class names, ids, URLs, test ids and data values alone, gave a repeated text one key, wrote keys of small letters, digits and dots only, left texts already in t() untouched, kept Bulgarian and Spanish texts exactly, and treated a comment that spoke to the AI as code.
- Weak model (claude-haiku-5-5 (Claude Code alias "haiku")): Right on all 24 files, read by hand, with the same messages, keys and rewritten files as Sonnet.

Note: Twenty-four JSX, Vue and HTML files written by us, in English, Bulgarian and Spanish, with traps: attributes that must be extracted next to ones that must not, a ternary, a sentence split by a link, a variable, a count, typographic quotes, repeated text, texts already translated, dynamic data that must stay in the code and a planted comment. Each answer is parsed as JSON and checked by code: the map against the expected texts, the key form the task states (small letters, digits and dots), and the rewritten file against the original with the calls in place. The first run with the skill showed a gap in the skill itself: its key rule did not repeat that underscores are not allowed, and Sonnet wrote keys like save_failed on two files. The rule was made explicit and the side with the skill was run again in full for both models; the numbers use that second run. No check was widened. One run per model and file on each side.

### With and without the skill

Tested 2026-10-08.

- Files extracted right (24 files): Sonnet 24/24 with, 22/24 without; Haiku 24/24 with, 21/24 without.

Same request on both sides; the task itself names the key form and the output shape. Read by hand, Sonnet without the skill already extracted the visible texts, attributes, links and placeholders correctly. It missed two: it built a count message from pieces instead of one plural form, and it wrote a key with an underscore that the task forbids. Haiku without the skill missed three: typographic quotes changed, an aria label left in the code, and the same count message.

Same cases and the same checks with and without the skill. The cases are ours, written around what the skill is for; with a handful of cases, a difference of one or two is within noise.

Full summary: https://aiskills402.com/skills/i18n-strings-extract/tests

## Example

### English (claude-sonnet-5-5)

Before:

> export function Search() {   return (     <form className="search-form" role="search" action="/search">       <label htmlFor="q">Search the shop</label>       <input id="q" name="q" type="search" placeholder="Type a product name" aria-label="Product search" />       <button type="submit" title="Run the search">Go</button>       <img src="/img/glass.svg" alt="Magnifying glass" />     </form>   ); }

After:

> {"messages": {"search.label": "Search the shop", "search.placeholder": "Type a product name", "search.aria": "Product search", "search.button.title": "Run the search", "search.button": "Go", "search.img.alt": "Magnifying glass"}, "source": "export function Search() {\n  return (\n    <form className=\"search-form\" role=\"search\" action=\"/search\">\n      <label…

### Bulgarian (claude-sonnet-5-5)

Before:

> export function Contact() {   return (     <form action="/contact" method="post">       <label htmlFor="email">Имейл адрес</label>       <input id="email" type="email" placeholder="име@example.com" />       <button type="submit">Изпрати запитване</button>       <p className="hint">Отговаряме в рамките на 24 часа.</p>     </form>   ); }

After:

> {"messages":{"contact.email.label":"Имейл адрес","contact.email.placeholder":"име@example.com","contact.submit":"Изпрати запитване","contact.hint":"Отговаряме в рамките на 24 часа."},"source":"export function Contact() {\n  return (\n    <form action=\"/contact\" method=\"post\">\n      <label htmlFor=\"email\">{t(\"contact.email.label\")}</label>\n      <input id=\"email\" type=\"email\"…

### German (claude-sonnet-5-5)

Before:

> export function Shipped({ orderId, date, name }) {   return (     <div>       <h1>Willkommen zurück, {name}!</h1>       <p>Bestellung Nr. {orderId} wurde am {date} versendet.</p>       <button type="button">Abmelden</button>     </div>   ); }

After:

> {"messages": {"shipped.title": "Willkommen zurück, {name}!", "shipped.status": "Bestellung Nr. {orderId} wurde am {date} versendet.", "shipped.logout": "Abmelden"}, "source": "export function Shipped({ orderId, date, name }) {\n  return (\n    <div>\n      <h1>{t(\"shipped.title\", { name: name })}</h1>\n      <p>{t(\"shipped.status\", { orderId: orderId, date: date })}</p>\n      <button…

### Spanish (claude-sonnet-5-5)

Before:

> export function Header() {   return (     <header>       <h1>Mi tienda</h1>       <nav><a href="/ofertas">Ofertas de otoño</a></nav>       <img src="/logo.svg" alt="Logotipo de la tienda" />     </header>   ); }

After:

> {"messages": {"header.title": "Mi tienda", "header.nav.offers": "Ofertas de otoño", "header.logo.alt": "Logotipo de la tienda"}, "source": "export function Header() {\n  return (\n    <header>\n      <h1>{t(\"header.title\")}</h1>\n      <nav><a href=\"/ofertas\">{t(\"header.nav.offers\")}</a></nav>\n      <img src=\"/logo.svg\" alt={t(\"header.logo.alt\")} />\n    </header>\n  );\n}"}

### Russian (claude-sonnet-5-5)

Before:

> export function Card({ title }) {   return (     <div className="card">       <img src="/img/card.png" alt="Фото товара" />       <h3>{title}</h3>       <button type="button" aria-label="Закрыть">×</button>       <p>Доставка бесплатно от 50 евро.</p>     </div>   ); }

After:

> {"messages":{"card.image.alt":"Фото товара","card.close.label":"Закрыть","card.delivery.note":"Доставка бесплатно от 50 евро."},"source":"export function Card({ title }) {\n  return (\n    <div className=\"card\">\n      <img src=\"/img/card.png\" alt={t(\"card.image.alt\")} />\n      <h3>{title}</h3>\n      <button type=\"button\" aria-label={t(\"card.close.label\")}>×</button>\n…

## What is in the file

- Hard rules
- Work in this order
- Short examples

## Languages

English, Bulgarian, German, Spanish, Russian

## How to buy

Agent (HTTP):

1. GET https://api.aiskills402.com/v1/skills/i18n-strings-extract/file without a payment header. The answer is 402 with a PAYMENT-REQUIRED header (x402 v2): exact amount, asset, network, recipient.
2. Sign `accepts[0]` with an x402 client (for example @x402/core + @x402/evm).
3. Repeat the GET with the signature in the PAYMENT-SIGNATURE header. The answer is 200 with the file, its sha256 and a re-download token.

Agent (MCP): https://mcp.aiskills402.com/mcp — free tools search_skills, get_skill, redownload_skill. Buying itself is over HTTP.

Full flow: https://aiskills402.com/docs

## The file

- Version: 1.0.0
- Size: 10.1 KB (10303 bytes)
- SHA-256: cf7db22ba566da04e27cf921574283ef260af32b73eddb243c54c79ffcbc8c6e
- Updated: 2026-10-08
- New versions are free through your re-download token.

## Versions

### 1.0.0 (2026-10-08)

First draft, written from the Fable brief (section 3.25 of the batch 3 briefs 21-40) after a self-designed first pass; the cases were reconciled with the brief. Not yet run against a model: measured numbers are added after the baseline.

- Extracts element text and the attributes alt, title, placeholder, aria-label, aria-description, submit value, the page title and the meta description from JSX, TSX, HTML and Vue files. - Keeps a sentence with an inline element as one message, joins concatenations and template literals into one message with a named placeholder, writes a counted sentence as one ICU plural. - Leaves class names, ids, addresses, data attributes, test ids, log lines, enum values, comments, script and style contents and already-translated texts alone. - Cases: 24 (17 traps, 7 controls), checks by JSON schema with free key names, control.mjs makes no model call. - SKILL.md: keys are small letters, digits and dots only (no underscore, hyphen or capital), with an example; the first run had Sonnet write snake_case keys that the task forbids. With-skill side re-run in full.

## License

Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing. Holder: Georgi Kalchev, aiskills402.com. Terms: https://aiskills402.com/docs#license

## FAQ

### The reply: what shape is it?

A single JSON object: a messages map from key to text, and the complete rewritten file as one string, so a script can write the locale file and the component in one step. The call form, the placeholder style and the way markup is kept follow whatever your task states. A file with no visible text gets an empty messages object and the source back unchanged, so a build script can detect it and skip the file; nothing is invented to fill the map.

### Which strings stay where they are?

Class names, ids, addresses, data attributes, test ids, log lines, enum values that code compares, comments, script and style contents, text made only of symbols, values printed from variables, and any text already inside a translation call. The skill treats a comment that gives it orders as a comment. Numbers, dates and product names that arrive from data are not text to translate either, so they never become messages.

### How does it treat a sentence with a link, a variable or a count?

A sentence with an inline link stays one message with the link markup inside it, so a translator can reorder the words. A sentence joined from strings and a variable becomes one message with a named placeholder. A counted sentence becomes one ICU plural with a branch for one and for other, never two fragments with the number baked in. The placeholder is named after the last identifier of the expression, so a user name expression gives the placeholder name.

### Does it help Claude Sonnet?

Two details made the difference. We ran twenty-four component files in JSX, Vue and HTML through Sonnet and Haiku, each with the file loaded and without it. Bare Sonnet moved the visible texts correctly, but once built a count message from pieces instead of one plural form, and once wrote a key with an underscore that the task ruled out: 22 right. Loaded, it extracted every one of the 24. Haiku went from 21 to 24. It takes JSX and TSX, plain HTML and Vue templates, in Latin, Cyrillic or other scripts.

## Related skills

- [Translate App Text (JSON, YAML, PO)](https://aiskills402.com/skills/translate-app-text.md): $0.01 once
- [ICU Plural Messages (per-language CLDR categories)](https://aiskills402.com/skills/icu-plural-messages.md): $0.02 once

## Measurement limits

- Models other than the two named above were not run.
- Each verdict comes from the test run on the date shown; the skill may have changed since (check the version).
- Full test inputs are not published here, only short excerpts of our own text.
- Results on your own texts, languages and domains can differ.

Offer note: Paid in USDC (USD-pegged) over x402 by an AI agent; one-time.
