Subject: [PATCH] Poison gettext with the Ook language

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Mon, 22 Oct 2018 17:36:33 +0200

The current gettext() function just replaces all strings with
'# GETTEXT POISON #' including format strings and hides the things
that we should be allowed to grep (like branch names, or some other
codes) even when gettext is poisoned.

This patch implements the poisoned _() with a universal and totally
legit language called Ook [1]. We could actually grep stuff even in
with this because format strings are preserved.

Long term, we could implement an "ook translator" for test_i18ngrep
and friends so that they translate English to Ook, allowing us to
match full text while making sure the text in the code is still marked
for translation.

[1] https://en.wikipedia.org/wiki/Unseen_University#Librarian

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 This started out as something fun to do while running the test suite
 last weekend. But it turns out actually working! If this patch ends
 up in git.git, the Librarian would be so proud!

 gettext.c       | 54 +++++++++++++++++++++++++++++++++++++++++++++++++
 gettext.h       |  7 ++++---
 t/lib-rebase.sh |  2 +-
 3 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/gettext.c b/gettext.c
index 7272771c8e..29901e1ddd 100644
--- a/gettext.c
+++ b/gettext.c
@@ -56,6 +56,60 @@ int use_gettext_poison(void)
 }
 #endif
 
+const char *gettext_poison(const char *msgid)
+{
+	/*
+	 * gettext() returns a string that is always valid. We would
+	 * need a hash map for that but let's stay simple and keep the
+	 * last 64 gettext() results. Should be more than enough.
+	 */
+	static char *bufs[64];
+	static int i;
+	struct strbuf sb = STRBUF_INIT;
+	char *buf;
+	const char *p;
+	const char *type_specifiers = "diouxXeEfFgGaAcsCSpnm%";
+
+	if (!strchr(msgid, '%'))
+		return "Eek!";
+
+	p = msgid;
+	while (*p) {
+		const char *type;
+		switch (*p) {
+		case '%':
+			/*
+			 * No strict parsing. We simply look for the end of a
+			 * format string
+			 */
+			type = p + 1;
+			while (*type && !strchr(type_specifiers, *type))
+				type++;
+			if (*type)
+				type++;
+			strbuf_add(&sb, p, (int)(type - p));
+			p = type;
+			break;
+		default:
+			if (!isalpha(*p)) {
+				strbuf_addch(&sb, *p);
+				p++;
+				break;
+			}
+			if (isupper(*p))
+				strbuf_addstr(&sb, "Ook");
+			else
+				strbuf_addstr(&sb, "ook");
+			while (isalpha(*p))
+				p++;
+		}
+	}
+	buf = bufs[(i++) % ARRAY_SIZE(bufs)];
+	free(buf);
+	buf = strbuf_detach(&sb, NULL);
+	return buf;
+}
+
 #ifndef NO_GETTEXT
 static int test_vsnprintf(const char *fmt, ...)
 {
diff --git a/gettext.h b/gettext.h
index 7eee64a34f..dc9851a06a 100644
--- a/gettext.h
+++ b/gettext.h
@@ -41,8 +41,9 @@ static inline int gettext_width(const char *s)
 }
 #endif
 
+const char *gettext_poison(const char *);
 #ifdef GETTEXT_POISON
-extern int use_gettext_poison(void);
+int use_gettext_poison(void);
 #else
 #define use_gettext_poison() 0
 #endif
@@ -51,14 +52,14 @@ static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
 {
 	if (!*msgid)
 		return "";
-	return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
+	return use_gettext_poison() ? gettext_poison(msgid) : gettext(msgid);
 }
 
 static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
 const char *Q_(const char *msgid, const char *plu, unsigned long n)
 {
 	if (use_gettext_poison())
-		return "# GETTEXT POISON #";
+		return gettext_poison(msgid);
 	return ngettext(msgid, plu, n);
 }
 
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index 2ca9fb69d6..1e8440e935 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -29,7 +29,7 @@ set_fake_editor () {
 	*/COMMIT_EDITMSG)
 		test -z "$EXPECT_HEADER_COUNT" ||
 			test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" ||
-			test "# # GETTEXT POISON #" = "$(sed -n '1p' < "$1")" ||
+			test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# Ook ook ook ook ook \(.*\) ook\./\1/p' < "$1")" ||
 			exit
 		test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
 		test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
-- 
2.19.1.647.g708186aaf9