PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception
Unable to read locale data for
Error message
Unable to read locale data for '{$locale}' What it means
The Locale helper used by all NumberFormat Wizards creates a NumberFormatter for the requested locale, then compares the locale ICU actually resolved. ICU silently falls back to a root/default locale when it has no data for the requested one, so a mismatch between requested and returned locale means the locale is unknown or its data is not installed on this system. The offending locale is embedded in the message.
Solutions
- Correct the locale code to a standard ISO-639 language + ISO-3166 country pair ICU knows ('en-GB', 'de-DE', 'fr-FR')
- Pre-verify the locale resolves: create a probe NumberFormatter and confirm ->getLocale() matches the requested locale (mirroring the library's own check)
- Catch the exception and fall back to a non-localized wizard or a hardcoded format mask
Example fix
// before
$wizard->setLocale('en-UK');
// after
$wizard->setLocale('en-GB'); Defensive patterns
Strategy: validation
Validate before calling
function icuKnowsLocale(string $locale): bool
{
$fmt = new \NumberFormatter(str_replace('-', '_', $locale), \NumberFormatter::DECIMAL);
return $fmt->getLocale() === str_replace('-', '_', $locale);
}
$locale = icuKnowsLocale('en-GB') ? 'en-GB' : 'en';
$wizard->setLocale($locale); Try / catch
try {
$wizard->setLocale($requestedLocale);
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
// unknown locale: degrade to the non-localized wizard
$wizard = new Number(2);
} Prevention
- Keep a whitelist of locales your app actually supports
- Test with a probe NumberFormatter in CI on the production image
- Normalize codes early (en_UK -> en-GB) instead of trusting input
When it happens
Trigger: Calling setLocale('en-UK'), setLocale('xx-YY') or any code that passes the STRUCTURE regex but has no ICU locale data; e.g. $wizard->setLocale('en-UK') where the valid form is 'en-GB'.
Common situations: Typo'd locale strings; codes valid in other ecosystems (Java, CLDR aliases) but not installed in the system ICU; slim Docker images where ICU locale data was pruned.
Related errors
- The Intl extension does not support Accounting Formats…
- Invalid locale code
- A Worksheet has already been assigned. Drawings can only…
- AND Value is only appropriate for range operators
- $bitmap: compression not supported in bitmap image.\n
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/2dcfbc44bc2d08ec.
Report an issue: GitHub.
Appendix: source
Thrown at src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php:25
final class Locale
{
/**
* Language code: ISO-639 2 character, alpha.
* Optional script code: ISO-15924 4 alpha.
* Optional country code: ISO-3166-1, 2 character alpha.
* Separated by underscores or dashes.
*/
public const STRUCTURE = '/^(?P<language>[a-z]{2})([-_](?P<script>[a-z]{4}))?([-_](?P<country>[a-z]{2}))?$/i';
private NumberFormatter $formatter;
public function __construct(?string $locale, int $style)
{
$formatterLocale = str_replace('-', '_', $locale ?? '');
$this->formatter = new NumberFormatter($formatterLocale, $style);
if ($this->formatter->getLocale() !== $formatterLocale) {
throw new Exception("Unable to read locale data for '{$locale}'");
}
}
public function format(bool $stripRlm = true): string
{
$str = $this->formatter->getPattern();
return ($stripRlm && str_starts_with($str, "\xe2\x80\x8f")) ? substr($str, 3) : $str;
}
}
View on GitHub (pinned to 65b080eef4)