cue/errors: change API to accept localization hooks

prepare for localizable errors

Issue #52

Change-Id: I6f5b4a7d1151b041bb1a73bd4ed32f2a79acd27a
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2211
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/cmd_test.go b/cmd/cue/cmd/cmd_test.go
index 5defa9f..e892730 100644
--- a/cmd/cue/cmd/cmd_test.go
+++ b/cmd/cue/cmd/cmd_test.go
@@ -48,7 +48,7 @@
 			}
 			err = executeTasks("command", name, tools)
 			if err != nil {
-				errors.Print(stdout, err)
+				errors.Print(stdout, err, nil)
 			}
 			return nil
 		}
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index 7d23861..3ed7383 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"io"
 	"os"
 
 	"cuelang.org/go/cue"
@@ -23,7 +24,10 @@
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/load"
 	"cuelang.org/go/cue/parser"
+	"github.com/cloudfoundry-attic/jibber_jabber"
 	"github.com/spf13/cobra"
+	"golang.org/x/text/language"
+	"golang.org/x/text/message"
 )
 
 var runtime = &cue.Runtime{}
@@ -41,8 +45,15 @@
 		cwd = p
 	}
 
+	// Link x/text as our localizer.
+	lang, _ := jibber_jabber.DetectIETF()
+	p := message.NewPrinter(language.Make(lang))
+	format := func(w io.Writer, format string, args ...interface{}) {
+		p.Fprintf(w, format, args...)
+	}
+
 	w := &bytes.Buffer{}
-	errors.Print(w, err)
+	errors.Print(w, err, &errors.Config{Format: format})
 
 	// TODO: do something more principled than this.
 	b := w.Bytes()
diff --git a/cmd/cue/cmd/common_test.go b/cmd/cue/cmd/common_test.go
index 2920aec..6a44a37 100644
--- a/cmd/cue/cmd/common_test.go
+++ b/cmd/cue/cmd/common_test.go
@@ -72,9 +72,9 @@
 					switch err := recover().(type) {
 					case nil:
 					case panicError:
-						errors.Print(wOut, err.Err)
+						errors.Print(wOut, err.Err, nil)
 					case error:
-						errors.Print(wOut, err)
+						errors.Print(wOut, err, nil)
 					default:
 						fmt.Fprintln(wOut, err)
 					}
diff --git a/cue/ast_test.go b/cue/ast_test.go
index c46f4e7..95b96ca 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -253,7 +253,7 @@
 			ctx, root, errs := compileFileWithErrors(t, tc.in)
 			buf := &bytes.Buffer{}
 			if len(errs) > 0 {
-				errors.Print(buf, errs)
+				errors.Print(buf, errs, nil)
 			}
 			buf.WriteString(debugStr(ctx, root))
 			got := buf.String()
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index 652c0cd..32b3770 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -324,24 +324,42 @@
 	return p
 }
 
+// A Config defines parameters for printing.
+type Config struct {
+	// Format formats the given string and arguments and writes it to w.
+	// It is used for all printing.
+	Format func(w io.Writer, format string, args ...interface{})
+}
+
 // Print is a utility function that prints a list of errors to w,
 // one error per line, if the err parameter is an List. Otherwise
 // it prints the err string.
 //
-func Print(w io.Writer, err error) {
+func Print(w io.Writer, err error, cfg *Config) {
+	if cfg == nil {
+		cfg = &Config{}
+	}
 	if list, ok := err.(List); ok {
 		for _, e := range list {
-			printError(w, e)
+			printError(w, e, cfg)
 		}
 	} else if err != nil {
-		printError(w, err)
+		printError(w, err, cfg)
 	}
 }
 
-func printError(w io.Writer, err error) {
+func defaultFprintf(w io.Writer, format string, args ...interface{}) {
+	fmt.Fprintf(w, format, args...)
+}
+
+func printError(w io.Writer, err error, cfg *Config) {
 	if err == nil {
 		return
 	}
+	fprintf := cfg.Format
+	if fprintf == nil {
+		fprintf = defaultFprintf
+	}
 
 	positions := []string{}
 
@@ -361,19 +379,19 @@
 		}
 	}
 
-	if p := Path(err); p != nil {
-		fmt.Fprintf(w, "%v:", strings.Join(p, "."))
+	if path := Path(err); path != nil {
+		fprintf(w, "%s:", strings.Join(path, "."))
 	}
 
 	if len(positions) == 0 {
-		fmt.Fprintln(w, err)
+		fprintf(w, "%v\n", err)
 		return
 	}
 
 	unique.Strings(&positions)
 
-	fmt.Fprintf(w, "%v:\n", err)
+	fprintf(w, "%v:\n", err)
 	for _, pos := range positions {
-		fmt.Fprintf(w, "    %v\n", pos)
+		fprintf(w, "    %s\n", pos)
 	}
 }
diff --git a/cue/errors/errors_test.go b/cue/errors/errors_test.go
index 2f124a4..f670cd8 100644
--- a/cue/errors/errors_test.go
+++ b/cue/errors/errors_test.go
@@ -184,7 +184,7 @@
 	}
 	for _, tt := range tests {
 		w := &bytes.Buffer{}
-		Print(w, tt.args.err)
+		Print(w, tt.args.err, nil)
 		if gotW := w.String(); gotW != tt.wantW {
 			t.Errorf("%q. PrintError() = %v, want %v", tt.name, gotW, tt.wantW)
 		}
diff --git a/cue/scanner/scanner_test.go b/cue/scanner/scanner_test.go
index 0e0ad45..9fc91d0 100644
--- a/cue/scanner/scanner_test.go
+++ b/cue/scanner/scanner_test.go
@@ -662,19 +662,19 @@
 
 	if len(list) != 9 {
 		t.Errorf("found %d raw errors, expected 9", len(list))
-		errors.Print(os.Stderr, list)
+		errors.Print(os.Stderr, list, nil)
 	}
 
 	list.Sort()
 	if len(list) != 9 {
 		t.Errorf("found %d sorted errors, expected 9", len(list))
-		errors.Print(os.Stderr, list)
+		errors.Print(os.Stderr, list, nil)
 	}
 
 	list.RemoveMultiples()
 	if len(list) != 4 {
 		t.Errorf("found %d one-per-line errors, expected 4", len(list))
-		errors.Print(os.Stderr, list)
+		errors.Print(os.Stderr, list, nil)
 	}
 }
 
diff --git a/go.mod b/go.mod
index 51bfbf7..f1868aa 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,7 @@
 module cuelang.org/go
 
 require (
+	github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
 	github.com/cockroachdb/apd v1.1.0
 	github.com/emicklei/proto v1.6.11
 	github.com/ghodss/yaml v1.0.0
@@ -15,6 +16,7 @@
 	github.com/spf13/pflag v1.0.3
 	golang.org/x/exp/errors v0.0.0-20181221233300-b68661188fbf
 	golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
+	golang.org/x/text v0.3.2
 	golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77
 	golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373
 	gopkg.in/yaml.v2 v2.2.2 // indirect
diff --git a/go.sum b/go.sum
index d768393..cd33d74 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:Yg2hDs4b13Evkpj42FU2idX2cVXVFqQSheXYKM86Qsk=
+github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:MgJyK38wkzZbiZSKeIeFankxxSA8gayko/nr5x5bgBA=
 github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -43,6 +45,9 @@
 golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181018182439-def26773749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77 h1:s+6psEFi3o1QryeA/qyvUoVaHMCQkYVvZ0i2ZolwSJc=
 golang.org/x/tools v0.0.0-20181210225255-6a3e9aa2ab77/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=