frankenphp icon indicating copy to clipboard operation
frankenphp copied to clipboard

docs: Document gettext incompatibility

Open Starfox64 opened this issue 2 months ago • 8 comments

gettext is not thread safe and can only load a single locale per process, this causes a mix and match of languages to be returned from translations.

https://stackoverflow.com/a/1646343

Starfox64 avatar Oct 24 '25 12:10 Starfox64

This should be added to the php documentation as well. And maybe php should even fail to compile gettext extension in a ZTS build? Does it also affect the intl extension, considering that libintl is part of the gettext package?

henderkes avatar Oct 24 '25 14:10 henderkes

After further research, it seems this isn't strictly gettext specific but in fact related to setlocale having a process-wide effect on Linux. I should probably change the reason then.

This probably also affects libintl in general whenever changes to the locale occur but I'm not 100% sure either. How should we go about documenting it ?

Starfox64 avatar Oct 24 '25 18:10 Starfox64

I wonder if this couldn't be changed in php-src. uselocale() does the same as setlocale(), but it's mt-safe and only affects the current thread.

henderkes avatar Oct 24 '25 20:10 henderkes

I will create a php RFC to rework setlocale() to use uselocale() on Posix too.

henderkes avatar Oct 25 '25 20:10 henderkes

I have a POC working, but it's more complicated than I'd hoped, because uselocale doesn't support setting individual locales. I need to createlocale with the intended masks and free old locales, if no longer required. To convert them to locale strings individually, we need to track one per available LC_ constant,, which is 5 in general and then another 5 gnu extensions.

Tl;dr: working, but messy.

henderkes avatar Oct 26 '25 11:10 henderkes

Given the following PHP code to simulate different FrankenPHP requests:

<?php
use parallel\Runtime;

setlocale(LC_ALL,'C');
echo "main@start   : ".setlocale(LC_ALL,0)." | dp=".localeconv()['decimal_point']."\n";

$f = (new Runtime())->run(function () {
    setlocale(LC_ALL,'de_DE.UTF-8');
    echo "worker@set   : ".setlocale(LC_ALL,0)." | dp=".localeconv()['decimal_point']."\n";
});

echo "main@after   : ".setlocale(LC_ALL,0)." | dp=".localeconv()['decimal_point']."\n";

$f->value();

Currently:

[m@M bin]$ php -d "extension=/home/m/static-php-cli/buildroot/modules/parallel.so" localetest.php 
main@start   : C.UTF-8 | dp=.
worker@set   : de_DE.UTF-8 | dp=,
main@after   : de_DE.UTF-8 | dp=,

Expected, after proposed implementation:

[m@M bin]$ ./php -d "extension=/home/m/static-php-cli/buildroot/modules/parallel.so" localetest.php 
main@start   : C.UTF-8 | dp=.
worker@set   : de_DE.UTF-8 | dp=,
main@after   : C.UTF-8 | dp=.

henderkes avatar Oct 28 '25 15:10 henderkes

Hi, I'm facing the very same issue right now and it would be great to see a fix for this soon. I also created a small demo with Copilot to demonstrate and test this issue with an actual web app. https://github.com/Seros/frankenphp-setlocale-demo

Seros avatar Nov 10 '25 09:11 Seros

It's "working as documented" for php. I've brought it into php-internals circulation, but the fix does not lie in our (frankenphp) domain.

Will update when there's news.

henderkes avatar Nov 10 '25 09:11 henderkes