On Mon, 2025-10-27 at 13:06 -0700, Jeff Davis wrote:
> On Mon, 2025-10-27 at 15:10 +0200, Álvaro Herrera wrote:
> > Hmm, interesting idea.  I think the most difficult part is
> > obtaining
> > the
> > source strings: we need to run your errno_translation.c program on
> > _all_
> > platforms,
> 
> I have attached .po files for the standard set of errnos (those
> recognized by strerror.c:get_errno_symbol()) on linux+glibc,
> linux+musl, freebsd, and mac.
> 
> Windows and solaris/illumos are missing, and perhaps some other
> variations too (e.g. the other BSDs).

Is this going in the right direction?

And generally, is NLS translation of system messages wanted at all, or
are ASCII messages more convenient anyway (given that it's just a
simple text representation of errno)?

If we don't actually want translation of the system messages, then do
we want to take the part of this patch that switches to the C locale,
so that it consistently uses ASCII messages across platforms?

The status quo seems like an awkward middle ground, where the system
messages are only translated on some platforms (perhaps only glibc?);
and whether they are translated or not is independent of whether
Postgres was compiled with NLS, which can lead to partially-translated
messages.

For instance, on linux/glibc if NLS is not enabled, you can end up with
messages like:

  ERROR:  could not open file "/etc/shadow" for reading: Permission non
accordée

AFAICT it makes zero sense to translate the errno message but not
translate the more interesting Postgres message.

> > Also we need a separate step to create the final postgres.po by
> > catenating the existing postgres.po with the new errstrings.po;
> > this
> > should not occur in the source tree but rather at install time,
> > because
> > of course pg_dump.po is going to have to do the same, and we don't
> > need
> > to make translators responsible for propagating translations from
> > one
> > file to others; that occurs already to a very small scale with the
> > src/common files and I hate it, so I wouldn't want to see it
> > happening
> > with this much larger set of strings.
> 
> I'm not familiar with the tooling in this area, but I can take a look
> into it. Would it affect packagers?

Would someone be willing to help here?

Attached new version; trivial rebase only.

> 
Regards,
        Jeff Davis

From 708b6d8c14b872bb3eb78eb51336ee767c6c31d4 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Wed, 22 Oct 2025 10:49:59 -0700
Subject: [PATCH v2] NLS: use gettext() to translate system error messages.

Previously, errors from the system such as "Permission denied"
(EACCES) relied on strerror_r() to perform translation; which has
different behavior from gettext(), which translates Postgres error
messages like "division by zero".

Disable translations inside of strerror_r() by temporarily switching
to the C locale, and instead perform the translations with gettext.

This makes translation of system error messages consistent across
platforms, respecting whether NLS is enabled or not. It also avoids
strerror_r()'s dependence on the global LC_CTYPE setting (gettext does
not rely on LC_CTYPE).

Creates a need to translate more messages -- one for each errno that
Postgres might plausibly encounter, or possibly a few more for
platform variations of the string representations.

Discussion: https://postgr.es/m/[email protected]
---
 src/port/snprintf.c | 73 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 71 insertions(+), 2 deletions(-)

diff --git a/src/port/snprintf.c b/src/port/snprintf.c
index d914547fae2..c1a96347e79 100644
--- a/src/port/snprintf.c
+++ b/src/port/snprintf.c
@@ -33,6 +33,9 @@
 
 #include "c.h"
 
+#ifdef HAVE_USELOCALE
+#include <locale.h>
+#endif
 #include <math.h>
 
 /*
@@ -161,6 +164,8 @@ typedef union
 
 static void flushbuffer(PrintfTarget *target);
 static void dopr(PrintfTarget *target, const char *format, va_list args);
+static char *c_strerror_r(int errnum, char *buf, size_t buflen);
+static char *nls_strerror_r(int errnum, char *buf, size_t buflen);
 
 
 /*
@@ -711,8 +716,8 @@ nextch2:
 			case 'm':
 				{
 					char		errbuf[PG_STRERROR_R_BUFLEN];
-					const char *errm = strerror_r(save_errno,
-												  errbuf, sizeof(errbuf));
+					const char *errm = nls_strerror_r(save_errno,
+													  errbuf, sizeof(errbuf));
 
 					dostr(errm, strlen(errm), target);
 				}
@@ -1513,3 +1518,67 @@ trailing_pad(int padlen, PrintfTarget *target)
 	if (padlen < 0)
 		dopr_outchmulti(' ', -padlen, target);
 }
+
+
+/*
+ * If NLS is enabled, translate the system error message. Otherwise, return
+ * the untranslated string.
+ */
+static char *
+nls_strerror_r(int errnum, char *buf, size_t buflen)
+{
+#ifdef ENABLE_NLS
+	char			 plain[PG_STRERROR_R_BUFLEN];
+	char			*msgid;
+	char			*msgstr;
+
+	/* run c_strerror_r to get plain untranslated string */
+	msgid = c_strerror_r(errnum, plain, PG_STRERROR_R_BUFLEN);
+
+	/* translate with gettext() and store in result buffer */
+	msgstr = _(msgid);
+	strlcpy(buf, msgstr, buflen);
+	return buf;
+#else
+	return c_strerror_r(errnum, buf, buflen);
+#endif
+}
+
+/*
+ * Temporarily switches to the C locale to ensure that strerror_r() returns an
+ * untranslated string.
+ *
+ * The purpose of this function is to avoid strerror_r() performing the
+ * translation itself, which has different behavior than gettext. In
+ * particular, strerror_r() may force the translated message into the ASCII
+ * character set if LC_CTYPE=C, even if the database encoding supports a wider
+ * character set (e.g. UTF-8). We also want to avoid translations when NLS is
+ * disabled.
+ */
+static char *
+c_strerror_r(int errnum, char *buf, size_t buflen)
+{
+#ifdef HAVE_USELOCALE
+	static locale_t	 c_locale = NULL;
+	char			*msgid;
+	locale_t		 save_loc;
+
+	if (!c_locale)
+		c_locale = newlocale(LC_ALL_MASK, "C", NULL);
+
+	save_loc = uselocale(c_locale);
+
+	msgid = strerror_r(errnum, buf, buflen);
+
+	if (save_loc != NULL)
+		uselocale(save_loc);
+
+	return msgid;
+#else
+	/*
+	 * Platforms lacking uselocale() have not been observed to translate
+	 * messages inside strerror_r().
+	 */
+	return strerror_r(errnum, buf, buflen);
+#endif
+}
-- 
2.43.0

Reply via email to