Greetings hackers,

The libintl library provides a libintl_setlocale function that we need
to use to correctly invalidate the libintl translation cache.  We are
not doing this as the following shows.

On Windows, changing lc_messages at runtime via SET returns stale
translations from the previous locale.  For example:

    SET lc_messages = 'de_DE';
    SELECT 1/0;
    -- FEHLER:  Division durch Null   (correct, German)

    SET lc_messages = 'en_US';
    SELECT 1/0;
    -- FEHLER:  Division durch Null   (wrong, still German)

libintl caches translated messages in a binary tree keyed by (msgid,
domain, category). The key struct includes a counter and a pointer to
the translation text and translation length in the mmap'd .mo file for
that locale.

Background:
Assume you have ("PL/pgSQL function %s line %d at %s",
"plpgsql-19",1729) for (msgid, domain, category) respectively.  Gettext
uses strcmp for msgid and domain, and integer subtraction for category
as it's comparison function. The search through the tree will short
circuit at a node as soon as one of the three-- compared left to right
as listed in the tuple-- is not a match.  If the comparison is negative
then it goes left in the tree, positive it goes right.  If all three
components of the key match then it checks that the counter matches
_nl_msg_cat_cntr.  If the counter matches then it is a cache hit.  If
the counter does not match then this translation was stored in the cache
before the current locale was set with libintl_setlocale-- in other
words it is stale. In this case the cache is invalidated and it is a
cache miss because the locale has changed.

Notice locale is not directly involved in the key structure for windows.
 So, if you don't increment the counter by calling libintl_setlocale and
the two locales have the same msgid's for a given domain you will get
the problem shown at the beginning of this email.


The fix adds an explicit call to libintl_setlocale(LC_MESSAGES, ...)
in pg_perm_setlocale() after IsoLocaleName() has produced the POSIX
name.  Instead of an explicit call, we could define
_INTL_REDIRECT_MACROS before including libintl.h in c.h.  This would
make all setlocale() calls go through libintl_setlocale on MSVC.  I
chose to be explicit to limit the blast radius.  The attached patch
limits the change to a single spot and resolves the identified bug.


Patch attached.

-- 
Bryan Green
EDB: https://www.enterprisedb.com
From be53a6e60c9a1d46e3797bffc604816324fdf616 Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
Date: Tue, 31 Mar 2026 20:14:12 -0500
Subject: [PATCH v1] Fix stale LC_MESSAGES translations after SET on Windows.

On Windows, pg_perm_setlocale() skips calling setlocale() for
LC_MESSAGES because the MSVC runtime doesn't support that category.
Instead it sets the LC_MESSAGES environment variable directly.
This means libintl's translation cache (_nl_msg_cat_cntr) is never
invalidated, so SET lc_messages at runtime returns stale translations
from the previous locale.

Fix by calling libintl_setlocale() directly after IsoLocaleName()
has produced the POSIX locale name.

Author: Bryan Green
---
 src/backend/utils/adt/pg_locale.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/backend/utils/adt/pg_locale.c 
b/src/backend/utils/adt/pg_locale.c
index 6c5c1019e1..cea3d1e716 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -237,6 +237,17 @@ pg_perm_setlocale(int category, const char *locale)
                        if (result == NULL)
                                result = (char *) locale;
                        elog(DEBUG3, "IsoLocaleName() executed; locale: 
\"%s\"", result);
+
+                       /*
+                        * Use the libintl_setlocale function provided by 
libintl so
+                        * it invalidates its translation cache 
(_nl_msg_cat_cntr) as
+                        * needed.  Without this, resetting lc_messages to a 
different
+                        * locale at runtime returns stale translations from the
+                        * previous locale.
+                        */
+#ifdef ENABLE_NLS
+                       (void) libintl_setlocale(LC_MESSAGES, result);
+#endif
 #endif                                                 /* WIN32 */
                        break;
 #endif                                                 /* LC_MESSAGES */
-- 
2.52.0.windows.1

Reply via email to