PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception

Unable to read locale data for '{$locale}'

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.

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)

Solutions

  1. Correct the locale code to a standard ISO-639 language + ISO-3166 country pair ICU knows ('en-GB', 'de-DE', 'fr-FR')
  2. Pre-verify the locale resolves: create a probe NumberFormatter and confirm ->getLocale() matches the requested locale (mirroring the library's own check)
  3. 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

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


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/2dcfbc44bc2d08ec. Report an issue: GitHub.