Обсуждение: Let's add a test for NLS translation of PRI* macros

Поиск
Список
Период
Сортировка

Let's add a test for NLS translation of PRI* macros

От
Tom Lane
Дата:
This was discussed a few weeks ago in [1], but I'm starting a
new thread so as not to confuse things with the latest patches in
that thread.  The issue is that it seems like it'd be a good idea to
have specific regression testing of whether translation of the
<inttypes.h> PRI* macros works properly, since that is an aspect
of gettext() behavior that didn't use to work everywhere.

I don't know whether the attached will pass on Windows: we might
not be able to assume that "es_ES" is the right LC_MESSAGES
setting to use.  But it works for me on Linux.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/20250331.152829.1921392690375275165.horikyota.ntt%40gmail.com

From d65fbf666e93fc3dd8c0e7c9817ace3c014ab99d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 8 Dec 2025 14:14:59 -0500
Subject: [PATCH v3] Add a regression test to verify that NLS translation
 works.

We've never actually had a formal test for this facility.
It seems worth adding one now, mainly because we are starting
to depend on gettext() being able to handle the PRI* macros
from <inttypes.h>, and it's not all that certain that that
works everywhere.  So the test goes to a bit of effort to
check all the PRI* macros we are likely to use.
---
 src/test/regress/expected/nls.out   |  32 ++++++
 src/test/regress/expected/nls_1.out |  17 +++
 src/test/regress/meson.build        |   2 +
 src/test/regress/nls.mk             |   5 +
 src/test/regress/parallel_schedule  |   2 +-
 src/test/regress/po/LINGUAS         |   1 +
 src/test/regress/po/es.po           | 159 ++++++++++++++++++++++++++++
 src/test/regress/po/meson.build     |   3 +
 src/test/regress/regress.c          |  63 +++++++++++
 src/test/regress/sql/nls.sql        |  16 +++
 10 files changed, 299 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/nls.out
 create mode 100644 src/test/regress/expected/nls_1.out
 create mode 100644 src/test/regress/nls.mk
 create mode 100644 src/test/regress/po/LINGUAS
 create mode 100644 src/test/regress/po/es.po
 create mode 100644 src/test/regress/po/meson.build
 create mode 100644 src/test/regress/sql/nls.sql

diff --git a/src/test/regress/expected/nls.out b/src/test/regress/expected/nls.out
new file mode 100644
index 00000000000..d1afc528a8d
--- /dev/null
+++ b/src/test/regress/expected/nls.out
@@ -0,0 +1,32 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+SET lc_messages = 'es_ES';
+SELECT test_translation();
+NOTICE:  traducido PRId64 = 424242424242
+NOTICE:  traducido PRId32 = -1234
+NOTICE:  traducido PRIdMAX = -5678
+NOTICE:  traducido PRIdPTR = 9999
+NOTICE:  traducido PRIu64 = 424242424242
+NOTICE:  traducido PRIu32 = 1234
+NOTICE:  traducido PRIuMAX = 5678
+NOTICE:  traducido PRIuPTR = 9999
+NOTICE:  traducido PRIx64 = 62c6d1a9b2
+NOTICE:  traducido PRIx32 = 4d2
+NOTICE:  traducido PRIxMAX = 162e
+NOTICE:  traducido PRIxPTR = 270f
+NOTICE:  traducido PRIX64 = 62C6D1A9B2
+NOTICE:  traducido PRIX32 = 4D2
+NOTICE:  traducido PRIXMAX = 162E
+NOTICE:  traducido PRIXPTR = 270F
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/expected/nls_1.out b/src/test/regress/expected/nls_1.out
new file mode 100644
index 00000000000..4b707e9dad4
--- /dev/null
+++ b/src/test/regress/expected/nls_1.out
@@ -0,0 +1,17 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+SET lc_messages = 'es_ES';
+SELECT test_translation();
+NOTICE:  NLS is not enabled
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index 1da9e9462a9..4001a81ffe5 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -57,3 +57,5 @@ tests += {
     'dbname': 'regression',
   },
 }
+
+subdir('po', if_found: libintl)
diff --git a/src/test/regress/nls.mk b/src/test/regress/nls.mk
new file mode 100644
index 00000000000..43227c64f09
--- /dev/null
+++ b/src/test/regress/nls.mk
@@ -0,0 +1,5 @@
+# src/test/regress/nls.mk
+CATALOG_NAME     = regress
+GETTEXT_FILES    = regress.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_FLAGS    = $(BACKEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0931f1dcccf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -76,7 +76,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan
tidrangescancollate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions nls sysviews tsrf tid
tidscantidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 

 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
diff --git a/src/test/regress/po/LINGUAS b/src/test/regress/po/LINGUAS
new file mode 100644
index 00000000000..8357fcaaed4
--- /dev/null
+++ b/src/test/regress/po/LINGUAS
@@ -0,0 +1 @@
+es
diff --git a/src/test/regress/po/es.po b/src/test/regress/po/es.po
new file mode 100644
index 00000000000..b3021d57e22
--- /dev/null
+++ b/src/test/regress/po/es.po
@@ -0,0 +1,159 @@
+# Spanish message translation file for regress test library
+#
+# Copyright (C) 2025 PostgreSQL Global Development Group
+# This file is distributed under the same license as the regress (PostgreSQL) package.
+#
+# Tom Lane <tgl@sss.pgh.pa.us>, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: regress (PostgreSQL) 19\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2025-12-08 13:57-0500\n"
+"PO-Revision-Date: 2025-11-19 19:01-0500\n"
+"Last-Translator: Tom Lane <tgl@sss.pgh.pa.us>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: regress.c:202
+#, c-format
+msgid "invalid input syntax for type %s: \"%s\""
+msgstr "la sintaxis de entrada no es válida para tipo %s: «%s»"
+
+#: regress.c:839
+#, c-format
+msgid "test_inline_in_from_support_func called with %d args but expected 3"
+msgstr ""
+
+#: regress.c:847 regress.c:863
+#, c-format
+msgid "test_inline_in_from_support_func called with non-Const parameters"
+msgstr ""
+
+#: regress.c:854 regress.c:870
+#, c-format
+msgid "test_inline_in_from_support_func called with non-TEXT parameters"
+msgstr ""
+
+#: regress.c:903
+#, c-format
+msgid "test_inline_in_from_support_func parsed to more than one node"
+msgstr ""
+
+#: regress.c:914
+#, c-format
+msgid "test_inline_in_from_support_func rewrote to more than one node"
+msgstr ""
+
+#: regress.c:921
+#, c-format
+msgid "test_inline_in_from_support_func didn't parse to a Query"
+msgstr ""
+
+#: regress.c:1028
+#, c-format
+msgid "invalid source encoding name \"%s\""
+msgstr "la codificación de origen «%s» no es válida"
+
+#: regress.c:1033
+#, c-format
+msgid "invalid destination encoding name \"%s\""
+msgstr "la codificación de destino «%s» no es válida"
+
+#: regress.c:1078
+#, c-format
+msgid "default conversion function for encoding \"%s\" to \"%s\" does not exist"
+msgstr "no existe el procedimiento por omisión de conversión desde la codificación «%s» a «%s»"
+
+#: regress.c:1085
+#, c-format
+msgid "out of memory"
+msgstr "memoria agotada"
+
+#: regress.c:1086
+#, c-format
+msgid "String of %d bytes is too long for encoding conversion."
+msgstr "La cadena de %d bytes es demasiado larga para la recodificación."
+
+#: regress.c:1175
+#, c-format
+msgid "translated PRId64 = %<PRId64>"
+msgstr "traducido PRId64 = %<PRId64>"
+
+#: regress.c:1177
+#, c-format
+msgid "translated PRId32 = %<PRId32>"
+msgstr "traducido PRId32 = %<PRId32>"
+
+#: regress.c:1179
+#, c-format
+msgid "translated PRIdMAX = %<PRIdMAX>"
+msgstr "traducido PRIdMAX = %<PRIdMAX>"
+
+#: regress.c:1181
+#, c-format
+msgid "translated PRIdPTR = %<PRIdPTR>"
+msgstr "traducido PRIdPTR = %<PRIdPTR>"
+
+#: regress.c:1184
+#, c-format
+msgid "translated PRIu64 = %<PRIu64>"
+msgstr "traducido PRIu64 = %<PRIu64>"
+
+#: regress.c:1186
+#, c-format
+msgid "translated PRIu32 = %<PRIu32>"
+msgstr "traducido PRIu32 = %<PRIu32>"
+
+#: regress.c:1188
+#, c-format
+msgid "translated PRIuMAX = %<PRIuMAX>"
+msgstr "traducido PRIuMAX = %<PRIuMAX>"
+
+#: regress.c:1190
+#, c-format
+msgid "translated PRIuPTR = %<PRIuPTR>"
+msgstr "traducido PRIuPTR = %<PRIuPTR>"
+
+#: regress.c:1193
+#, c-format
+msgid "translated PRIx64 = %<PRIx64>"
+msgstr "traducido PRIx64 = %<PRIx64>"
+
+#: regress.c:1195
+#, c-format
+msgid "translated PRIx32 = %<PRIx32>"
+msgstr "traducido PRIx32 = %<PRIx32>"
+
+#: regress.c:1197
+#, c-format
+msgid "translated PRIxMAX = %<PRIxMAX>"
+msgstr "traducido PRIxMAX = %<PRIxMAX>"
+
+#: regress.c:1199
+#, c-format
+msgid "translated PRIxPTR = %<PRIxPTR>"
+msgstr "traducido PRIxPTR = %<PRIxPTR>"
+
+#: regress.c:1202
+#, c-format
+msgid "translated PRIX64 = %<PRIX64>"
+msgstr "traducido PRIX64 = %<PRIX64>"
+
+#: regress.c:1204
+#, c-format
+msgid "translated PRIX32 = %<PRIX32>"
+msgstr "traducido PRIX32 = %<PRIX32>"
+
+#: regress.c:1206
+#, c-format
+msgid "translated PRIXMAX = %<PRIXMAX>"
+msgstr "traducido PRIXMAX = %<PRIXMAX>"
+
+#: regress.c:1208
+#, c-format
+msgid "translated PRIXPTR = %<PRIXPTR>"
+msgstr "traducido PRIXPTR = %<PRIXPTR>"
diff --git a/src/test/regress/po/meson.build b/src/test/regress/po/meson.build
new file mode 100644
index 00000000000..e9bd964aa7f
--- /dev/null
+++ b/src/test/regress/po/meson.build
@@ -0,0 +1,3 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+nls_targets += [i18n.gettext('regress-' + pg_version_major.to_string())]
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 56cc0567b1c..2d5d799f6e4 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -48,6 +48,10 @@
 #include "utils/rel.h"
 #include "utils/typcache.h"

+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("regress")
+
 #define EXPECT_TRUE(expr)    \
     do { \
         if (!(expr)) \
@@ -1149,3 +1153,62 @@ test_relpath(PG_FUNCTION_ARGS)

     PG_RETURN_VOID();
 }
+
+/*
+ * Simple test to verify NLS support, particularly that the PRI* macros work.
+ */
+PG_FUNCTION_INFO_V1(test_translation);
+Datum
+test_translation(PG_FUNCTION_ARGS)
+{
+#ifdef ENABLE_NLS
+    /* This would be better done in _PG_init(), if this module had one */
+    static bool inited = false;
+
+    if (!inited)
+    {
+        pg_bindtextdomain(TEXTDOMAIN);
+        inited = true;
+    }
+
+    ereport(NOTICE,
+            errmsg("translated PRId64 = %" PRId64, (int64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRId32 = %" PRId32, (int32) -1234));
+    ereport(NOTICE,
+            errmsg("translated PRIdMAX = %" PRIdMAX, (intmax_t) -5678));
+    ereport(NOTICE,
+            errmsg("translated PRIdPTR = %" PRIdPTR, (intptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIu64 = %" PRIu64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIu32 = %" PRIu32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIuMAX = %" PRIuMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIuPTR = %" PRIuPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIx64 = %" PRIx64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIx32 = %" PRIx32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIxMAX = %" PRIxMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIxPTR = %" PRIxPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIX64 = %" PRIX64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIX32 = %" PRIX32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIXMAX = %" PRIXMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIXPTR = %" PRIXPTR, (uintptr_t) 9999));
+#else
+    elog(NOTICE, "NLS is not enabled");
+#endif
+
+    PG_RETURN_VOID();
+}
diff --git a/src/test/regress/sql/nls.sql b/src/test/regress/sql/nls.sql
new file mode 100644
index 00000000000..53b4add86eb
--- /dev/null
+++ b/src/test/regress/sql/nls.sql
@@ -0,0 +1,16 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+
+\set regresslib :libdir '/regress' :dlsuffix
+
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+
+SET lc_messages = 'es_ES';
+
+SELECT test_translation();
+
+RESET lc_messages;
--
2.43.7


Re: Let's add a test for NLS translation of PRI* macros

От
Bryan Green
Дата:
On 12/8/2025 1:23 PM, Tom Lane wrote:
> This was discussed a few weeks ago in [1], but I'm starting a
> new thread so as not to confuse things with the latest patches in
> that thread.  The issue is that it seems like it'd be a good idea to
> have specific regression testing of whether translation of the
> <inttypes.h> PRI* macros works properly, since that is an aspect
> of gettext() behavior that didn't use to work everywhere.
> 
> I don't know whether the attached will pass on Windows: we might
> not be able to assume that "es_ES" is the right LC_MESSAGES
> setting to use.  But it works for me on Linux.
> 
>             regards, tom lane
> 
> [1] https://www.postgresql.org/message-id/flat/20250331.152829.1921392690375275165.horikyota.ntt%40gmail.com
> 
gettext() will be fine with that if you are using < 0.20.  After that
version it expects you to send it the windows locale, not the
IsoLocalName() converted one-- it will fail to find "es-ES" (by
enumerating through ~259 window locales) and use a fallback to translate
messages.  Since gettext() will not cache the "not found" locale, the
expensive enumeration call will happen everytime [1].  I am in the
middle of writing some patches to take care of that problem and a couple
of others involving that area of the code and gettext().

[1] https://savannah.gnu.org/bugs/?67781

-- 
Bryan Green
EDB: https://www.enterprisedb.com



Re: Let's add a test for NLS translation of PRI* macros

От
Tom Lane
Дата:
Bryan Green <dbryan.green@gmail.com> writes:
> On 12/8/2025 1:23 PM, Tom Lane wrote:
>> I don't know whether the attached will pass on Windows: we might
>> not be able to assume that "es_ES" is the right LC_MESSAGES
>> setting to use.  But it works for me on Linux.

> gettext() will be fine with that if you are using < 0.20.  After that
> version it expects you to send it the windows locale, not the
> IsoLocalName() converted one-- it will fail to find "es-ES" (by
> enumerating through ~259 window locales) and use a fallback to translate
> messages.  Since gettext() will not cache the "not found" locale, the
> expensive enumeration call will happen everytime [1].  I am in the
> middle of writing some patches to take care of that problem and a couple
> of others involving that area of the code and gettext().

Cool; we'll worry about that later then.  In the meantime, the
cfbot discovered two other problems with this patch:

1. FreeBSD and NetBSD don't like "es_ES" either: they want a codeset
specification appended, and it had better be spelled just so
(eg, "UTF-8" not "utf-8" or "utf8").

2. FreeBSD and macOS translate PRIdMAX as "jd" which causes our
snprintf.c to spit up.  We might have noticed this earlier, but
the only use of PRI?MAX in our tree at the moment is in zic.c's
error reports, a code path we don't ordinarily exercise.

v4-0001 attached tries to deal with #1 by extracting a codeset
name from pg_database.datctype. That seems to work for me locally,
but I'll be interested to see what cfbot thinks.

v4-0002 attached deals with #2 by making snprintf.c support the "j"
width modifier, as required by POSIX for years now.  We'd likely
have had to do that at some point anyway, so might as well be now.

(0002 probably ought to get committed first, but I wrote them
in this order so here they are.)

            regards, tom lane

From 8363b5e39a3a3b3b02884359d82a7a7950b18a47 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 8 Dec 2025 17:40:00 -0500
Subject: [PATCH v4 1/2] Add a regression test to verify that NLS translation
 works.

We've never actually had a formal test for this facility.
It seems worth adding one now, mainly because we are starting
to depend on gettext() being able to handle the PRI* macros
from <inttypes.h>, and it's not all that certain that that
works everywhere.  So the test goes to a bit of effort to
check all the PRI* macros we are likely to use.
---
 src/test/regress/expected/nls.out   |  37 +++++++
 src/test/regress/expected/nls_1.out |  22 ++++
 src/test/regress/meson.build        |   2 +
 src/test/regress/nls.mk             |   5 +
 src/test/regress/parallel_schedule  |   2 +-
 src/test/regress/po/LINGUAS         |   1 +
 src/test/regress/po/es.po           | 159 ++++++++++++++++++++++++++++
 src/test/regress/po/meson.build     |   3 +
 src/test/regress/regress.c          |  63 +++++++++++
 src/test/regress/sql/nls.sql        |  21 ++++
 10 files changed, 314 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/nls.out
 create mode 100644 src/test/regress/expected/nls_1.out
 create mode 100644 src/test/regress/nls.mk
 create mode 100644 src/test/regress/po/LINGUAS
 create mode 100644 src/test/regress/po/es.po
 create mode 100644 src/test/regress/po/meson.build
 create mode 100644 src/test/regress/sql/nls.sql

diff --git a/src/test/regress/expected/nls.out b/src/test/regress/expected/nls.out
new file mode 100644
index 00000000000..32ef23aa057
--- /dev/null
+++ b/src/test/regress/expected/nls.out
@@ -0,0 +1,37 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+-- Some BSDen are sticky about wanting a codeset in lc_messages.
+-- Try to extract it from pg_database.datctype.
+SELECT 'es_ES' || coalesce(regexp_substr(datctype, '\..*'), '') AS es_locale
+  FROM pg_database WHERE datname = current_database()
+\gset
+SET lc_messages = :'es_locale';
+SELECT test_translation();
+NOTICE:  traducido PRId64 = 424242424242
+NOTICE:  traducido PRId32 = -1234
+NOTICE:  traducido PRIdMAX = -5678
+NOTICE:  traducido PRIdPTR = 9999
+NOTICE:  traducido PRIu64 = 424242424242
+NOTICE:  traducido PRIu32 = 1234
+NOTICE:  traducido PRIuMAX = 5678
+NOTICE:  traducido PRIuPTR = 9999
+NOTICE:  traducido PRIx64 = 62c6d1a9b2
+NOTICE:  traducido PRIx32 = 4d2
+NOTICE:  traducido PRIxMAX = 162e
+NOTICE:  traducido PRIxPTR = 270f
+NOTICE:  traducido PRIX64 = 62C6D1A9B2
+NOTICE:  traducido PRIX32 = 4D2
+NOTICE:  traducido PRIXMAX = 162E
+NOTICE:  traducido PRIXPTR = 270F
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/expected/nls_1.out b/src/test/regress/expected/nls_1.out
new file mode 100644
index 00000000000..6bebf9bb2ef
--- /dev/null
+++ b/src/test/regress/expected/nls_1.out
@@ -0,0 +1,22 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+-- Some BSDen are sticky about wanting a codeset in lc_messages.
+-- Try to extract it from pg_database.datctype.
+SELECT 'es_ES' || coalesce(regexp_substr(datctype, '\..*'), '') AS es_locale
+  FROM pg_database WHERE datname = current_database()
+\gset
+SET lc_messages = :'es_locale';
+SELECT test_translation();
+NOTICE:  NLS is not enabled
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index 1da9e9462a9..4001a81ffe5 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -57,3 +57,5 @@ tests += {
     'dbname': 'regression',
   },
 }
+
+subdir('po', if_found: libintl)
diff --git a/src/test/regress/nls.mk b/src/test/regress/nls.mk
new file mode 100644
index 00000000000..43227c64f09
--- /dev/null
+++ b/src/test/regress/nls.mk
@@ -0,0 +1,5 @@
+# src/test/regress/nls.mk
+CATALOG_NAME     = regress
+GETTEXT_FILES    = regress.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_FLAGS    = $(BACKEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0931f1dcccf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -76,7 +76,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan
tidrangescancollate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions nls sysviews tsrf tid
tidscantidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 

 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
diff --git a/src/test/regress/po/LINGUAS b/src/test/regress/po/LINGUAS
new file mode 100644
index 00000000000..8357fcaaed4
--- /dev/null
+++ b/src/test/regress/po/LINGUAS
@@ -0,0 +1 @@
+es
diff --git a/src/test/regress/po/es.po b/src/test/regress/po/es.po
new file mode 100644
index 00000000000..b3021d57e22
--- /dev/null
+++ b/src/test/regress/po/es.po
@@ -0,0 +1,159 @@
+# Spanish message translation file for regress test library
+#
+# Copyright (C) 2025 PostgreSQL Global Development Group
+# This file is distributed under the same license as the regress (PostgreSQL) package.
+#
+# Tom Lane <tgl@sss.pgh.pa.us>, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: regress (PostgreSQL) 19\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2025-12-08 13:57-0500\n"
+"PO-Revision-Date: 2025-11-19 19:01-0500\n"
+"Last-Translator: Tom Lane <tgl@sss.pgh.pa.us>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: regress.c:202
+#, c-format
+msgid "invalid input syntax for type %s: \"%s\""
+msgstr "la sintaxis de entrada no es válida para tipo %s: «%s»"
+
+#: regress.c:839
+#, c-format
+msgid "test_inline_in_from_support_func called with %d args but expected 3"
+msgstr ""
+
+#: regress.c:847 regress.c:863
+#, c-format
+msgid "test_inline_in_from_support_func called with non-Const parameters"
+msgstr ""
+
+#: regress.c:854 regress.c:870
+#, c-format
+msgid "test_inline_in_from_support_func called with non-TEXT parameters"
+msgstr ""
+
+#: regress.c:903
+#, c-format
+msgid "test_inline_in_from_support_func parsed to more than one node"
+msgstr ""
+
+#: regress.c:914
+#, c-format
+msgid "test_inline_in_from_support_func rewrote to more than one node"
+msgstr ""
+
+#: regress.c:921
+#, c-format
+msgid "test_inline_in_from_support_func didn't parse to a Query"
+msgstr ""
+
+#: regress.c:1028
+#, c-format
+msgid "invalid source encoding name \"%s\""
+msgstr "la codificación de origen «%s» no es válida"
+
+#: regress.c:1033
+#, c-format
+msgid "invalid destination encoding name \"%s\""
+msgstr "la codificación de destino «%s» no es válida"
+
+#: regress.c:1078
+#, c-format
+msgid "default conversion function for encoding \"%s\" to \"%s\" does not exist"
+msgstr "no existe el procedimiento por omisión de conversión desde la codificación «%s» a «%s»"
+
+#: regress.c:1085
+#, c-format
+msgid "out of memory"
+msgstr "memoria agotada"
+
+#: regress.c:1086
+#, c-format
+msgid "String of %d bytes is too long for encoding conversion."
+msgstr "La cadena de %d bytes es demasiado larga para la recodificación."
+
+#: regress.c:1175
+#, c-format
+msgid "translated PRId64 = %<PRId64>"
+msgstr "traducido PRId64 = %<PRId64>"
+
+#: regress.c:1177
+#, c-format
+msgid "translated PRId32 = %<PRId32>"
+msgstr "traducido PRId32 = %<PRId32>"
+
+#: regress.c:1179
+#, c-format
+msgid "translated PRIdMAX = %<PRIdMAX>"
+msgstr "traducido PRIdMAX = %<PRIdMAX>"
+
+#: regress.c:1181
+#, c-format
+msgid "translated PRIdPTR = %<PRIdPTR>"
+msgstr "traducido PRIdPTR = %<PRIdPTR>"
+
+#: regress.c:1184
+#, c-format
+msgid "translated PRIu64 = %<PRIu64>"
+msgstr "traducido PRIu64 = %<PRIu64>"
+
+#: regress.c:1186
+#, c-format
+msgid "translated PRIu32 = %<PRIu32>"
+msgstr "traducido PRIu32 = %<PRIu32>"
+
+#: regress.c:1188
+#, c-format
+msgid "translated PRIuMAX = %<PRIuMAX>"
+msgstr "traducido PRIuMAX = %<PRIuMAX>"
+
+#: regress.c:1190
+#, c-format
+msgid "translated PRIuPTR = %<PRIuPTR>"
+msgstr "traducido PRIuPTR = %<PRIuPTR>"
+
+#: regress.c:1193
+#, c-format
+msgid "translated PRIx64 = %<PRIx64>"
+msgstr "traducido PRIx64 = %<PRIx64>"
+
+#: regress.c:1195
+#, c-format
+msgid "translated PRIx32 = %<PRIx32>"
+msgstr "traducido PRIx32 = %<PRIx32>"
+
+#: regress.c:1197
+#, c-format
+msgid "translated PRIxMAX = %<PRIxMAX>"
+msgstr "traducido PRIxMAX = %<PRIxMAX>"
+
+#: regress.c:1199
+#, c-format
+msgid "translated PRIxPTR = %<PRIxPTR>"
+msgstr "traducido PRIxPTR = %<PRIxPTR>"
+
+#: regress.c:1202
+#, c-format
+msgid "translated PRIX64 = %<PRIX64>"
+msgstr "traducido PRIX64 = %<PRIX64>"
+
+#: regress.c:1204
+#, c-format
+msgid "translated PRIX32 = %<PRIX32>"
+msgstr "traducido PRIX32 = %<PRIX32>"
+
+#: regress.c:1206
+#, c-format
+msgid "translated PRIXMAX = %<PRIXMAX>"
+msgstr "traducido PRIXMAX = %<PRIXMAX>"
+
+#: regress.c:1208
+#, c-format
+msgid "translated PRIXPTR = %<PRIXPTR>"
+msgstr "traducido PRIXPTR = %<PRIXPTR>"
diff --git a/src/test/regress/po/meson.build b/src/test/regress/po/meson.build
new file mode 100644
index 00000000000..e9bd964aa7f
--- /dev/null
+++ b/src/test/regress/po/meson.build
@@ -0,0 +1,3 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+nls_targets += [i18n.gettext('regress-' + pg_version_major.to_string())]
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 56cc0567b1c..2d5d799f6e4 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -48,6 +48,10 @@
 #include "utils/rel.h"
 #include "utils/typcache.h"

+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("regress")
+
 #define EXPECT_TRUE(expr)    \
     do { \
         if (!(expr)) \
@@ -1149,3 +1153,62 @@ test_relpath(PG_FUNCTION_ARGS)

     PG_RETURN_VOID();
 }
+
+/*
+ * Simple test to verify NLS support, particularly that the PRI* macros work.
+ */
+PG_FUNCTION_INFO_V1(test_translation);
+Datum
+test_translation(PG_FUNCTION_ARGS)
+{
+#ifdef ENABLE_NLS
+    /* This would be better done in _PG_init(), if this module had one */
+    static bool inited = false;
+
+    if (!inited)
+    {
+        pg_bindtextdomain(TEXTDOMAIN);
+        inited = true;
+    }
+
+    ereport(NOTICE,
+            errmsg("translated PRId64 = %" PRId64, (int64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRId32 = %" PRId32, (int32) -1234));
+    ereport(NOTICE,
+            errmsg("translated PRIdMAX = %" PRIdMAX, (intmax_t) -5678));
+    ereport(NOTICE,
+            errmsg("translated PRIdPTR = %" PRIdPTR, (intptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIu64 = %" PRIu64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIu32 = %" PRIu32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIuMAX = %" PRIuMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIuPTR = %" PRIuPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIx64 = %" PRIx64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIx32 = %" PRIx32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIxMAX = %" PRIxMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIxPTR = %" PRIxPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIX64 = %" PRIX64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIX32 = %" PRIX32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIXMAX = %" PRIXMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIXPTR = %" PRIXPTR, (uintptr_t) 9999));
+#else
+    elog(NOTICE, "NLS is not enabled");
+#endif
+
+    PG_RETURN_VOID();
+}
diff --git a/src/test/regress/sql/nls.sql b/src/test/regress/sql/nls.sql
new file mode 100644
index 00000000000..d6f7954d61e
--- /dev/null
+++ b/src/test/regress/sql/nls.sql
@@ -0,0 +1,21 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+
+\set regresslib :libdir '/regress' :dlsuffix
+
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+
+-- Some BSDen are sticky about wanting a codeset in lc_messages.
+-- Try to extract it from pg_database.datctype.
+SELECT 'es_ES' || coalesce(regexp_substr(datctype, '\..*'), '') AS es_locale
+  FROM pg_database WHERE datname = current_database()
+\gset
+SET lc_messages = :'es_locale';
+
+SELECT test_translation();
+
+RESET lc_messages;
--
2.43.7

From 69194b1e9d391a3717c4eabff7de0b5dab932aae Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 8 Dec 2025 18:19:59 -0500
Subject: [PATCH v4 2/2] Support "j" length modifier in snprintf.c.

POSIX has for a long time defined the "j" length modifier for
printf conversions as meaning the size of intmax_t or uintmax_t.
We got away without supporting that so far, but there are good
reasons to start doing so now:

* On some platforms, <inttypes.h> defines PRIdMAX as "jd",
so that snprintf.c falls over if that is used.

* Commit e6be84356 re-introduced upstream's use of PRIdMAX
into zic.c.  (We hadn't noticed yet because it would only
become apparent if bad data was fed to zic, resulting in
an error report.)

We could revert that decision from our copy of zic.c, but
on the whole it seems better to update snprintf.c to support
this standard modifier.  There might well be extensions,
now or in future, that expect it to work.

I did this in the lazy man's way of translating "j" to either
"l" or "ll" depending on a compile-time sizeof() check, just
as was done long ago to support "z" for size_t.  One could
imagine promoting intmax_t to have full support in snprintf.c,
for example converting fmtint()'s value argument and internal
arithmetic to use [u]intmax_t not [unsigned] long long.  But
that'd be more work and I'm hesitant to do it anyway: if there
are any platforms out there where intmax_t is actually wider
than "long long", this would doubtless result in a noticeable
speed penalty to snprintf().  Let's not go there until we have
positive evidence that there's a reason to, and some way to
measure what size of penalty we're taking.
---
 configure                  | 33 +++++++++++++++++++++++++++++++++
 configure.ac               |  1 +
 meson.build                |  1 +
 src/include/pg_config.h.in |  3 +++
 src/port/snprintf.c        | 18 ++++++++++++++++++
 5 files changed, 56 insertions(+)

diff --git a/configure b/configure
index 3a0ed11fa8e..aaabe2aff70 100755
--- a/configure
+++ b/configure
@@ -16811,6 +16811,39 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF


+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of intmax_t" >&5
+$as_echo_n "checking size of intmax_t... " >&6; }
+if ${ac_cv_sizeof_intmax_t+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (intmax_t))" "ac_cv_sizeof_intmax_t"
"$ac_includes_default";then : 
+
+else
+  if test "$ac_cv_type_intmax_t" = yes; then
+     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (intmax_t)
+See \`config.log' for more details" "$LINENO" 5; }
+   else
+     ac_cv_sizeof_intmax_t=0
+   fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_intmax_t" >&5
+$as_echo "$ac_cv_sizeof_intmax_t" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_INTMAX_T $ac_cv_sizeof_intmax_t
+_ACEOF
+
+

 # Determine memory alignment requirements for the basic C data types.

diff --git a/configure.ac b/configure.ac
index c2413720a18..c3c072e9ec7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1981,6 +1981,7 @@ AC_CHECK_SIZEOF([void *])
 AC_CHECK_SIZEOF([size_t])
 AC_CHECK_SIZEOF([long])
 AC_CHECK_SIZEOF([long long])
+AC_CHECK_SIZEOF([intmax_t])

 # Determine memory alignment requirements for the basic C data types.

diff --git a/meson.build b/meson.build
index 6e7ddd74683..6a9c70bede6 100644
--- a/meson.build
+++ b/meson.build
@@ -1775,6 +1775,7 @@ cdata.set('SIZEOF_LONG', cc.sizeof('long', args: test_c_args))
 cdata.set('SIZEOF_LONG_LONG', cc.sizeof('long long', args: test_c_args))
 cdata.set('SIZEOF_VOID_P', cc.sizeof('void *', args: test_c_args))
 cdata.set('SIZEOF_SIZE_T', cc.sizeof('size_t', args: test_c_args))
+cdata.set('SIZEOF_INTMAX_T', cc.sizeof('intmax_t', args: test_c_args))


 # Check if __int128 is a working 128 bit integer type, and if so
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index b0b0cfdaf79..72434ce957e 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -645,6 +645,9 @@
    RELSEG_SIZE requires an initdb. */
 #undef RELSEG_SIZE

+/* The size of `intmax_t', as computed by sizeof. */
+#undef SIZEOF_INTMAX_T
+
 /* The size of `long', as computed by sizeof. */
 #undef SIZEOF_LONG

diff --git a/src/port/snprintf.c b/src/port/snprintf.c
index 6541182df6d..d914547fae2 100644
--- a/src/port/snprintf.c
+++ b/src/port/snprintf.c
@@ -563,6 +563,15 @@ nextch2:
                 else
                     longflag = 1;
                 goto nextch2;
+            case 'j':
+#if SIZEOF_INTMAX_T == SIZEOF_LONG
+                longflag = 1;
+#elif SIZEOF_INTMAX_T == SIZEOF_LONG_LONG
+                longlongflag = 1;
+#else
+#error "cannot find integer type of the same size as intmax_t"
+#endif
+                goto nextch2;
             case 'z':
 #if SIZEOF_SIZE_T == SIZEOF_LONG
                 longflag = 1;
@@ -826,6 +835,15 @@ nextch1:
                 else
                     longflag = 1;
                 goto nextch1;
+            case 'j':
+#if SIZEOF_INTMAX_T == SIZEOF_LONG
+                longflag = 1;
+#elif SIZEOF_INTMAX_T == SIZEOF_LONG_LONG
+                longlongflag = 1;
+#else
+#error "cannot find integer type of the same size as intmax_t"
+#endif
+                goto nextch1;
             case 'z':
 #if SIZEOF_SIZE_T == SIZEOF_LONG
                 longflag = 1;
--
2.43.7


Re: Let's add a test for NLS translation of PRI* macros

От
Tom Lane
Дата:
Sigh ... I should know better than to assume meson code will work
without testing it.  One-line fix in v5-0002.

            regards, tom lane

From 672e889c76194985c9c325b3500c26e0aaaa583f Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 8 Dec 2025 17:40:00 -0500
Subject: [PATCH v5 1/2] Add a regression test to verify that NLS translation
 works.

We've never actually had a formal test for this facility.
It seems worth adding one now, mainly because we are starting
to depend on gettext() being able to handle the PRI* macros
from <inttypes.h>, and it's not all that certain that that
works everywhere.  So the test goes to a bit of effort to
check all the PRI* macros we are likely to use.
---
 src/test/regress/expected/nls.out   |  37 +++++++
 src/test/regress/expected/nls_1.out |  22 ++++
 src/test/regress/meson.build        |   2 +
 src/test/regress/nls.mk             |   5 +
 src/test/regress/parallel_schedule  |   2 +-
 src/test/regress/po/LINGUAS         |   1 +
 src/test/regress/po/es.po           | 159 ++++++++++++++++++++++++++++
 src/test/regress/po/meson.build     |   3 +
 src/test/regress/regress.c          |  63 +++++++++++
 src/test/regress/sql/nls.sql        |  21 ++++
 10 files changed, 314 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/nls.out
 create mode 100644 src/test/regress/expected/nls_1.out
 create mode 100644 src/test/regress/nls.mk
 create mode 100644 src/test/regress/po/LINGUAS
 create mode 100644 src/test/regress/po/es.po
 create mode 100644 src/test/regress/po/meson.build
 create mode 100644 src/test/regress/sql/nls.sql

diff --git a/src/test/regress/expected/nls.out b/src/test/regress/expected/nls.out
new file mode 100644
index 00000000000..32ef23aa057
--- /dev/null
+++ b/src/test/regress/expected/nls.out
@@ -0,0 +1,37 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+-- Some BSDen are sticky about wanting a codeset in lc_messages.
+-- Try to extract it from pg_database.datctype.
+SELECT 'es_ES' || coalesce(regexp_substr(datctype, '\..*'), '') AS es_locale
+  FROM pg_database WHERE datname = current_database()
+\gset
+SET lc_messages = :'es_locale';
+SELECT test_translation();
+NOTICE:  traducido PRId64 = 424242424242
+NOTICE:  traducido PRId32 = -1234
+NOTICE:  traducido PRIdMAX = -5678
+NOTICE:  traducido PRIdPTR = 9999
+NOTICE:  traducido PRIu64 = 424242424242
+NOTICE:  traducido PRIu32 = 1234
+NOTICE:  traducido PRIuMAX = 5678
+NOTICE:  traducido PRIuPTR = 9999
+NOTICE:  traducido PRIx64 = 62c6d1a9b2
+NOTICE:  traducido PRIx32 = 4d2
+NOTICE:  traducido PRIxMAX = 162e
+NOTICE:  traducido PRIxPTR = 270f
+NOTICE:  traducido PRIX64 = 62C6D1A9B2
+NOTICE:  traducido PRIX32 = 4D2
+NOTICE:  traducido PRIXMAX = 162E
+NOTICE:  traducido PRIXPTR = 270F
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/expected/nls_1.out b/src/test/regress/expected/nls_1.out
new file mode 100644
index 00000000000..6bebf9bb2ef
--- /dev/null
+++ b/src/test/regress/expected/nls_1.out
@@ -0,0 +1,22 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+-- Some BSDen are sticky about wanting a codeset in lc_messages.
+-- Try to extract it from pg_database.datctype.
+SELECT 'es_ES' || coalesce(regexp_substr(datctype, '\..*'), '') AS es_locale
+  FROM pg_database WHERE datname = current_database()
+\gset
+SET lc_messages = :'es_locale';
+SELECT test_translation();
+NOTICE:  NLS is not enabled
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index 1da9e9462a9..4001a81ffe5 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -57,3 +57,5 @@ tests += {
     'dbname': 'regression',
   },
 }
+
+subdir('po', if_found: libintl)
diff --git a/src/test/regress/nls.mk b/src/test/regress/nls.mk
new file mode 100644
index 00000000000..43227c64f09
--- /dev/null
+++ b/src/test/regress/nls.mk
@@ -0,0 +1,5 @@
+# src/test/regress/nls.mk
+CATALOG_NAME     = regress
+GETTEXT_FILES    = regress.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_FLAGS    = $(BACKEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0931f1dcccf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -76,7 +76,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan
tidrangescancollate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions nls sysviews tsrf tid
tidscantidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 

 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
diff --git a/src/test/regress/po/LINGUAS b/src/test/regress/po/LINGUAS
new file mode 100644
index 00000000000..8357fcaaed4
--- /dev/null
+++ b/src/test/regress/po/LINGUAS
@@ -0,0 +1 @@
+es
diff --git a/src/test/regress/po/es.po b/src/test/regress/po/es.po
new file mode 100644
index 00000000000..b3021d57e22
--- /dev/null
+++ b/src/test/regress/po/es.po
@@ -0,0 +1,159 @@
+# Spanish message translation file for regress test library
+#
+# Copyright (C) 2025 PostgreSQL Global Development Group
+# This file is distributed under the same license as the regress (PostgreSQL) package.
+#
+# Tom Lane <tgl@sss.pgh.pa.us>, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: regress (PostgreSQL) 19\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2025-12-08 13:57-0500\n"
+"PO-Revision-Date: 2025-11-19 19:01-0500\n"
+"Last-Translator: Tom Lane <tgl@sss.pgh.pa.us>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: regress.c:202
+#, c-format
+msgid "invalid input syntax for type %s: \"%s\""
+msgstr "la sintaxis de entrada no es válida para tipo %s: «%s»"
+
+#: regress.c:839
+#, c-format
+msgid "test_inline_in_from_support_func called with %d args but expected 3"
+msgstr ""
+
+#: regress.c:847 regress.c:863
+#, c-format
+msgid "test_inline_in_from_support_func called with non-Const parameters"
+msgstr ""
+
+#: regress.c:854 regress.c:870
+#, c-format
+msgid "test_inline_in_from_support_func called with non-TEXT parameters"
+msgstr ""
+
+#: regress.c:903
+#, c-format
+msgid "test_inline_in_from_support_func parsed to more than one node"
+msgstr ""
+
+#: regress.c:914
+#, c-format
+msgid "test_inline_in_from_support_func rewrote to more than one node"
+msgstr ""
+
+#: regress.c:921
+#, c-format
+msgid "test_inline_in_from_support_func didn't parse to a Query"
+msgstr ""
+
+#: regress.c:1028
+#, c-format
+msgid "invalid source encoding name \"%s\""
+msgstr "la codificación de origen «%s» no es válida"
+
+#: regress.c:1033
+#, c-format
+msgid "invalid destination encoding name \"%s\""
+msgstr "la codificación de destino «%s» no es válida"
+
+#: regress.c:1078
+#, c-format
+msgid "default conversion function for encoding \"%s\" to \"%s\" does not exist"
+msgstr "no existe el procedimiento por omisión de conversión desde la codificación «%s» a «%s»"
+
+#: regress.c:1085
+#, c-format
+msgid "out of memory"
+msgstr "memoria agotada"
+
+#: regress.c:1086
+#, c-format
+msgid "String of %d bytes is too long for encoding conversion."
+msgstr "La cadena de %d bytes es demasiado larga para la recodificación."
+
+#: regress.c:1175
+#, c-format
+msgid "translated PRId64 = %<PRId64>"
+msgstr "traducido PRId64 = %<PRId64>"
+
+#: regress.c:1177
+#, c-format
+msgid "translated PRId32 = %<PRId32>"
+msgstr "traducido PRId32 = %<PRId32>"
+
+#: regress.c:1179
+#, c-format
+msgid "translated PRIdMAX = %<PRIdMAX>"
+msgstr "traducido PRIdMAX = %<PRIdMAX>"
+
+#: regress.c:1181
+#, c-format
+msgid "translated PRIdPTR = %<PRIdPTR>"
+msgstr "traducido PRIdPTR = %<PRIdPTR>"
+
+#: regress.c:1184
+#, c-format
+msgid "translated PRIu64 = %<PRIu64>"
+msgstr "traducido PRIu64 = %<PRIu64>"
+
+#: regress.c:1186
+#, c-format
+msgid "translated PRIu32 = %<PRIu32>"
+msgstr "traducido PRIu32 = %<PRIu32>"
+
+#: regress.c:1188
+#, c-format
+msgid "translated PRIuMAX = %<PRIuMAX>"
+msgstr "traducido PRIuMAX = %<PRIuMAX>"
+
+#: regress.c:1190
+#, c-format
+msgid "translated PRIuPTR = %<PRIuPTR>"
+msgstr "traducido PRIuPTR = %<PRIuPTR>"
+
+#: regress.c:1193
+#, c-format
+msgid "translated PRIx64 = %<PRIx64>"
+msgstr "traducido PRIx64 = %<PRIx64>"
+
+#: regress.c:1195
+#, c-format
+msgid "translated PRIx32 = %<PRIx32>"
+msgstr "traducido PRIx32 = %<PRIx32>"
+
+#: regress.c:1197
+#, c-format
+msgid "translated PRIxMAX = %<PRIxMAX>"
+msgstr "traducido PRIxMAX = %<PRIxMAX>"
+
+#: regress.c:1199
+#, c-format
+msgid "translated PRIxPTR = %<PRIxPTR>"
+msgstr "traducido PRIxPTR = %<PRIxPTR>"
+
+#: regress.c:1202
+#, c-format
+msgid "translated PRIX64 = %<PRIX64>"
+msgstr "traducido PRIX64 = %<PRIX64>"
+
+#: regress.c:1204
+#, c-format
+msgid "translated PRIX32 = %<PRIX32>"
+msgstr "traducido PRIX32 = %<PRIX32>"
+
+#: regress.c:1206
+#, c-format
+msgid "translated PRIXMAX = %<PRIXMAX>"
+msgstr "traducido PRIXMAX = %<PRIXMAX>"
+
+#: regress.c:1208
+#, c-format
+msgid "translated PRIXPTR = %<PRIXPTR>"
+msgstr "traducido PRIXPTR = %<PRIXPTR>"
diff --git a/src/test/regress/po/meson.build b/src/test/regress/po/meson.build
new file mode 100644
index 00000000000..e9bd964aa7f
--- /dev/null
+++ b/src/test/regress/po/meson.build
@@ -0,0 +1,3 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+nls_targets += [i18n.gettext('regress-' + pg_version_major.to_string())]
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 56cc0567b1c..2d5d799f6e4 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -48,6 +48,10 @@
 #include "utils/rel.h"
 #include "utils/typcache.h"

+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("regress")
+
 #define EXPECT_TRUE(expr)    \
     do { \
         if (!(expr)) \
@@ -1149,3 +1153,62 @@ test_relpath(PG_FUNCTION_ARGS)

     PG_RETURN_VOID();
 }
+
+/*
+ * Simple test to verify NLS support, particularly that the PRI* macros work.
+ */
+PG_FUNCTION_INFO_V1(test_translation);
+Datum
+test_translation(PG_FUNCTION_ARGS)
+{
+#ifdef ENABLE_NLS
+    /* This would be better done in _PG_init(), if this module had one */
+    static bool inited = false;
+
+    if (!inited)
+    {
+        pg_bindtextdomain(TEXTDOMAIN);
+        inited = true;
+    }
+
+    ereport(NOTICE,
+            errmsg("translated PRId64 = %" PRId64, (int64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRId32 = %" PRId32, (int32) -1234));
+    ereport(NOTICE,
+            errmsg("translated PRIdMAX = %" PRIdMAX, (intmax_t) -5678));
+    ereport(NOTICE,
+            errmsg("translated PRIdPTR = %" PRIdPTR, (intptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIu64 = %" PRIu64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIu32 = %" PRIu32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIuMAX = %" PRIuMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIuPTR = %" PRIuPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIx64 = %" PRIx64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIx32 = %" PRIx32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIxMAX = %" PRIxMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIxPTR = %" PRIxPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIX64 = %" PRIX64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIX32 = %" PRIX32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIXMAX = %" PRIXMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIXPTR = %" PRIXPTR, (uintptr_t) 9999));
+#else
+    elog(NOTICE, "NLS is not enabled");
+#endif
+
+    PG_RETURN_VOID();
+}
diff --git a/src/test/regress/sql/nls.sql b/src/test/regress/sql/nls.sql
new file mode 100644
index 00000000000..d6f7954d61e
--- /dev/null
+++ b/src/test/regress/sql/nls.sql
@@ -0,0 +1,21 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+
+\set regresslib :libdir '/regress' :dlsuffix
+
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+
+-- Some BSDen are sticky about wanting a codeset in lc_messages.
+-- Try to extract it from pg_database.datctype.
+SELECT 'es_ES' || coalesce(regexp_substr(datctype, '\..*'), '') AS es_locale
+  FROM pg_database WHERE datname = current_database()
+\gset
+SET lc_messages = :'es_locale';
+
+SELECT test_translation();
+
+RESET lc_messages;
--
2.43.7

From 70d6af4a15c09560888311d07de323c86dcb6625 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 8 Dec 2025 18:19:59 -0500
Subject: [PATCH v5 2/2] Support "j" length modifier in snprintf.c.

POSIX has for a long time defined the "j" length modifier for
printf conversions as meaning the size of intmax_t or uintmax_t.
We got away without supporting that so far, but there are good
reasons to start doing so now:

* On some platforms, <inttypes.h> defines PRIdMAX as "jd",
so that snprintf.c falls over if that is used.

* Commit e6be84356 re-introduced upstream's use of PRIdMAX
into zic.c.  (We hadn't noticed yet because it would only
become apparent if bad data was fed to zic, resulting in
an error report.)

We could revert that decision from our copy of zic.c, but
on the whole it seems better to update snprintf.c to support
this standard modifier.  There might well be extensions,
now or in future, that expect it to work.

I did this in the lazy man's way of translating "j" to either
"l" or "ll" depending on a compile-time sizeof() check, just
as was done long ago to support "z" for size_t.  One could
imagine promoting intmax_t to have full support in snprintf.c,
for example converting fmtint()'s value argument and internal
arithmetic to use [u]intmax_t not [unsigned] long long.  But
that'd be more work and I'm hesitant to do it anyway: if there
are any platforms out there where intmax_t is actually wider
than "long long", this would doubtless result in a noticeable
speed penalty to snprintf().  Let's not go there until we have
positive evidence that there's a reason to, and some way to
measure what size of penalty we're taking.
---
 configure                  | 33 +++++++++++++++++++++++++++++++++
 configure.ac               |  1 +
 meson.build                |  2 ++
 src/include/pg_config.h.in |  3 +++
 src/port/snprintf.c        | 18 ++++++++++++++++++
 5 files changed, 57 insertions(+)

diff --git a/configure b/configure
index 3a0ed11fa8e..aaabe2aff70 100755
--- a/configure
+++ b/configure
@@ -16811,6 +16811,39 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF


+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of intmax_t" >&5
+$as_echo_n "checking size of intmax_t... " >&6; }
+if ${ac_cv_sizeof_intmax_t+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (intmax_t))" "ac_cv_sizeof_intmax_t"
"$ac_includes_default";then : 
+
+else
+  if test "$ac_cv_type_intmax_t" = yes; then
+     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (intmax_t)
+See \`config.log' for more details" "$LINENO" 5; }
+   else
+     ac_cv_sizeof_intmax_t=0
+   fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_intmax_t" >&5
+$as_echo "$ac_cv_sizeof_intmax_t" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_INTMAX_T $ac_cv_sizeof_intmax_t
+_ACEOF
+
+

 # Determine memory alignment requirements for the basic C data types.

diff --git a/configure.ac b/configure.ac
index c2413720a18..c3c072e9ec7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1981,6 +1981,7 @@ AC_CHECK_SIZEOF([void *])
 AC_CHECK_SIZEOF([size_t])
 AC_CHECK_SIZEOF([long])
 AC_CHECK_SIZEOF([long long])
+AC_CHECK_SIZEOF([intmax_t])

 # Determine memory alignment requirements for the basic C data types.

diff --git a/meson.build b/meson.build
index 6e7ddd74683..e01a77b6f2a 100644
--- a/meson.build
+++ b/meson.build
@@ -1775,6 +1775,8 @@ cdata.set('SIZEOF_LONG', cc.sizeof('long', args: test_c_args))
 cdata.set('SIZEOF_LONG_LONG', cc.sizeof('long long', args: test_c_args))
 cdata.set('SIZEOF_VOID_P', cc.sizeof('void *', args: test_c_args))
 cdata.set('SIZEOF_SIZE_T', cc.sizeof('size_t', args: test_c_args))
+cdata.set('SIZEOF_INTMAX_T', cc.sizeof('intmax_t', args: test_c_args,
+                                       prefix: '#include <stdint.h>'))


 # Check if __int128 is a working 128 bit integer type, and if so
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index b0b0cfdaf79..72434ce957e 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -645,6 +645,9 @@
    RELSEG_SIZE requires an initdb. */
 #undef RELSEG_SIZE

+/* The size of `intmax_t', as computed by sizeof. */
+#undef SIZEOF_INTMAX_T
+
 /* The size of `long', as computed by sizeof. */
 #undef SIZEOF_LONG

diff --git a/src/port/snprintf.c b/src/port/snprintf.c
index 6541182df6d..d914547fae2 100644
--- a/src/port/snprintf.c
+++ b/src/port/snprintf.c
@@ -563,6 +563,15 @@ nextch2:
                 else
                     longflag = 1;
                 goto nextch2;
+            case 'j':
+#if SIZEOF_INTMAX_T == SIZEOF_LONG
+                longflag = 1;
+#elif SIZEOF_INTMAX_T == SIZEOF_LONG_LONG
+                longlongflag = 1;
+#else
+#error "cannot find integer type of the same size as intmax_t"
+#endif
+                goto nextch2;
             case 'z':
 #if SIZEOF_SIZE_T == SIZEOF_LONG
                 longflag = 1;
@@ -826,6 +835,15 @@ nextch1:
                 else
                     longflag = 1;
                 goto nextch1;
+            case 'j':
+#if SIZEOF_INTMAX_T == SIZEOF_LONG
+                longflag = 1;
+#elif SIZEOF_INTMAX_T == SIZEOF_LONG_LONG
+                longlongflag = 1;
+#else
+#error "cannot find integer type of the same size as intmax_t"
+#endif
+                goto nextch1;
             case 'z':
 #if SIZEOF_SIZE_T == SIZEOF_LONG
                 longflag = 1;
--
2.43.7


Re: Let's add a test for NLS translation of PRI* macros

От
Tom Lane
Дата:
I wrote:
> v4-0001 attached tries to deal with #1 by extracting a codeset
> name from pg_database.datctype. That seems to work for me locally,
> but I'll be interested to see what cfbot thinks.

cfbot didn't like that :-(.  Upon further review, it seems that
the locale name needs to include a valid encoding spec, but that
encoding spec doesn't actually have to match the database encoding
(checked here on Linux, macOS, all three major BSDen).  Perhaps
there would be some issues with character set conversion if not,
but the test case I'm proposing is intentionally chosen to not
involve any non-ASCII characters, so it shouldn't matter.

Hence, new version attached that just hard-codes es_ES.UTF-8.
We'll see if cfbot agrees with my local testing.

(I went ahead and pushed 0002, since that seemed pretty
uncontroversial as well as fixing a demonstrated bug.)

            regards, tom lane

From 7f9808e99ad69f77410c82f22e229d0b14c9876c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 9 Dec 2025 12:01:17 -0500
Subject: [PATCH v6] Add a regression test to verify that NLS translation
 works.

We've never actually had a formal test for this facility.
It seems worth adding one now, mainly because we are starting
to depend on gettext() being able to handle the PRI* macros
from <inttypes.h>, and it's not all that certain that that
works everywhere.  So the test goes to a bit of effort to
check all the PRI* macros we are likely to use.

(This effort has indeed found one problem already, now fixed
in commit f8715ec86.)

Discussion: https://postgr.es/m/3098752.1765221796@sss.pgh.pa.us
---
 src/test/regress/expected/nls.out   |  35 ++++++
 src/test/regress/expected/nls_1.out |  20 ++++
 src/test/regress/meson.build        |   2 +
 src/test/regress/nls.mk             |   5 +
 src/test/regress/parallel_schedule  |   2 +-
 src/test/regress/po/LINGUAS         |   1 +
 src/test/regress/po/es.po           | 159 ++++++++++++++++++++++++++++
 src/test/regress/po/meson.build     |   3 +
 src/test/regress/regress.c          |  63 +++++++++++
 src/test/regress/sql/nls.sql        |  19 ++++
 10 files changed, 308 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/nls.out
 create mode 100644 src/test/regress/expected/nls_1.out
 create mode 100644 src/test/regress/nls.mk
 create mode 100644 src/test/regress/po/LINGUAS
 create mode 100644 src/test/regress/po/es.po
 create mode 100644 src/test/regress/po/meson.build
 create mode 100644 src/test/regress/sql/nls.sql

diff --git a/src/test/regress/expected/nls.out b/src/test/regress/expected/nls.out
new file mode 100644
index 00000000000..5a650294eaf
--- /dev/null
+++ b/src/test/regress/expected/nls.out
@@ -0,0 +1,35 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+-- Some BSDen are sticky about wanting a codeset name in lc_messages,
+-- but it seems that at least on common platforms it doesn't have
+-- to match the actual database encoding.
+SET lc_messages = 'es_ES.UTF-8';
+SELECT test_translation();
+NOTICE:  traducido PRId64 = 424242424242
+NOTICE:  traducido PRId32 = -1234
+NOTICE:  traducido PRIdMAX = -5678
+NOTICE:  traducido PRIdPTR = 9999
+NOTICE:  traducido PRIu64 = 424242424242
+NOTICE:  traducido PRIu32 = 1234
+NOTICE:  traducido PRIuMAX = 5678
+NOTICE:  traducido PRIuPTR = 9999
+NOTICE:  traducido PRIx64 = 62c6d1a9b2
+NOTICE:  traducido PRIx32 = 4d2
+NOTICE:  traducido PRIxMAX = 162e
+NOTICE:  traducido PRIxPTR = 270f
+NOTICE:  traducido PRIX64 = 62C6D1A9B2
+NOTICE:  traducido PRIX32 = 4D2
+NOTICE:  traducido PRIXMAX = 162E
+NOTICE:  traducido PRIXPTR = 270F
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/expected/nls_1.out b/src/test/regress/expected/nls_1.out
new file mode 100644
index 00000000000..9f1a2776e50
--- /dev/null
+++ b/src/test/regress/expected/nls_1.out
@@ -0,0 +1,20 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+-- Some BSDen are sticky about wanting a codeset name in lc_messages,
+-- but it seems that at least on common platforms it doesn't have
+-- to match the actual database encoding.
+SET lc_messages = 'es_ES.UTF-8';
+SELECT test_translation();
+NOTICE:  NLS is not enabled
+ test_translation
+------------------
+
+(1 row)
+
+RESET lc_messages;
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index 1da9e9462a9..4001a81ffe5 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -57,3 +57,5 @@ tests += {
     'dbname': 'regression',
   },
 }
+
+subdir('po', if_found: libintl)
diff --git a/src/test/regress/nls.mk b/src/test/regress/nls.mk
new file mode 100644
index 00000000000..43227c64f09
--- /dev/null
+++ b/src/test/regress/nls.mk
@@ -0,0 +1,5 @@
+# src/test/regress/nls.mk
+CATALOG_NAME     = regress
+GETTEXT_FILES    = regress.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_FLAGS    = $(BACKEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0931f1dcccf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -76,7 +76,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan
tidrangescancollate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions nls sysviews tsrf tid
tidscantidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual 

 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
diff --git a/src/test/regress/po/LINGUAS b/src/test/regress/po/LINGUAS
new file mode 100644
index 00000000000..8357fcaaed4
--- /dev/null
+++ b/src/test/regress/po/LINGUAS
@@ -0,0 +1 @@
+es
diff --git a/src/test/regress/po/es.po b/src/test/regress/po/es.po
new file mode 100644
index 00000000000..b3021d57e22
--- /dev/null
+++ b/src/test/regress/po/es.po
@@ -0,0 +1,159 @@
+# Spanish message translation file for regress test library
+#
+# Copyright (C) 2025 PostgreSQL Global Development Group
+# This file is distributed under the same license as the regress (PostgreSQL) package.
+#
+# Tom Lane <tgl@sss.pgh.pa.us>, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: regress (PostgreSQL) 19\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2025-12-08 13:57-0500\n"
+"PO-Revision-Date: 2025-11-19 19:01-0500\n"
+"Last-Translator: Tom Lane <tgl@sss.pgh.pa.us>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: regress.c:202
+#, c-format
+msgid "invalid input syntax for type %s: \"%s\""
+msgstr "la sintaxis de entrada no es válida para tipo %s: «%s»"
+
+#: regress.c:839
+#, c-format
+msgid "test_inline_in_from_support_func called with %d args but expected 3"
+msgstr ""
+
+#: regress.c:847 regress.c:863
+#, c-format
+msgid "test_inline_in_from_support_func called with non-Const parameters"
+msgstr ""
+
+#: regress.c:854 regress.c:870
+#, c-format
+msgid "test_inline_in_from_support_func called with non-TEXT parameters"
+msgstr ""
+
+#: regress.c:903
+#, c-format
+msgid "test_inline_in_from_support_func parsed to more than one node"
+msgstr ""
+
+#: regress.c:914
+#, c-format
+msgid "test_inline_in_from_support_func rewrote to more than one node"
+msgstr ""
+
+#: regress.c:921
+#, c-format
+msgid "test_inline_in_from_support_func didn't parse to a Query"
+msgstr ""
+
+#: regress.c:1028
+#, c-format
+msgid "invalid source encoding name \"%s\""
+msgstr "la codificación de origen «%s» no es válida"
+
+#: regress.c:1033
+#, c-format
+msgid "invalid destination encoding name \"%s\""
+msgstr "la codificación de destino «%s» no es válida"
+
+#: regress.c:1078
+#, c-format
+msgid "default conversion function for encoding \"%s\" to \"%s\" does not exist"
+msgstr "no existe el procedimiento por omisión de conversión desde la codificación «%s» a «%s»"
+
+#: regress.c:1085
+#, c-format
+msgid "out of memory"
+msgstr "memoria agotada"
+
+#: regress.c:1086
+#, c-format
+msgid "String of %d bytes is too long for encoding conversion."
+msgstr "La cadena de %d bytes es demasiado larga para la recodificación."
+
+#: regress.c:1175
+#, c-format
+msgid "translated PRId64 = %<PRId64>"
+msgstr "traducido PRId64 = %<PRId64>"
+
+#: regress.c:1177
+#, c-format
+msgid "translated PRId32 = %<PRId32>"
+msgstr "traducido PRId32 = %<PRId32>"
+
+#: regress.c:1179
+#, c-format
+msgid "translated PRIdMAX = %<PRIdMAX>"
+msgstr "traducido PRIdMAX = %<PRIdMAX>"
+
+#: regress.c:1181
+#, c-format
+msgid "translated PRIdPTR = %<PRIdPTR>"
+msgstr "traducido PRIdPTR = %<PRIdPTR>"
+
+#: regress.c:1184
+#, c-format
+msgid "translated PRIu64 = %<PRIu64>"
+msgstr "traducido PRIu64 = %<PRIu64>"
+
+#: regress.c:1186
+#, c-format
+msgid "translated PRIu32 = %<PRIu32>"
+msgstr "traducido PRIu32 = %<PRIu32>"
+
+#: regress.c:1188
+#, c-format
+msgid "translated PRIuMAX = %<PRIuMAX>"
+msgstr "traducido PRIuMAX = %<PRIuMAX>"
+
+#: regress.c:1190
+#, c-format
+msgid "translated PRIuPTR = %<PRIuPTR>"
+msgstr "traducido PRIuPTR = %<PRIuPTR>"
+
+#: regress.c:1193
+#, c-format
+msgid "translated PRIx64 = %<PRIx64>"
+msgstr "traducido PRIx64 = %<PRIx64>"
+
+#: regress.c:1195
+#, c-format
+msgid "translated PRIx32 = %<PRIx32>"
+msgstr "traducido PRIx32 = %<PRIx32>"
+
+#: regress.c:1197
+#, c-format
+msgid "translated PRIxMAX = %<PRIxMAX>"
+msgstr "traducido PRIxMAX = %<PRIxMAX>"
+
+#: regress.c:1199
+#, c-format
+msgid "translated PRIxPTR = %<PRIxPTR>"
+msgstr "traducido PRIxPTR = %<PRIxPTR>"
+
+#: regress.c:1202
+#, c-format
+msgid "translated PRIX64 = %<PRIX64>"
+msgstr "traducido PRIX64 = %<PRIX64>"
+
+#: regress.c:1204
+#, c-format
+msgid "translated PRIX32 = %<PRIX32>"
+msgstr "traducido PRIX32 = %<PRIX32>"
+
+#: regress.c:1206
+#, c-format
+msgid "translated PRIXMAX = %<PRIXMAX>"
+msgstr "traducido PRIXMAX = %<PRIXMAX>"
+
+#: regress.c:1208
+#, c-format
+msgid "translated PRIXPTR = %<PRIXPTR>"
+msgstr "traducido PRIXPTR = %<PRIXPTR>"
diff --git a/src/test/regress/po/meson.build b/src/test/regress/po/meson.build
new file mode 100644
index 00000000000..e9bd964aa7f
--- /dev/null
+++ b/src/test/regress/po/meson.build
@@ -0,0 +1,3 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+nls_targets += [i18n.gettext('regress-' + pg_version_major.to_string())]
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index c27305cf10b..83f61e0038e 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -48,6 +48,10 @@
 #include "utils/rel.h"
 #include "utils/typcache.h"

+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("regress")
+
 #define EXPECT_TRUE(expr)    \
     do { \
         if (!(expr)) \
@@ -1149,3 +1153,62 @@ test_relpath(PG_FUNCTION_ARGS)

     PG_RETURN_VOID();
 }
+
+/*
+ * Simple test to verify NLS support, particularly that the PRI* macros work.
+ */
+PG_FUNCTION_INFO_V1(test_translation);
+Datum
+test_translation(PG_FUNCTION_ARGS)
+{
+#ifdef ENABLE_NLS
+    /* This would be better done in _PG_init(), if this module had one */
+    static bool inited = false;
+
+    if (!inited)
+    {
+        pg_bindtextdomain(TEXTDOMAIN);
+        inited = true;
+    }
+
+    ereport(NOTICE,
+            errmsg("translated PRId64 = %" PRId64, (int64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRId32 = %" PRId32, (int32) -1234));
+    ereport(NOTICE,
+            errmsg("translated PRIdMAX = %" PRIdMAX, (intmax_t) -5678));
+    ereport(NOTICE,
+            errmsg("translated PRIdPTR = %" PRIdPTR, (intptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIu64 = %" PRIu64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIu32 = %" PRIu32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIuMAX = %" PRIuMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIuPTR = %" PRIuPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIx64 = %" PRIx64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIx32 = %" PRIx32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIxMAX = %" PRIxMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIxPTR = %" PRIxPTR, (uintptr_t) 9999));
+
+    ereport(NOTICE,
+            errmsg("translated PRIX64 = %" PRIX64, (uint64) 424242424242));
+    ereport(NOTICE,
+            errmsg("translated PRIX32 = %" PRIX32, (uint32) 1234));
+    ereport(NOTICE,
+            errmsg("translated PRIXMAX = %" PRIXMAX, (uintmax_t) 5678));
+    ereport(NOTICE,
+            errmsg("translated PRIXPTR = %" PRIXPTR, (uintptr_t) 9999));
+#else
+    elog(NOTICE, "NLS is not enabled");
+#endif
+
+    PG_RETURN_VOID();
+}
diff --git a/src/test/regress/sql/nls.sql b/src/test/regress/sql/nls.sql
new file mode 100644
index 00000000000..efeda8c5841
--- /dev/null
+++ b/src/test/regress/sql/nls.sql
@@ -0,0 +1,19 @@
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+
+\set regresslib :libdir '/regress' :dlsuffix
+
+CREATE FUNCTION test_translation()
+    RETURNS void
+    AS :'regresslib'
+    LANGUAGE C;
+
+-- Some BSDen are sticky about wanting a codeset name in lc_messages,
+-- but it seems that at least on common platforms it doesn't have
+-- to match the actual database encoding.
+SET lc_messages = 'es_ES.UTF-8';
+
+SELECT test_translation();
+
+RESET lc_messages;
--
2.43.7


Re: Let's add a test for NLS translation of PRI* macros

От
Tom Lane
Дата:
I wrote:
> Hence, new version attached that just hard-codes es_ES.UTF-8.
> We'll see if cfbot agrees with my local testing.

Yay, that passed CI.  However, I fear this proves little about
what would happen on Windows with NLS enabled, because per the
other thread we don't have NLS enabled in the CI Windows builds.

AFAICS we don't have it enabled in any of the Windows buildfarm
animals either, which may well mean that I could push v6 and
the buildfarm would stay green.  I'm a bit tempted to do that
and see what happens.  I don't have any way to make further
progress on this question here.

            regards, tom lane