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 <[email protected]>
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/[email protected]
---
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 tidrangescan collate.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 tidscan tidrangescan 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 <[email protected]>, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: regress (PostgreSQL) 19\n"
+"Report-Msgid-Bugs-To: [email protected]\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 <[email protected]>\n"
+"Language-Team: PgSQL-es-Ayuda <[email protected]>\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