Internationalization enforcement for TypeScript, JavaScript, and Python. Detects hardcoded user-visible strings, locale-unaware date/number/currency formatting, and missing translation keys. Wired into the conductor's review and write operations.
From clean-code-codexnpx claudepluginhub mikecubed/agent-orchestration --plugin clean-code-codexThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Analyzes BMad project state from catalog CSV, configs, artifacts, and query to recommend next skills or answer questions. Useful for help requests, 'what next', or starting BMad.
Precedence in the overall system: SEC → TDD → ARCH/TYPE → I18N-1 (BLOCK) → I18N-2 through I18N-3.
Severity: BLOCK | Languages: TypeScript, JavaScript, Python | Source: CCC
What it prohibits: String literals that will be rendered to end users (UI text, error messages shown to users, button labels, page titles) that are hardcoded rather than routed through a translation/i18n function.
Prohibited patterns:
// TypeScript/JavaScript — JSX text
<Button>Submit</Button>
<h1>Dashboard</h1>
<label>Email address</label>
// TypeScript/JavaScript — user-facing API response messages
return res.status(404).json({ message: "User not found" });
// TypeScript/JavaScript — toast / flash / notification APIs
toast("Changes saved successfully");
showNotification("Your session has expired");
// TypeScript/JavaScript — page titles and labels
const title = "Dashboard";
document.title = "Settings";
# Python — user-facing messages
flash("Password must be at least 8 characters")
messages.error(request, "Invalid credentials")
raise ValidationError("This field is required")
title = "Dashboard"
Exemptions:
logger.info("Processing request")throw new Error("Invalid state"), raise RuntimeError("...")Detection:
>Some text</ patterns in .tsx/.jsx filestoast(, showNotification(,
flash(, messages.error(, messages.success() with inline string literalsres.json({ message: "..." }))flash(, messages.error(, messages.success(, raise ValidationError(
with inline string literalstitle, label, placeholder, heading, description
variables with hardcoded strings in component or view filesagent_action:
I18N-1 (BLOCK): Hardcoded user-visible string at {file}:{line} — "{string}" must be routed through i18n.// TypeScript/JavaScript (react-i18next)
<Button>{t('common.submit')}</Button>
// TypeScript/JavaScript — toast/notification APIs
toast(t('notifications.changesSaved'));
# Python (Django)
flash(_("Password must be at least 8 characters"))
# Python (gettext)
raise ValidationError(gettext("This field is required"))
--fix: wrap the string with the detected project i18n function and add
the key to the default locale file — require human confirmation for key namingBypass prohibition: "We only support English", "I'll add i18n later" → Refuse. Cite I18N-1. If the project genuinely has no i18n requirement, add an explicit project-level exemption comment or configuration flag, not an ad-hoc skip.
Severity: WARN | Languages: TypeScript, JavaScript, Python | Source: CCC
What it prohibits: Formatting dates, numbers, and currencies in ways that produce locale-specific output without using locale-aware APIs. A date formatted as "04/03/2026" is ambiguous (April 3 vs March 4 depending on locale). A number formatted with a period as decimal separator is incorrect in many European locales.
Prohibited patterns:
// TypeScript/JavaScript — dates
new Date().toString(); // locale-unaware
`${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`; // manual US format
// TypeScript/JavaScript — numbers and currency
"$" + amount; // hardcoded currency symbol
`$${amount.toFixed(2)}`; // hardcoded symbol + locale-unaware decimal
amount.toFixed(2); // for display — ignores locale decimal separator
# Python — dates
date.strftime("%m/%d/%Y") # US-locale-specific format in display code
f"{date:%m/%d/%Y}" # same issue via f-string
# Python — numbers and currency
f"${amount:.2f}" # hardcoded dollar sign + locale-unaware
str(number) # for display in user-facing context
Exemptions:
date.toISOString(), date.isoformat()toLocaleDateString(), toLocaleString(), toLocaleTimeString() with no explicit
locale argument are locale-aware (they use the runtime locale) and are not violationsIntl.DateTimeFormat(locale, options).format(date)date.toLocaleDateString(userLocale, options) (with explicit locale)Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)babel.dates.format_date(date, locale=user_locale)locale.format_string() or locale.currency()Detection:
.toFixed( in display/render contexts (component files, template files)new Date().toString() and Date().toString()"$" +, `$${ patternsstrftime( with US-specific format strings (%m/%d/%Y)f"$ currency formatting patternstoLocaleDateString(), toLocaleString(), or toLocaleTimeString()
without arguments — these are locale-aware by design (they use the runtime locale)agent_action:
I18N-2 (WARN): Locale-unaware formatting at {file}:{line} — "{expression}" produces locale-dependent output without explicit locale.// Dates
new Intl.DateTimeFormat(userLocale, { dateStyle: 'medium' }).format(date)
// Numbers
new Intl.NumberFormat(userLocale).format(amount)
// Currency
new Intl.NumberFormat(userLocale, { style: 'currency', currency: userCurrency }).format(amount)
# Dates (using babel)
from babel.dates import format_date
format_date(date, locale=user_locale)
# Currency (using babel)
from babel.numbers import format_currency
format_currency(amount, currency, locale=user_locale)
--fix: replace the locale-unaware call with the locale-aware equivalent
— require the developer to confirm the locale source variable nameSeverity: WARN | Languages: TypeScript, JavaScript | Source: CCC
What it prohibits: Code that calls an i18n function (t(), i18n.t(),
useTranslation) with a key that does not exist in the default locale translation
file. The key will silently fall back to the key string itself, producing broken UI
text like user.profile.newFeature displayed to the user.
Prohibited patterns:
// Key absent from en.json / messages.json / default locale file
t('user.profile.newFeature');
i18n.t('errors.paymentFailed');
const { t } = useTranslation();
return <span>{t('dashboard.welcomeBanner')}</span>; // key missing from locale
Exemptions:
i18next-parser, babel-plugin-react-intl)
where key absence in source triggers extraction — document this at the project levelt(dynamicKey), t(\errors.${code}`)`t('key', { defaultValue: 'Fallback text' })Detection:
t('...') and i18n.t('...') calls in the diffen.json, en/translation.json,
messages/en.json, locales/en.json, public/locales/en/translation.json,
or the path configured in i18n config files (i18next.config.*, next-i18next.config.*)i18next.config.js, i18next.config.ts,
i18n.ts, i18n.js, next-i18next.config.js). If the config sets keySeparator: false,
treat all keys as flat literal strings and skip nested traversal entirely."user.profile.name": "...")keySeparator is not false): attempt dot-notation
nested traversal (user.profile.name → { "user": { "profile": { "name": ... } } })agent_action:
I18N-3 (WARN): Translation key '{key}' at {file}:{line} not found in default locale file '{localeFile}'.keySeparator: false detected): add as a top-level literal key:
// en.json — add the missing key (flat key store)
{
"user.profile.newFeature": "TODO: add translation"
}
// en.json — add the missing key (nested)
{
"user": {
"profile": {
"newFeature": "TODO: add translation"
}
}
}
I18N-3 (WARN): Could not locate default locale file. Ensure i18n is configured and the locale file path is discoverable.--fix: add the key with a "TODO: add translation" placeholder value using the format appropriate for the detected config (flat literal key or nested) — require human to provide the actual translationReport schema: see skills/conductor/shared-contracts.md.