{"slug":"translate-app-text","name":"Translate App Text (JSON, YAML, PO)","version":"1.0.1","updated_at":"2026-10-04T10:20:45.936Z","use_when":"Translates app strings and locale files (JSON, YAML, gettext PO, ICU messages, HTML snippets) into another language without breaking them. Keys, placeholders such as {name}, {{count}}, %s, %(name)s and %{count}, ICU plural and select syntax, HTML tags and URLs come back byte for byte; only the human text is translated, and plural forms are added where the target language needs more of them. Use when asked to translate a JSON or YAML locale file, an en.json, a .po file, i18n strings or UI text, or to localize an app.","not_for":"Marketing copy, articles or documents where style matters more than structure; legal text that needs a certified translator; finding the strings in your code or deciding which ones your app needs. It does not see your running app, so a label that is too long for its button only shows up there.","languages":["en","bg","de","es","ru"],"tags":["translate","i18n","localization","json","placeholders","icu-messageformat","gettext","yaml"],"category":"translation","category_url":"https://aiskills402.com/categories/translation","keywords":[".po file","i18n strings","localize an app","UI text"],"faq":[{"q":"Will it break my placeholders?","a":"In our runs neither model changed a key, a placeholder or an ICU keyword: {name}, {{count}}, %s, %(name)s, %{count}, $t(...) and {count, plural, ...} came back exactly. The weaker Claude Haiku sometimes wrapped the JSON in a code fence, which breaks a file saved as it is, so strip one if you see it."},{"q":"What about languages with more plural forms than English?","a":"The skill adds the categories the target needs. Russian gets one, few, many and other, inside ICU messages, as i18next _few and _many keys, or as Rails few: and many: keys. Both models did this in our Russian cases. PO files follow gettext's own count, which is three forms for Russian."},{"q":"Which formats does it handle?","a":"Nested JSON and i18next resources, Rails-style YAML, gettext PO, ICU MessageFormat strings, and HTML or Markdown snippets. Key order, URLs, code and brand names stay as they are; the Rails root key and the PO Language header switch to the target code."},{"q":"Is the wording as good as a human translator?","a":"For short app strings Claude Sonnet wrote natural Bulgarian, German, Russian and Spanish in our tests. Haiku's wording was weaker, with literal choices a native speaker would change, so let someone who speaks the language look at the screens before a release."}],"examples":[{"lang":"bg","model":"claude-sonnet-5-5","input_excerpt":"{\n  \"nav\": {\n    \"home\": \"Home\",\n    \"pricing\": \"Pricing\",\n    \"signIn\": \"Sign in\"\n  },\n  \"welcome\": \"Welcome back, {name}!\",\n  \"cart\": {\n    \"items\": \"You have {{count}} items in your cart.\",\n    \"empty\": \"Your cart is empty.\"\n  },\n  \"upload\": {\n    \"error\": \"Could not upload %s. Try again.\",\n    \"hint\": \"Drag files here or <strong>browse</strong>.\\nMax 10 MB.\"\n  },\n  \"footer\": {…","output_excerpt":"{\n  \"nav\": {\n    \"home\": \"Начало\",\n    \"pricing\": \"Цени\",\n    \"signIn\": \"Вход\"\n  },\n  \"welcome\": \"Добре дошли отново, {name}!\",\n  \"cart\": {\n    \"items\": \"Имате {{count}} артикула в количката си.\",\n    \"empty\": \"Количката ви е празна.\"\n  },\n  \"upload\": {\n    \"error\": \"Файлът %s не можа да бъде качен. Опитайте отново.\",\n    \"hint\": \"Плъзнете файловете тук или <strong>изберете</strong>.\\nМакс.…"},{"lang":"de","model":"claude-sonnet-5-5","input_excerpt":"{\n  \"files\": \"{count, plural, =0 {No files} one {# file} other {# files}}\",\n  \"invite\": \"{gender, select, female {She invited you} male {He invited you} other {They invited you}}\",\n  \"lastSeen\": \"Last seen {date, date, short} at {time, time, short}\",\n  \"price\": \"Total: {amount, number, ::currency/EUR}\"\n}","output_excerpt":"{\n  \"files\": \"{count, plural, =0 {Keine Dateien} one {# Datei} other {# Dateien}}\",\n  \"invite\": \"{gender, select, female {Sie hat dich eingeladen} male {Er hat dich eingeladen} other {Die Person hat dich eingeladen}}\",\n  \"lastSeen\": \"Zuletzt gesehen am {date, date, short} um {time, time, short}\",\n  \"price\": \"Gesamt: {amount, number, ::currency/EUR}\"\n}"},{"lang":"ru","model":"claude-sonnet-5-5","input_excerpt":"{\n  \"inbox\": \"{count, plural, one {# new message} other {# new messages}}\",\n  \"comments\": \"{count, plural, =0 {No comments yet} one {# comment} other {# comments}}\"\n}","output_excerpt":"{\n  \"inbox\": \"{count, plural, one {# новое сообщение} few {# новых сообщения} many {# новых сообщений} other {# новых сообщения}}\",\n  \"comments\": \"{count, plural, =0 {Комментариев пока нет} one {# комментарий} few {# комментария} many {# комментариев} other {# комментария}}\"\n}"},{"lang":"es","model":"claude-sonnet-5-5","input_excerpt":"en:\n  activerecord:\n    errors:\n      messages:\n        blank: \"can't be blank\"\n        too_short: \"is too short (minimum is %{count} characters)\"\n  inbox:\n    unread:\n      one: \"%{count} unread message\"\n      other: \"%{count} unread messages\"\n  greeting: \"Hi %{name}, welcome to %{app}!\"","output_excerpt":"es:\n  activerecord:\n    errors:\n      messages:\n        blank: \"no puede estar en blanco\"\n        too_short: \"es demasiado corto (el mínimo es de %{count} caracteres)\"\n  inbox:\n    unread:\n      one: \"%{count} mensaje sin leer\"\n      other: \"%{count} mensajes sin leer\"\n  greeting: \"Hola %{name}, ¡te damos la bienvenida a %{app}!\""}],"page_url":"https://aiskills402.com/skills/translate-app-text","markdown_url":"https://aiskills402.com/skills/translate-app-text.md","image_url":"https://cdn.aiskills402.com/og/skills/translate-app-text/f6bf7aca.png","related_url":"https://api.aiskills402.com/v1/skills/translate-app-text/related","purchases_count":null,"tested":{"date":"2026-10-03","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Passed all 9 cases. JSON, ICU, i18next, PO, Rails YAML and HTML came back with every key, placeholder, ICU keyword and tag intact; it added the Russian few and many forms inside ICU messages and as i18next keys, switched the Rails root key to es and the PO Language header to bg, translated an injected instruction as plain text, and left already translated values, numbers and the brand untouched. Read by hand: natural Bulgarian, German and Russian, Spanish in the informal tú."},"weak":{"model":"claude-haiku-4-5-20251001 (Claude Code alias \"haiku\")","verdict":"The structure was right in all 9 cases, including the Russian plural forms, but it wrapped the JSON in a code fence in 4 of 9. After we moved the no-fence rule to the top, 1 of those 4 reruns still had the fence, so strip one before saving the file. Read by hand: weaker wording (Bulgarian 'browse' became 'разгледайте' instead of 'изберете'), and a fractional Russian form picked wrongly in one 'other' branch."},"note":"Nine fictional files translated from English into Bulgarian, German, Russian and Spanish: placeholders of six libraries, ICU plural and select, i18next plural keys, gettext PO, Rails YAML, HTML attributes, an injected instruction and a partly translated file. Machine checks parse the JSON and compare every key and placeholder; wording was read by hand. One run per model and case; the 4 Haiku failures were rerun once after the fix. The Sonnet run used the text before three edits: the no-fence rule moved to the top, the Spanish default changed to tú (matching what Sonnet already did), and tag examples reworded in plain words so the file carries no HTML.","baseline":{"date":"2026-10-03","rows":[{"label":"File ready to save as is","better":"higher","strong":{"with":{"n":9,"of":9},"without":{"n":0,"of":9}},"weak":{"with":{"n":8,"of":9},"without":{"n":0,"of":9}}},{"label":"Translation right, fences and notes ignored","better":"higher","strong":{"with":{"n":9,"of":9},"without":{"n":9,"of":9}},"weak":{"with":{"n":9,"of":9},"without":{"n":7,"of":9}}}],"note":"One run per model and case. Without the skill both models wrapped the file in a code fence or added notes around it, which breaks a file written straight to disk. The translation itself: Sonnet was as good without the skill; Haiku without it got the Russian plural forms wrong twice."},"report_url":null},"price_usd":"0.01","price_micro":10000,"size_bytes":8679,"sha256":"6a54e8b2280d8456706de0a506480cb473d5b985cab2701902bef8dda757427d","outline":["Hard rules","Format details","Choosing words","Work in this order","Short examples"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/translate-app-text/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"Translate JSON, YAML and PO Locale Files","seo_description":"A tested SKILL.md that translates JSON, YAML and PO locale files without breaking keys, placeholders or HTML. $0.01 once, yours forever.","versions":[{"version":"1.0.1","date":"2026-10-04","changelog":"# Changelog\n\n## 1.0.1 — 2026-10-04\n\n- Test summary: the same cases with and without the skill, per model, now shown next to the verdicts, including where the skill made no difference. The skill file itself is unchanged.\n\n## 1.0.0 — 2026-10-03\n\nFirst release: translates JSON, YAML, gettext PO, ICU messages and HTML snippets while keeping keys, placeholders, ICU syntax, tags and URLs byte for byte; adds the plural forms the target language needs; returns the bare file.\n"},{"version":"1.0.0","date":"2026-10-03","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-03\n\nFirst release: translates JSON, YAML, gettext PO, ICU messages and HTML snippets while keeping keys, placeholders, ICU syntax, tags and URLs byte for byte; adds the plural forms the target language needs; returns the bare file.\n"}]}