cmd/gotext: distinguish between key and id

The id of the translation and the key that is used at
runtime to look up the translation are not the same.
The id is used for tracking and linking messages.
The key is used for lookup internally.

Change-Id: I26cac35fb421ee8227fd8b0b5a712a752c4f5f5e
Reviewed-on: https://go-review.googlesource.com/82202
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/locales/de/out.gotext.json b/cmd/gotext/examples/extract/locales/de/out.gotext.json
index aa626cc..3e617db 100755
--- a/cmd/gotext/examples/extract/locales/de/out.gotext.json
+++ b/cmd/gotext/examples/extract/locales/de/out.gotext.json
@@ -2,17 +2,15 @@
     "language": "de",
     "messages": [
         {
-            "key": [
-                "Hello world!\n"
-            ],
+            "id": "Hello world!\n",
+            "key": "Hello world!\n",
             "message": "Hello world!\n",
             "translation": "",
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:27:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {City}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {City}!\n",
             "translation": "",
             "placeholders": [
@@ -28,9 +26,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:31:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {Town}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {Town}!\n",
             "translation": "",
             "placeholders": [
@@ -47,9 +44,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:35:10"
         },
         {
-            "key": [
-                "%s is visiting %s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%s is visiting %s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "placeholders": [
@@ -75,9 +71,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:40:10"
         },
         {
-            "key": [
-                "%[1]s is visiting %[3]s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%[1]s is visiting %[3]s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "comment": "Person visiting a place.",
@@ -111,9 +106,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:55:10"
         },
         {
-            "key": [
-                "%d files remaining!"
-            ],
+            "id": "{} files remaining!",
+            "key": "%d files remaining!",
             "message": "{} files remaining!",
             "translation": "",
             "placeholders": [
@@ -129,9 +123,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:62:10"
         },
         {
-            "key": [
-                "%d more files remaining!"
-            ],
+            "id": "{N} more files remaining!",
+            "key": "%d more files remaining!",
             "message": "{N} more files remaining!",
             "translation": "",
             "placeholders": [
@@ -147,9 +140,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:67:10"
         },
         {
-            "key": [
-                "Use the following code for your discount: %d\n"
-            ],
+            "id": "Use the following code for your discount: {ReferralCode}\n",
+            "key": "Use the following code for your discount: %d\n",
             "message": "Use the following code for your discount: {ReferralCode}\n",
             "translation": "",
             "placeholders": [
@@ -165,10 +157,11 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:73:10"
         },
         {
-            "key": [
+            "id": [
                 "msgOutOfOrder",
-                "%s is out of order!"
+                "{Device} is out of order!"
             ],
+            "key": "%s is out of order!",
             "message": "{Device} is out of order!",
             "translation": "",
             "comment": "FOO\n",
@@ -185,9 +178,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:81:10"
         },
         {
-            "key": [
-                "%.2[1]f miles traveled (%[1]f)"
-            ],
+            "id": "{Miles} miles traveled ({Miles_1})",
+            "key": "%.2[1]f miles traveled (%[1]f)",
             "message": "{Miles} miles traveled ({Miles_1})",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/examples/extract/locales/en-US/out.gotext.json b/cmd/gotext/examples/extract/locales/en-US/out.gotext.json
index 48c55a2..b97c012 100755
--- a/cmd/gotext/examples/extract/locales/en-US/out.gotext.json
+++ b/cmd/gotext/examples/extract/locales/en-US/out.gotext.json
@@ -2,17 +2,15 @@
     "language": "en-US",
     "messages": [
         {
-            "key": [
-                "Hello world!\n"
-            ],
+            "id": "Hello world!\n",
+            "key": "Hello world!\n",
             "message": "Hello world!\n",
             "translation": "",
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:27:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {City}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {City}!\n",
             "translation": "",
             "placeholders": [
@@ -28,9 +26,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:31:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {Town}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {Town}!\n",
             "translation": "",
             "placeholders": [
@@ -47,9 +44,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:35:10"
         },
         {
-            "key": [
-                "%s is visiting %s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%s is visiting %s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "placeholders": [
@@ -75,9 +71,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:40:10"
         },
         {
-            "key": [
-                "%[1]s is visiting %[3]s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%[1]s is visiting %[3]s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "comment": "Person visiting a place.",
@@ -111,9 +106,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:55:10"
         },
         {
-            "key": [
-                "%d files remaining!"
-            ],
+            "id": "{} files remaining!",
+            "key": "%d files remaining!",
             "message": "{} files remaining!",
             "translation": "",
             "placeholders": [
@@ -129,9 +123,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:62:10"
         },
         {
-            "key": [
-                "%d more files remaining!"
-            ],
+            "id": "{N} more files remaining!",
+            "key": "%d more files remaining!",
             "message": "{N} more files remaining!",
             "translation": "",
             "placeholders": [
@@ -147,9 +140,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:67:10"
         },
         {
-            "key": [
-                "Use the following code for your discount: %d\n"
-            ],
+            "id": "Use the following code for your discount: {ReferralCode}\n",
+            "key": "Use the following code for your discount: %d\n",
             "message": "Use the following code for your discount: {ReferralCode}\n",
             "translation": "",
             "placeholders": [
@@ -165,10 +157,11 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:73:10"
         },
         {
-            "key": [
+            "id": [
                 "msgOutOfOrder",
-                "%s is out of order!"
+                "{Device} is out of order!"
             ],
+            "key": "%s is out of order!",
             "message": "{Device} is out of order!",
             "translation": "",
             "comment": "FOO\n",
@@ -185,9 +178,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:81:10"
         },
         {
-            "key": [
-                "%.2[1]f miles traveled (%[1]f)"
-            ],
+            "id": "{Miles} miles traveled ({Miles_1})",
+            "key": "%.2[1]f miles traveled (%[1]f)",
             "message": "{Miles} miles traveled ({Miles_1})",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/examples/extract/locales/extracted.gotext.json b/cmd/gotext/examples/extract/locales/extracted.gotext.json
index 48c55a2..b97c012 100755
--- a/cmd/gotext/examples/extract/locales/extracted.gotext.json
+++ b/cmd/gotext/examples/extract/locales/extracted.gotext.json
@@ -2,17 +2,15 @@
     "language": "en-US",
     "messages": [
         {
-            "key": [
-                "Hello world!\n"
-            ],
+            "id": "Hello world!\n",
+            "key": "Hello world!\n",
             "message": "Hello world!\n",
             "translation": "",
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:27:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {City}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {City}!\n",
             "translation": "",
             "placeholders": [
@@ -28,9 +26,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:31:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {Town}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {Town}!\n",
             "translation": "",
             "placeholders": [
@@ -47,9 +44,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:35:10"
         },
         {
-            "key": [
-                "%s is visiting %s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%s is visiting %s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "placeholders": [
@@ -75,9 +71,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:40:10"
         },
         {
-            "key": [
-                "%[1]s is visiting %[3]s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%[1]s is visiting %[3]s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "comment": "Person visiting a place.",
@@ -111,9 +106,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:55:10"
         },
         {
-            "key": [
-                "%d files remaining!"
-            ],
+            "id": "{} files remaining!",
+            "key": "%d files remaining!",
             "message": "{} files remaining!",
             "translation": "",
             "placeholders": [
@@ -129,9 +123,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:62:10"
         },
         {
-            "key": [
-                "%d more files remaining!"
-            ],
+            "id": "{N} more files remaining!",
+            "key": "%d more files remaining!",
             "message": "{N} more files remaining!",
             "translation": "",
             "placeholders": [
@@ -147,9 +140,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:67:10"
         },
         {
-            "key": [
-                "Use the following code for your discount: %d\n"
-            ],
+            "id": "Use the following code for your discount: {ReferralCode}\n",
+            "key": "Use the following code for your discount: %d\n",
             "message": "Use the following code for your discount: {ReferralCode}\n",
             "translation": "",
             "placeholders": [
@@ -165,10 +157,11 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:73:10"
         },
         {
-            "key": [
+            "id": [
                 "msgOutOfOrder",
-                "%s is out of order!"
+                "{Device} is out of order!"
             ],
+            "key": "%s is out of order!",
             "message": "{Device} is out of order!",
             "translation": "",
             "comment": "FOO\n",
@@ -185,9 +178,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:81:10"
         },
         {
-            "key": [
-                "%.2[1]f miles traveled (%[1]f)"
-            ],
+            "id": "{Miles} miles traveled ({Miles_1})",
+            "key": "%.2[1]f miles traveled (%[1]f)",
             "message": "{Miles} miles traveled ({Miles_1})",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/examples/extract/locales/zh/out.gotext.json b/cmd/gotext/examples/extract/locales/zh/out.gotext.json
index 1e3fee1..7f3b997 100755
--- a/cmd/gotext/examples/extract/locales/zh/out.gotext.json
+++ b/cmd/gotext/examples/extract/locales/zh/out.gotext.json
@@ -2,17 +2,15 @@
     "language": "zh",
     "messages": [
         {
-            "key": [
-                "Hello world!\n"
-            ],
+            "id": "Hello world!\n",
+            "key": "Hello world!\n",
             "message": "Hello world!\n",
             "translation": "",
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:27:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {City}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {City}!\n",
             "translation": "",
             "placeholders": [
@@ -28,9 +26,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:31:10"
         },
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {Town}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {Town}!\n",
             "translation": "",
             "placeholders": [
@@ -47,9 +44,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:35:10"
         },
         {
-            "key": [
-                "%s is visiting %s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%s is visiting %s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "placeholders": [
@@ -75,9 +71,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:40:10"
         },
         {
-            "key": [
-                "%[1]s is visiting %[3]s!\n"
-            ],
+            "id": "{Person} is visiting {Place}!\n",
+            "key": "%[1]s is visiting %[3]s!\n",
             "message": "{Person} is visiting {Place}!\n",
             "translation": "",
             "comment": "Person visiting a place.",
@@ -111,9 +106,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:55:10"
         },
         {
-            "key": [
-                "%d files remaining!"
-            ],
+            "id": "{} files remaining!",
+            "key": "%d files remaining!",
             "message": "{} files remaining!",
             "translation": "",
             "placeholders": [
@@ -129,9 +123,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:62:10"
         },
         {
-            "key": [
-                "%d more files remaining!"
-            ],
+            "id": "{N} more files remaining!",
+            "key": "%d more files remaining!",
             "message": "{N} more files remaining!",
             "translation": "",
             "placeholders": [
@@ -147,9 +140,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:67:10"
         },
         {
-            "key": [
-                "Use the following code for your discount: %d\n"
-            ],
+            "id": "Use the following code for your discount: {ReferralCode}\n",
+            "key": "Use the following code for your discount: %d\n",
             "message": "Use the following code for your discount: {ReferralCode}\n",
             "translation": "",
             "placeholders": [
@@ -165,10 +157,11 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:73:10"
         },
         {
-            "key": [
+            "id": [
                 "msgOutOfOrder",
-                "%s is out of order!"
+                "{Device} is out of order!"
             ],
+            "key": "%s is out of order!",
             "message": "{Device} is out of order!",
             "translation": "",
             "comment": "FOO\n",
@@ -185,9 +178,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract/main.go:81:10"
         },
         {
-            "key": [
-                "%.2[1]f miles traveled (%[1]f)"
-            ],
+            "id": "{Miles} miles traveled ({Miles_1})",
+            "key": "%.2[1]f miles traveled (%[1]f)",
             "message": "{Miles} miles traveled ({Miles_1})",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/examples/extract_http/locales/de/out.gotext.json b/cmd/gotext/examples/extract_http/locales/de/out.gotext.json
index f8eeff2..bd71590 100755
--- a/cmd/gotext/examples/extract_http/locales/de/out.gotext.json
+++ b/cmd/gotext/examples/extract_http/locales/de/out.gotext.json
@@ -2,9 +2,8 @@
     "language": "de",
     "messages": [
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {From}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {From}!\n",
             "translation": "",
             "placeholders": [
@@ -20,9 +19,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract_http/pkg/pkg.go:22:11"
         },
         {
-            "key": [
-                "Do you like your browser (%s)?\n"
-            ],
+            "id": "Do you like your browser ({User_Agent})?\n",
+            "key": "Do you like your browser (%s)?\n",
             "message": "Do you like your browser ({User_Agent})?\n",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/examples/extract_http/locales/en-US/out.gotext.json b/cmd/gotext/examples/extract_http/locales/en-US/out.gotext.json
index 72b987e..d0c4684 100755
--- a/cmd/gotext/examples/extract_http/locales/en-US/out.gotext.json
+++ b/cmd/gotext/examples/extract_http/locales/en-US/out.gotext.json
@@ -2,9 +2,8 @@
     "language": "en-US",
     "messages": [
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {From}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {From}!\n",
             "translation": "",
             "placeholders": [
@@ -20,9 +19,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract_http/pkg/pkg.go:22:11"
         },
         {
-            "key": [
-                "Do you like your browser (%s)?\n"
-            ],
+            "id": "Do you like your browser ({User_Agent})?\n",
+            "key": "Do you like your browser (%s)?\n",
             "message": "Do you like your browser ({User_Agent})?\n",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/examples/extract_http/locales/extracted.gotext.json b/cmd/gotext/examples/extract_http/locales/extracted.gotext.json
index 72b987e..d0c4684 100755
--- a/cmd/gotext/examples/extract_http/locales/extracted.gotext.json
+++ b/cmd/gotext/examples/extract_http/locales/extracted.gotext.json
@@ -2,9 +2,8 @@
     "language": "en-US",
     "messages": [
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {From}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {From}!\n",
             "translation": "",
             "placeholders": [
@@ -20,9 +19,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract_http/pkg/pkg.go:22:11"
         },
         {
-            "key": [
-                "Do you like your browser (%s)?\n"
-            ],
+            "id": "Do you like your browser ({User_Agent})?\n",
+            "key": "Do you like your browser (%s)?\n",
             "message": "Do you like your browser ({User_Agent})?\n",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/examples/extract_http/locales/zh/out.gotext.json b/cmd/gotext/examples/extract_http/locales/zh/out.gotext.json
index 3ea7a13..cad0ff0 100755
--- a/cmd/gotext/examples/extract_http/locales/zh/out.gotext.json
+++ b/cmd/gotext/examples/extract_http/locales/zh/out.gotext.json
@@ -2,9 +2,8 @@
     "language": "zh",
     "messages": [
         {
-            "key": [
-                "Hello %s!\n"
-            ],
+            "id": "Hello {From}!\n",
+            "key": "Hello %s!\n",
             "message": "Hello {From}!\n",
             "translation": "",
             "placeholders": [
@@ -20,9 +19,8 @@
             "position": "golang.org/x/text/cmd/gotext/examples/extract_http/pkg/pkg.go:22:11"
         },
         {
-            "key": [
-                "Do you like your browser (%s)?\n"
-            ],
+            "id": "Do you like your browser ({User_Agent})?\n",
+            "key": "Do you like your browser (%s)?\n",
             "message": "Do you like your browser ({User_Agent})?\n",
             "translation": "",
             "placeholders": [
diff --git a/cmd/gotext/extract.go b/cmd/gotext/extract.go
index d789ab5..1e9402b 100644
--- a/cmd/gotext/extract.go
+++ b/cmd/gotext/extract.go
@@ -130,7 +130,6 @@
 					}
 				}
 
-				key = append(key, fmtMsg)
 				arguments := []argument{}
 				args = args[1:]
 				simArgs := make([]interface{}, len(args))
@@ -181,6 +180,7 @@
 						msg += fmt.Sprintf("{%s}", ph.addArg(&arg, sub))
 					}
 				}
+				key = append(key, msg)
 
 				// Add additional Placeholders that can be used in translations
 				// that are not present in the string.
@@ -196,7 +196,8 @@
 				}
 
 				messages = append(messages, Message{
-					Key:     key,
+					ID:      key,
+					Key:     fmtMsg,
 					Message: Text{Msg: msg},
 					// TODO(fix): this doesn't get the before comment.
 					Comment:      comment,
diff --git a/cmd/gotext/message.go b/cmd/gotext/message.go
index d0d0233..f2edc37 100644
--- a/cmd/gotext/message.go
+++ b/cmd/gotext/message.go
@@ -32,12 +32,13 @@
 
 // A Message describes a message to be translated.
 type Message struct {
-	// Key contains a list of identifiers for the message. If this list is empty
-	// the message itself is used as the key.
-	Key         []string `json:"key,omitempty"`
-	Meaning     string   `json:"meaning,omitempty"`
-	Message     Text     `json:"message"`
-	Translation Text     `json:"translation"`
+	// ID contains a list of identifiers for the message.
+	ID IDList `json:"id"`
+	// Key is the string that is used to look up the message at runtime.
+	Key         string `json:"key"`
+	Meaning     string `json:"meaning,omitempty"`
+	Message     Text   `json:"message"`
+	Translation Text   `json:"translation"`
 
 	Comment           string `json:"comment,omitempty"`
 	TranslatorComment string `json:"translatorComment,omitempty"`
@@ -136,6 +137,28 @@
 	return json.Marshal((*rawText)(t))
 }
 
+// IDList is a set identifiers that each may refer to possibly different
+// versions of the same message. When looking up a messages, the first
+// identifier in the list takes precedence.
+type IDList []string
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (id *IDList) UnmarshalJSON(b []byte) error {
+	if b[0] == '"' {
+		*id = []string{""}
+		return json.Unmarshal(b, &((*id)[0]))
+	}
+	return json.Unmarshal(b, (*[]string)(id))
+}
+
+// MarshalJSON implements json.Marshaler.
+func (id *IDList) MarshalJSON() ([]byte, error) {
+	if len(*id) == 1 {
+		return json.Marshal((*id)[0])
+	}
+	return json.Marshal((*[]string)(id))
+}
+
 // Select selects a Text based on the feature value associated with a feature of
 // a certain argument.
 type Select struct {