diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index e78e59ad9000b72a4c74d145ff98e2febdca0c72..fe581f39be930364d4e43188c94d13958afa57db 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/sources.sgml,v 2.31 2008/09/07 02:01:04 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/sources.sgml,v 2.32 2008/10/27 19:37:21 tgl Exp $ -->
 
  <chapter id="source">
   <title>PostgreSQL Coding Conventions</title>
@@ -176,7 +176,7 @@ ereport(ERROR,
     <para>
      <function>errmsg_internal(const char *msg, ...)</function> is the same as
      <function>errmsg</>, except that the message string will not be
-     included in the internationalization message dictionary.
+     translated nor included in the internationalization message dictionary.
      This should be used for <quote>cannot happen</> cases that are probably
      not worth expending translation effort on.
     </para>
@@ -271,7 +271,7 @@ elog(level, "format string", ...);
 ereport(level, (errmsg_internal("format string", ...)));
 </programlisting>
     Notice that the SQLSTATE error code is always defaulted, and the message
-    string is not included in the internationalization message dictionary.
+    string is not subject to translation.
     Therefore, <function>elog</> should be used only for internal errors and
     low-level debug logging.  Any message that is likely to be of interest to
     ordinary users should go through <function>ereport</>.  Nonetheless,
diff --git a/src/backend/nls.mk b/src/backend/nls.mk
index 99e7e9f90a852deab9673bf77d087924977653d2..bfca4f1220f48a9cd1c3b709288e584e35a5b3f0 100644
--- a/src/backend/nls.mk
+++ b/src/backend/nls.mk
@@ -1,9 +1,7 @@
-# $PostgreSQL: pgsql/src/backend/nls.mk,v 1.22 2008/03/24 18:08:47 tgl Exp $
+# $PostgreSQL: pgsql/src/backend/nls.mk,v 1.23 2008/10/27 19:37:21 tgl Exp $
 CATALOG_NAME	:= postgres
 AVAIL_LANGUAGES	:= af cs de es fr hr hu it ko nb nl pt_BR ro ru sk sl sv tr zh_CN zh_TW
 GETTEXT_FILES	:= + gettext-files
-# you can add "elog:2" and "errmsg_internal" to this list if you want to
-# include internal messages in the translation list.
 GETTEXT_TRIGGERS:= _ errmsg errdetail errdetail_log errhint errcontext write_stderr yyerror
 
 gettext-files: distprep
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index c458ecf31944101515d5053cb480fab9bab670f5..057ca5df82380a044115e922f14c0e893e1b41f5 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -42,7 +42,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.208 2008/10/17 22:56:16 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.209 2008/10/27 19:37:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -149,6 +149,21 @@ static void write_csvlog(ErrorData *edata);
 static void setup_formatted_log_time(void);
 static void setup_formatted_start_time(void);
 
+
+/*
+ * in_error_recursion_trouble --- are we at risk of infinite error recursion?
+ *
+ * This function exists to provide common control of various fallback steps
+ * that we take if we think we are facing infinite error recursion.  See the
+ * callers for details.
+ */
+bool
+in_error_recursion_trouble(void)
+{
+	/* Pull the plug if recurse more than once */
+	return (recursion_depth > 2);
+}
+
 /*
  * errstart --- begin an error-reporting cycle
  *
@@ -261,12 +276,12 @@ errstart(int elevel, const char *filename, int lineno,
 		MemoryContextReset(ErrorContext);
 
 		/*
-		 * If we recurse more than once, the problem might be something broken
+		 * Infinite error recursion might be due to something broken
 		 * in a context traceback routine.	Abandon them too.  We also abandon
 		 * attempting to print the error statement (which, if long, could
 		 * itself be the source of the recursive failure).
 		 */
-		if (recursion_depth > 2)
+		if (in_error_recursion_trouble())
 		{
 			error_context_stack = NULL;
 			debug_query_string = NULL;
@@ -604,18 +619,20 @@ errcode_for_socket_access(void)
  * it's common code for errmsg(), errdetail(), etc.  Must be called inside
  * a routine that is declared like "const char *fmt, ..." and has an edata
  * pointer set up.	The message is assigned to edata->targetfield, or
- * appended to it if appendval is true.
+ * appended to it if appendval is true.  The message is subject to translation
+ * if translateit is true.
  *
  * Note: we pstrdup the buffer rather than just transferring its storage
  * to the edata field because the buffer might be considerably larger than
  * really necessary.
  */
-#define EVALUATE_MESSAGE(targetfield, appendval)  \
+#define EVALUATE_MESSAGE(targetfield, appendval, translateit)  \
 	{ \
 		char		   *fmtbuf; \
 		StringInfoData	buf; \
 		/* Internationalize the error format string */ \
-		fmt = dgettext(edata->domain, fmt); \
+		if (translateit) \
+			fmt = dgettext(edata->domain, fmt); \
 		/* Expand %m in format string */ \
 		fmtbuf = expand_fmt_string(fmt, edata); \
 		initStringInfo(&buf); \
@@ -662,7 +679,7 @@ errmsg(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(message, false);
+	EVALUATE_MESSAGE(message, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -674,9 +691,12 @@ errmsg(const char *fmt,...)
  * errmsg_internal --- add a primary error message text to the current error
  *
  * This is exactly like errmsg() except that strings passed to errmsg_internal
- * are customarily left out of the internationalization message dictionary.
- * This should be used for "can't happen" cases that are probably not worth
- * spending translation effort on.
+ * are not translated, and are customarily left out of the
+ * internationalization message dictionary.  This should be used for "can't
+ * happen" cases that are probably not worth spending translation effort on.
+ * We also use this for certain cases where we *must* not try to translate
+ * the message because the translation would fail and result in infinite
+ * error recursion.
  */
 int
 errmsg_internal(const char *fmt,...)
@@ -688,7 +708,7 @@ errmsg_internal(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(message, false);
+	EVALUATE_MESSAGE(message, false, false);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -709,7 +729,7 @@ errdetail(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(detail, false);
+	EVALUATE_MESSAGE(detail, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -730,7 +750,7 @@ errdetail_log(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(detail_log, false);
+	EVALUATE_MESSAGE(detail_log, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -751,7 +771,7 @@ errhint(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(hint, false);
+	EVALUATE_MESSAGE(hint, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -776,7 +796,7 @@ errcontext(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(context, true);
+	EVALUATE_MESSAGE(context, true, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -956,7 +976,9 @@ elog_start(const char *filename, int lineno, const char *funcname)
 		/*
 		 * Wups, stack not big enough.	We treat this as a PANIC condition
 		 * because it suggests an infinite loop of errors during error
-		 * recovery.
+		 * recovery.  Note that the message is intentionally not localized,
+		 * else failure to convert it to client encoding could cause further
+		 * recursion.
 		 */
 		errordata_stack_depth = -1;		/* make room on stack */
 		ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
@@ -990,12 +1012,12 @@ elog_finish(int elevel, const char *fmt,...)
 		return;					/* nothing to do */
 
 	/*
-	 * Format error message just like errmsg().
+	 * Format error message just like errmsg_internal().
 	 */
 	recursion_depth++;
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(message, false);
+	EVALUATE_MESSAGE(message, false, false);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -2408,6 +2430,10 @@ useful_strerror(int errnum)
 
 /*
  * error_severity --- get localized string representing elevel
+ *
+ * Note: in an error recursion situation, we stop localizing the tags
+ * for ERROR and above.  This is necessary because the problem might be
+ * failure to convert one of these strings to the client encoding.
  */
 static const char *
 error_severity(int elevel)
@@ -2437,13 +2463,22 @@ error_severity(int elevel)
 			prefix = _("WARNING");
 			break;
 		case ERROR:
-			prefix = _("ERROR");
+			if (in_error_recursion_trouble())
+				prefix = "ERROR";
+			else
+				prefix = _("ERROR");
 			break;
 		case FATAL:
-			prefix = _("FATAL");
+			if (in_error_recursion_trouble())
+				prefix = "FATAL";
+			else
+				prefix = _("FATAL");
 			break;
 		case PANIC:
-			prefix = _("PANIC");
+			if (in_error_recursion_trouble())
+				prefix = "PANIC";
+			else
+				prefix = _("PANIC");
 			break;
 		default:
 			prefix = "???";
diff --git a/src/backend/utils/mb/wchar.c b/src/backend/utils/mb/wchar.c
index 348a57e4d6e64fd11151a6cbbca4acc1fb30b36e..2f11b3aa9b0db267c19cd2925886162640034c93 100644
--- a/src/backend/utils/mb/wchar.c
+++ b/src/backend/utils/mb/wchar.c
@@ -1,7 +1,7 @@
 /*
  * conversion functions between pg_wchar and multibyte streams.
  * Tatsuo Ishii
- * $PostgreSQL: pgsql/src/backend/utils/mb/wchar.c,v 1.66 2007/11/15 21:14:40 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/mb/wchar.c,v 1.67 2008/10/27 19:37:22 tgl Exp $
  *
  */
 /* can be used in either frontend or backend */
@@ -1567,12 +1567,25 @@ report_untranslatable_char(int src_encoding, int dest_encoding,
 	for (j = 0; j < jlimit; j++)
 		p += sprintf(p, "%02x", (unsigned char) mbstr[j]);
 
-	ereport(ERROR,
-			(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
-	  errmsg("character 0x%s of encoding \"%s\" has no equivalent in \"%s\"",
-			 buf,
-			 pg_enc2name_tbl[src_encoding].name,
-			 pg_enc2name_tbl[dest_encoding].name)));
+	/*
+	 * In an error recursion situation, don't try to translate the message.
+	 * This gets us out of trouble if the problem is failure to convert
+	 * this very message (after translation) to the client encoding.
+	 */
+	if (in_error_recursion_trouble())
+		ereport(ERROR,
+				(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
+				 errmsg_internal("character 0x%s of encoding \"%s\" has no equivalent in \"%s\"",
+								 buf,
+								 pg_enc2name_tbl[src_encoding].name,
+								 pg_enc2name_tbl[dest_encoding].name)));
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
+				 errmsg("character 0x%s of encoding \"%s\" has no equivalent in \"%s\"",
+						buf,
+						pg_enc2name_tbl[src_encoding].name,
+						pg_enc2name_tbl[dest_encoding].name)));
 }
 
 #endif
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 437252e8d8344c9a282fdfc72343b5e1ab51a639..ba01a1386cd7b5fcd771c68843a5bfbf851bc6b2 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.96 2008/10/09 22:22:31 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.97 2008/10/27 19:37:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -324,6 +324,7 @@ extern int	Log_destination;
 /* Other exported functions */
 extern void DebugFileOpen(void);
 extern char *unpack_sql_state(int sql_state);
+extern bool in_error_recursion_trouble(void);
 
 #ifdef HAVE_SYSLOG
 extern void set_syslog_parameters(const char *ident, int facility);