From 967a540348b4e92608c24125b4bd840042eb80ec Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 10 Oct 2025 14:16:56 +0900
Subject: [PATCH v5 2/2] Avoid double translation of messages for invalid
 primary_slot_name.

When primary_slot_name is invalid, ReplicationSlotValidateNameInternal()
returns already translated error messages (when NLS is enabled). Previously,
the check hook for primary_slot_name passed these messages to
GUC_check_errdetail() and GUC_check_errhint(), which could retranslate
them and cause double translation.

To fix this, this commit adds GUC_check_errmsg_internal(),
GUC_check_errdetail_internal(), and GUC_check_errhint_internal().
These work like their counterparts but skip translation. The check hook
for primary_slot_name now uses these new functions when handling
messages from ReplicationSlotValidateNameInternal().
---
 src/backend/access/transam/xlogrecovery.c | 10 ++++++--
 src/backend/utils/error/elog.c            | 31 +++++++++++++++++++++++
 src/include/utils/elog.h                  |  1 +
 src/include/utils/guc.h                   | 18 +++++++++++++
 4 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 3e3c4da01a2..51374194130 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4769,10 +4769,16 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
 		!ReplicationSlotValidateNameInternal(*newval, false, &err_code,
 											 &err_msg, &err_hint))
 	{
+		/*
+		 * Use GUC_check_errdetail_internal() and GUC_check_errhint_internal()
+		 * instead of GUC_check_errdetail() and GUC_check_errhint(), since the
+		 * messages from ReplicationSlotValidateNameInternal() are already
+		 * translated. This avoids double translation.
+		 */
 		GUC_check_errcode(err_code);
-		GUC_check_errdetail("%s", err_msg);
+		GUC_check_errdetail_internal("%s", err_msg);
 		if (err_hint != NULL)
-			GUC_check_errhint("%s", err_hint);
+			GUC_check_errhint_internal("%s", err_hint);
 		return false;
 	}
 
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 648d2d2e70c..33b4d4d70cc 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -1692,6 +1692,37 @@ format_elog_string(const char *fmt,...)
 	return edata->message;
 }
 
+/*
+ * This is exactly like format_elog_string() except that strings passed to
+ * format_elog_string_internal are not translated, and are customarily left
+ * out of the internationalization message dictionary. This should be used
+ * when the passed strings have already been translated.
+ */
+char *
+format_elog_string_internal(const char *fmt,...)
+{
+	ErrorData	errdata;
+	ErrorData  *edata;
+	MemoryContext oldcontext;
+
+	/* Initialize a mostly-dummy error frame */
+	edata = &errdata;
+	MemSet(edata, 0, sizeof(ErrorData));
+	/* the default text domain is the backend's */
+	edata->domain = save_format_domain ? save_format_domain : PG_TEXTDOMAIN("postgres");
+	/* set the errno to be used to interpret %m */
+	edata->saved_errno = save_format_errnumber;
+
+	oldcontext = MemoryContextSwitchTo(ErrorContext);
+
+	edata->message_id = fmt;
+	EVALUATE_MESSAGE(edata->domain, message, false, false);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return edata->message;
+}
+
 
 /*
  * Actual output of the top-of-stack error message
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 348dafbf906..57a57d2b027 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -302,6 +302,7 @@ extern void errsave_finish(struct Node *context,
 
 extern void pre_format_elog_string(int errnumber, const char *domain);
 extern char *format_elog_string(const char *fmt,...) pg_attribute_printf(1, 2);
+extern char *format_elog_string_internal(const char *fmt,...) pg_attribute_printf(1, 2);
 
 
 /* Support for attaching context information to error reports */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..af9f64c93ea 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -510,4 +510,22 @@ extern void GUC_check_errcode(int sqlerrcode);
 	pre_format_elog_string(errno, TEXTDOMAIN), \
 	GUC_check_errhint_string = format_elog_string
 
+/*
+ * These are exactly like GUC_check_errmsg/errdtail/errhint except that
+ * strings passed to them are not translated, and are customarily left
+ * out of the internationalization message dictionary. This should be used
+ * when the passed strings have already been translated.
+ */
+#define GUC_check_errmsg_internal \
+	pre_format_elog_string(errno, TEXTDOMAIN), \
+	GUC_check_errmsg_string = format_elog_string_internal
+
+#define GUC_check_errdetail_internal \
+	pre_format_elog_string(errno, TEXTDOMAIN), \
+	GUC_check_errdetail_string = format_elog_string_internal
+
+#define GUC_check_errhint_internal \
+	pre_format_elog_string(errno, TEXTDOMAIN), \
+	GUC_check_errhint_string = format_elog_string_internal
+
 #endif							/* GUC_H */
-- 
2.50.1

