Implement internationalization and localization — string extraction, RTL support, locale-aware formatting, translation workflows, and platform i18n patterns
npx claudepluginhub cure-consulting-group/productengineeringskillsThis skill uses the workspace's default tool permissions.
Implements production-grade i18n/l10n across Android, iOS, and web. Every output enforces strict string externalization, platform-native formatting, RTL correctness, and CI-validated translation completeness. No hardcoded user-facing strings — ever.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Implements production-grade i18n/l10n across Android, iOS, and web. Every output enforces strict string externalization, platform-native formatting, RTL correctness, and CI-validated translation completeness. No hardcoded user-facing strings — ever.
Before starting, gather project context silently:
PORTFOLIO.md if it exists in the project root or parent directories for product/team contextcat package.json 2>/dev/null || cat build.gradle.kts 2>/dev/null || cat Podfile 2>/dev/null to detect stackgit log --oneline -5 2>/dev/null for recent changesls src/ app/ lib/ functions/ 2>/dev/null to understand project structureDay 1 decision → Externalize every user-facing string from the start
Concatenation → Never concatenate translated strings; use parameterized templates
Formatting → Always use platform formatters for dates, numbers, currency
Layout → Design for 40% text expansion and RTL from the beginning
Hard rules:
greeting + name) — use ICU MessageFormat or platform equivalentsLocale-aware platform formattersfeature.screen.element naming conventionif count == 1 branches| Request | Primary Output | Action |
|---|---|---|
| Greenfield i18n setup | Full i18n architecture + string extraction plan | Scaffold i18n layer |
| Add new locale | Translation file templates + locale routing | Add locale |
| RTL support | Layout mirroring rules + bidirectional text handling | Implement RTL |
| Translation workflow | Export/import pipeline + CI validation | Design workflow |
| i18n audit | Hardcoded string report + compliance gaps | Audit codebase |
| Locale-aware formatting | Date/number/currency formatter setup | Configure formatters |
Before generating, confirm:
Android:
res/values/strings.xml ← Default (English)
res/values-es/strings.xml ← Spanish
res/values-ar/strings.xml ← Arabic
res/values-ja/strings.xml ← Japanese
res/values-es-rMX/strings.xml ← Spanish (Mexico) — region-specific
iOS:
Localizable.xcstrings ← String Catalog (Xcode 15+, preferred)
OR
en.lproj/Localizable.strings ← Legacy format
es.lproj/Localizable.strings
ar.lproj/Localizable.strings
Web (Next.js):
messages/en.json ← next-intl JSON files
messages/es.json
messages/ar.json
middleware.ts ← Locale detection + routing
i18n.ts ← Configuration
All string keys use dot-separated hierarchy: feature.screen.element
# Good
auth.login.title = "Sign In"
auth.login.email_placeholder = "Email address"
auth.login.submit_button = "Sign In"
auth.login.error.invalid_credentials = "Invalid email or password"
profile.settings.language_label = "Language"
orders.detail.status.shipped = "Shipped"
# Bad
login_title ← No feature namespace
btnSignIn ← camelCase, no hierarchy
error1 ← Meaningless key
Use ICU MessageFormat for all plurals — never hand-roll plural logic.
Android (strings.xml):
<plurals name="orders.count">
<item quantity="zero">No orders</item>
<item quantity="one">%d order</item>
<item quantity="other">%d orders</item>
</plurals>
iOS (String Catalog):
"orders.count" = "%lld order(s)";
← Xcode String Catalogs handle plural variants automatically per locale
Web (ICU MessageFormat):
{
"orders.count": "{count, plural, =0 {No orders} one {# order} other {# orders}}"
}
"Hello, " + name is WRONG"Hello, {name}" is correct%1$s, %2$d positional format specifiers{name} named parameters with next-intlConfigure lint rules to catch violations:
android:text="..." in XML and string literals in Text() composablesText() views// Compose — always use stringResource()
Text(text = stringResource(R.string.auth_login_title))
Text(text = pluralStringResource(R.plurals.orders_count, count, count))
// ViewModel — inject StringProvider interface (domain layer stays platform-free)
interface StringProvider {
fun getString(@StringRes resId: Int): String
fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg args: Any): String
}
// Locale change — Compose recomposes automatically via LocalConfiguration
// For programmatic locale change:
val locale = Locale("es", "MX")
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(locale.toLanguageTag()))
// SwiftUI — String Catalogs handle localization automatically
Text("auth.login.title") // Looked up in Localizable.xcstrings
// Programmatic string lookup
let message = String(localized: "orders.count \(count)")
// Locale override for previews
Text("auth.login.title")
.environment(\.locale, Locale(identifier: "ar-SA"))
// middleware.ts — locale routing
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'es', 'ar', 'ja'],
defaultLocale: 'en',
localePrefix: 'always' // /en/about, /es/about
});
// Server Component
import { getTranslations } from 'next-intl/server';
export default async function LoginPage() {
const t = await getTranslations('auth.login');
return <h1>{t('title')}</h1>;
}
// Client Component
'use client';
import { useTranslations } from 'next-intl';
export function LoginForm() {
const t = useTranslations('auth.login');
return <button>{t('submit_button')}</button>;
}
i18n_{locale}_{feature}_{key} (e.g., i18n_en_promo_banner_text)android:supportsRtl="true" in manifest; use start/end instead of left/right everywhere.leading/.trailingdir="auto" on the <html> tag; use CSS logical properties (margin-inline-start, padding-inline-end) instead of margin-left/padding-right\u2066...\u2069) when embedding LTR text in RTL context or vice versaen-XA for accents/expansion, ar-XB for RTL)NSDoubleLocalizedStrings launch argument for expansion testingNEVER: "${month}/${day}/${year}"
ALWAYS: platform date formatter with locale
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(locale)Date.FormatStyle.dateTime.locale(locale) or DateFormatter with locale setIntl.DateTimeFormat(locale, options) — never moment.js format stringsNumberFormat.getCurrencyInstance(locale)Decimal.FormatStyle.currency(code:).locale(locale)Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' })+15551234567), display in local format1. Developer adds strings to source locale file (en.json / strings.xml / .xcstrings)
2. CI extracts new/changed strings → export to translation platform
3. Translators translate in platform (Lokalise, Phrase, Crowdin)
4. Translated files imported back via CI pull request
5. CI validates: no missing keys, no untranslated strings, no broken format specifiers
6. Merge → deploy
%1$s count matches across all translations of a key[WIP] marker in CI configar locale.environment(\.layoutDirection, .rightToLeft)locale: 'ar' configurationFor every i18n implementation, deliver:
| Key | Source (en) | Status | Notes |
|-----|-------------|--------|-------|
| auth.login.title | "Sign In" | Translated | — |
| auth.login.error.network | "Connection failed" | Missing: ar, ja | Needs translation |
android:
string_resources: res/values-{locale}/strings.xml
locale_switching: AppCompatDelegate.setApplicationLocales()
compose: stringResource(), pluralStringResource()
lint: custom detekt rule for hardcoded strings
ios:
string_catalogs: Localizable.xcstrings (Xcode 15+)
swiftui: automatic Text() localization
locale_override: environment(\.locale)
lint: SwiftLint custom rule
web:
framework: next-intl 3.x
routing: middleware locale detection + /[locale]/ prefix
icu: ICU MessageFormat for plurals and select
lint: eslint-plugin-i18next
translation_platforms:
recommended: Lokalise, Phrase, or Crowdin
export_format: XLIFF (cross-platform), JSON (web), XML (Android)
testing:
pseudolocale: en-XA (expansion) + ar-XB (RTL)
screenshots: per-locale baseline comparison in CI
Generate i18n infrastructure using Write:
locales/en.json — base English strings extracted from codebasesrc/i18n/config.ts — i18next or react-intl configurationscripts/generate-pseudolocale.ts — generates en-XA for testingscripts/check-translations.sh — finds keys in code without translationsBefore generating, Grep for existing i18n setup (i18next|react-intl|NSLocalizedString|getString|stringResource) and hardcoded user-facing strings.
/product-design — design for text expansion and RTL from wireframe stage/accessibility-audit — ensure localized strings include accessibility labels/testing-strategy — integrate i18n test suite into testing pyramid/release-management — coordinate translation completion with release schedule/customer-onboarding — localize onboarding flows and activation emails