internal/catmsg: support Affix message type

Change-Id: Iabf2ef415f3610598607a44d8f2c35a70743da3e
Reviewed-on: https://go-review.googlesource.com/83815
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/cmd/gotext/examples/extract/catalog.go b/cmd/gotext/examples/extract/catalog.go
index 4c2303f..c9e8495 100644
--- a/cmd/gotext/examples/extract/catalog.go
+++ b/cmd/gotext/examples/extract/catalog.go
@@ -62,7 +62,7 @@
 
 const en_USData string = "" + // Size: 201 bytes
 	"\x02Hello world!\x0a\x02Hello %[1]s!\x0a\x02%[1]s is visiting %[2]s!\x0a" +
-	"\x02%[1]s is visiting %[3]s!\x0a\x04\x01\x81\x01\x00\x02\x14\x02One file" +
+	"\x02%[1]s is visiting %[3]s!\x0a\x14\x01\x81\x01\x00\x02\x14\x02One file" +
 	" remaining!\x00&\x02There are %[1]d more files remaining!\x02%[1]s is ou" +
 	"t of order!\x02%.2[1]f miles traveled (%[1]f)"
 
@@ -73,4 +73,4 @@
 
 const zhData string = ""
 
-// Total table size 471 bytes (0KiB); checksum: 7746955
+// Total table size 471 bytes (0KiB); checksum: 2452D1C5
diff --git a/internal/catmsg/catmsg.go b/internal/catmsg/catmsg.go
index 32d6c20..c0bf86f 100644
--- a/internal/catmsg/catmsg.go
+++ b/internal/catmsg/catmsg.go
@@ -104,20 +104,24 @@
 	msgFirst
 	msgRaw
 	msgString
-	numFixed
+	msgAffix
+	// Leave some arbitrary room for future expansion: 20 should suffice.
+	numInternal = 20
 )
 
 const prefix = "golang.org/x/text/internal/catmsg."
 
 var (
+	// TODO: find a more stable way to link handles to message types.
 	mutex sync.Mutex
 	names = map[string]Handle{
 		prefix + "Vars":   msgVars,
 		prefix + "First":  msgFirst,
 		prefix + "Raw":    msgRaw,
 		prefix + "String": msgString,
+		prefix + "Affix":  msgAffix,
 	}
-	handlers = make([]Handler, numFixed)
+	handlers = make([]Handler, numInternal)
 )
 
 func init() {
@@ -161,6 +165,20 @@
 		}
 		return true
 	}
+
+	handlers[msgAffix] = func(d *Decoder) bool {
+		// TODO: use an alternative method for common cases.
+		prefix := d.DecodeString()
+		suffix := d.DecodeString()
+		if prefix != "" {
+			d.Render(prefix)
+		}
+		ret := d.ExecuteMessage()
+		if suffix != "" {
+			d.Render(suffix)
+		}
+		return ret
+	}
 }
 
 var (
@@ -374,3 +392,24 @@
 	}
 	return err
 }
+
+// Affix is a message that adds a prefix and suffix to another message.
+// This is mostly used add back whitespace to a translation that was stripped
+// before sending it out.
+type Affix struct {
+	Message Message
+	Prefix  string
+	Suffix  string
+}
+
+// Compile implements Message.
+func (a Affix) Compile(e *Encoder) (err error) {
+	// TODO: consider adding a special message type that just adds a single
+	// return. This is probably common enough to handle the majority of cases.
+	// Get some stats first, though.
+	e.EncodeMessageType(msgAffix)
+	e.EncodeString(a.Prefix)
+	e.EncodeString(a.Suffix)
+	e.EncodeMessage(a.Message)
+	return nil
+}
diff --git a/internal/catmsg/catmsg_test.go b/internal/catmsg/catmsg_test.go
index 485d19c..b2a7a9e 100644
--- a/internal/catmsg/catmsg_test.go
+++ b/internal/catmsg/catmsg_test.go
@@ -71,6 +71,10 @@
 		m:     String("foo"),
 		tests: single("foo", ""),
 	}, {
+		desc:  "affix",
+		m:     &Affix{String("foo"), "\t", "\n"},
+		tests: single("\t|foo|\n", ""),
+	}, {
 		desc:   "missing var",
 		m:      String("foo${bar}"),
 		enc:    "\x03\x03foo\x02\x03bar",
@@ -101,6 +105,13 @@
 		},
 		tests: single("foo|baz", ""),
 	}, {
+		desc: "affix with substitution",
+		m: &Affix{seq{
+			&Var{"bar", String("baz")},
+			String("foo${bar}"),
+		}, "\t", "\n"},
+		tests: single("\t|foo|baz|\n", ""),
+	}, {
 		desc: "shadowed variable",
 		m: seq{
 			&Var{"bar", String("baz")},
@@ -140,7 +151,7 @@
 			&Var{"bar", incomplete{}},
 			String("${bar}"),
 		},
-		enc: "\x00\t\b\x01\x01\x04\x04\x02bar\x03\x00\x00\x00",
+		enc: "\x00\t\b\x01\x01\x14\x04\x02bar\x03\x00\x00\x00",
 		// TODO: recognize that it is cheaper to substitute bar.
 		tests: single("bar", ""),
 	}, {
diff --git a/message/pipeline/testdata/test1/catalog_gen.go b/message/pipeline/testdata/test1/catalog_gen.go
index 76b0bf0..62f8686 100644
--- a/message/pipeline/testdata/test1/catalog_gen.go
+++ b/message/pipeline/testdata/test1/catalog_gen.go
@@ -66,7 +66,7 @@
 
 const en_USData string = "" + // Size: 201 bytes
 	"\x02Hello world!\x0a\x02Hello %[1]s!\x0a\x02%[1]s is visiting %[2]s!\x0a" +
-	"\x02%[1]s is visiting %[3]s!\x0a\x04\x01\x81\x01\x00\x02\x14\x02One file" +
+	"\x02%[1]s is visiting %[3]s!\x0a\x14\x01\x81\x01\x00\x02\x14\x02One file" +
 	" remaining!\x00&\x02There are %[1]d more files remaining!\x02%[1]s is ou" +
 	"t of order!\x02%.2[1]f miles traveled (%[1]f)"
 
@@ -78,4 +78,4 @@
 
 const zhData string = ""
 
-// Total table size 513 bytes (0KiB); checksum: B21180E3
+// Total table size 513 bytes (0KiB); checksum: 41415C53