message/pipeline: sort maps to generate predictable output

When using gotext utility, the output files are always reshuffled,
which makes any kind of source versioning and incremental translation
a real challenge.

This commit sorts the Go files by package name and order index (file
name is not available) before processing the string extraction.

Furthermore, an issue with sorting plural cases has been resolved.
Originally, it sorted ["two", "few", "one", "other"] wrong.

Fixes golang/go#33552

Change-Id: I76fc5d40cf4f989e01ba6d897c0a69029ca30337
Reviewed-on: https://go-review.googlesource.com/c/text/+/207281
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/message/pipeline/extract.go b/message/pipeline/extract.go
index 39b3dd5..a15a7f9 100644
--- a/message/pipeline/extract.go
+++ b/message/pipeline/extract.go
@@ -14,6 +14,7 @@
 	"go/token"
 	"go/types"
 	"path/filepath"
+	"sort"
 	"strings"
 	"unicode"
 	"unicode/utf8"
@@ -509,8 +510,14 @@
 
 func (x *extracter) extractMessages() {
 	prog := x.iprog
+	keys := make([]*types.Package, 0, len(x.iprog.AllPackages))
+	for k := range x.iprog.AllPackages {
+		keys = append(keys, k)
+	}
+	sort.Slice(keys, func(i, j int) bool { return keys[i].Path() < keys[j].Path() })
 	files := []packageExtracter{}
-	for _, info := range x.iprog.AllPackages {
+	for _, k := range keys {
+		info := x.iprog.AllPackages[k]
 		for _, f := range info.Files {
 			// Associate comments with nodes.
 			px := packageExtracter{
diff --git a/message/pipeline/generate.go b/message/pipeline/generate.go
index ada927a..2770d14 100644
--- a/message/pipeline/generate.go
+++ b/message/pipeline/generate.go
@@ -271,8 +271,11 @@
 func sortCases(cases []string) {
 	// TODO: implement full interface.
 	sort.Slice(cases, func(i, j int) bool {
-		if cases[j] == "other" && cases[i] != "other" {
+		switch {
+		case cases[i] != "other" && cases[j] == "other":
 			return true
+		case cases[i] == "other" && cases[j] != "other":
+			return false
 		}
 		// the following code relies on '<' < '=' < any letter.
 		return cmpNumeric(cases[i], cases[j]) == -1