Locale-Aware Formatting
debt(d5/e3/b5/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and psalm, both specialist static analysis tools. The code_pattern shows hardcoded separators like number_format($price, 2, '.', ',') and date('d/m/Y') — these patterns are detectable by SAST/static analysers but won't be caught by compiler or default linter rules. Not silent in production (users see wrong output), but not caught instantly either.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates replacing hardcoded format strings with NumberFormatter and DateFormatter calls — a straightforward swap within the same component or template layer. However, common_mistakes list four distinct failure modes (currency symbols, date strings, pluralisation, number formatting) meaning it's slightly more than a one-liner but still localised to formatting call sites rather than cross-cutting.
Closest to 'persistent productivity tax' (b5). The term applies_to both web and cli contexts broadly, meaning any output-rendering code across the codebase must be consistent about locale-aware formatting. Developers adding new display features must always consider locale, but it doesn't reshape overall architecture — it's a pervasive formatting discipline rather than a structural choice.
Closest to 'serious trap' (t7). The misconception field explicitly states that developers believe replacing '.' with ',' is sufficient for European number formatting, not realising it also affects grouping separators, currency symbols, percent signs, and date patterns. This contradicts the intuition that number formatting is a simple text substitution, and the common_mistakes reinforce multiple non-obvious failure dimensions — a competent developer will frequently guess wrong about the scope of the fix.
Also Known As
TL;DR
Explanation
PHP's Intl extension (ICU-based) provides locale-aware formatting: NumberFormatter (numbers, currencies, percentages), DateFormatter (dates, times with locale-specific patterns), and MessageFormatter (pluralisation rules, placeholders). Locale identifiers use BCP 47 format (en-US, de-DE, zh-Hans-CN). Formatting must happen at the presentation layer — store raw values (1234.56, timestamps in UTC) and format for display.
Common Misconception
Why It Matters
Common Mistakes
- Hardcoding currency symbols — $ means dollars only in en-US context; use NumberFormatter with the currency code.
- Manual date string replacement for localisation — different locales have completely different date structures.
- Not handling pluralisation with MessageFormatter — '1 item' vs '2 items' varies by language in non-obvious ways.
- Assuming number formatting is sufficient — order of day/month/year varies by locale and cannot be fixed by number formatting alone.
Code Examples
// Manual locale formatting — fragile:
$price = 1234.56;
echo '€' . number_format($price, 2, ',', '.'); // '€1.234,56'
// Hardcoded to German format — wrong for French (1 234,56 €)
// Intl NumberFormatter — correct for any locale:
$formatter = new NumberFormatter('de-DE', NumberFormatter::CURRENCY);
echo $formatter->formatCurrency(1234.56, 'EUR'); // '1.234,56 €'
$formatter = new NumberFormatter('fr-FR', NumberFormatter::CURRENCY);
echo $formatter->formatCurrency(1234.56, 'EUR'); // '1 234,56 €'
// Date formatting:
$fmt = new IntlDateFormatter('de-DE', IntlDateFormatter::LONG, IntlDateFormatter::NONE);
echo $fmt->format(new DateTime('2026-03-15')); // '15. März 2026'