Quellcode durchsuchen

feat(localization): wrap all translatable strings

jguer vor 5 Jahren
Ursprung
Commit
732f1a5412
24 geänderte Dateien mit 433 neuen und 257 gelöschten Zeilen
  1. 0 1
      .github/workflows/docker-ci.yml
  2. 4 4
      .gitignore
  3. 10 0
      .pre-commit-config.yaml
  4. 29 3
      Makefile
  5. 13 11
      callbacks.go
  6. 14 10
      clean.go
  7. 18 18
      cmd.go
  8. 13 13
      config.go
  9. 16 18
      depCheck.go
  10. 21 17
      download.go
  11. 5 3
      exec.go
  12. 3 1
      go.mod
  13. 6 0
      go.sum
  14. 55 55
      install.go
  15. 11 7
      keys.go
  16. 20 11
      main.go
  17. 4 3
      parser.go
  18. 57 0
      pkg/text/color.go
  19. 52 0
      pkg/text/print.go
  20. 54 61
      print.go
  21. 7 4
      query.go
  22. 10 11
      upgrade.go
  23. 6 3
      utils.go
  24. 5 3
      vcs.go

+ 0 - 1
.github/workflows/docker-ci.yml

@@ -11,7 +11,6 @@ on:
 jobs:
   build:
     name: Lint and test yay
-    # This job runs on Linux
     runs-on: ubuntu-latest
     container:
       image: samip537/archlinux:devel

+ 4 - 4
.gitignore

@@ -7,10 +7,6 @@
 _obj
 _test
 
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
 *.cgo1.go
 *.cgo2.c
 _cgo_defun.c
@@ -25,3 +21,7 @@ yay_*/
 *.tar.gz
 qemu-*
 .go
+
+# Locale
+*.mo
+*.pot

+ 10 - 0
.pre-commit-config.yaml

@@ -0,0 +1,10 @@
+repos:
+  - repo: git://github.com/dnephin/pre-commit-golang
+    rev: master
+    hooks:
+      - id: go-fmt
+      - id: go-imports
+        args: [-local=github.com/Jguer/yay/v9/]
+      - id: golangci-lint
+      - id: go-unit-tests
+      - id: go-build

+ 29 - 3
Makefile

@@ -15,14 +15,24 @@ MINORVERSION := 4
 PATCHVERSION := 2
 VERSION ?= ${MAJORVERSION}.${MINORVERSION}.${PATCHVERSION}
 
+LOCALEDIR := locale
+SYSTEMLOCALEPATH := $(DESTDIR)$(PREFIX)/share/locale
+
+LANGS := pt
+POTFILE := ${PKGNAME}.pot
+POFILES := $(addprefix $(LOCALEDIR)/,$(addsuffix .po,$(LANGS)))
+MOFILES := $(POFILES:.po=.mo)
+
 GOFLAGS := -v -mod=mod
 EXTRA_GOFLAGS ?=
-LDFLAGS := $(LDFLAGS) -X "main.yayVersion=${VERSION}"
+LDFLAGS := $(LDFLAGS) -X "main.yayVersion=${VERSION} -X main.localePath=${SYSTEMLOCALEPATH}"
 
 RELEASE_DIR := ${PKGNAME}_${VERSION}_${ARCH}
 PACKAGE := $(RELEASE_DIR).tar.gz
 SOURCES ?= $(shell find . -name "*.go" -type f ! -path "./vendor/*")
 
+.PRECIOUS: ${LOCALEDIR}/%.po
+
 .PHONY: default
 default: build
 
@@ -53,7 +63,7 @@ $(RELEASE_DIR):
 	mkdir $(RELEASE_DIR)
 
 $(PACKAGE): $(BIN) $(RELEASE_DIR)
-	cp -t $(RELEASE_DIR) ${BIN} doc/${PKGNAME}.8 completions/*
+	cp -t $(RELEASE_DIR) ${BIN} doc/${PKGNAME}.8 completions/* ${MOFILES}
 	tar -czvf $(PACKAGE) $(RELEASE_DIR)
 
 .PHONY: docker-release-all
@@ -110,7 +120,7 @@ fmt:
 	gofmt -s -w $(SOURCES)
 
 .PHONY: install
-install:
+install: build ${MOFILES}
 	install -Dm755 ${BIN} $(DESTDIR)$(PREFIX)/bin/${BIN}
 	install -Dm644 doc/${PKGNAME}.8 $(DESTDIR)$(PREFIX)/share/man/man8/${PKGNAME}.8
 	install -Dm644 completions/bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/${PKGNAME}
@@ -124,3 +134,19 @@ uninstall:
 	rm -f $(DESTDIR)$(PREFIX)/share/bash-completion/completions/${PKGNAME}
 	rm -f $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_${PKGNAME}
 	rm -f $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/${PKGNAME}.fish
+	for lang in ${LANGS}; do \
+		rm -f $(DESTDIR)$(PREFIX)/share/locale/$$lang/LC_MESSAGES/${PKGNAME}.mo; \
+	done
+
+locale: ${MOFILES}
+
+${LOCALEDIR}/${POTFILE}: ${GOFILES}
+	xgettext --from-code=UTF-8 -Lc -sc -d ${PKGNAME} -kGet -o locale/${PKGNAME}.pot ${GOFILES}
+
+${LOCALEDIR}/%.po: ${LOCALEDIR}/${POTFILE}
+	test -f $@ || msginit -l $* -i $< -o $@
+	msgmerge -U $@ $<
+	touch $@
+
+${LOCALEDIR}/%.mo: ${LOCALEDIR}/%.po
+	msgfmt $< -o $@

+ 13 - 11
callbacks.go

@@ -7,6 +7,9 @@ import (
 	"strconv"
 
 	alpm "github.com/Jguer/go-alpm"
+	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 func questionCallback(question alpm.QuestionAny) {
@@ -30,8 +33,7 @@ func questionCallback(question alpm.QuestionAny) {
 		return nil
 	})
 
-	fmt.Print(bold(cyan(":: ")))
-	str := bold(fmt.Sprintf(bold("There are %d providers available for %s:"), size, qp.Dep()))
+	str := gotext.Get("There are %d providers available for %s:", size, qp.Dep())
 
 	size = 1
 	var db string
@@ -41,17 +43,17 @@ func questionCallback(question alpm.QuestionAny) {
 
 		if db != thisDB {
 			db = thisDB
-			str += bold(cyan("\n:: ")) + bold("Repository "+db+"\n    ")
+			str += bold(cyan("\n:: ")) + bold(gotext.Get("Repository ")+db+"\n    ")
 		}
 		str += fmt.Sprintf("%d) %s ", size, pkg.Name())
 		size++
 		return nil
 	})
 
-	fmt.Println(str)
+	text.OperationInfoln(str)
 
 	for {
-		fmt.Print("\nEnter a number (default=1): ")
+		fmt.Print(gotext.Get("\nEnter a number (default=1): "))
 
 		if config.NoConfirm {
 			fmt.Println()
@@ -62,12 +64,12 @@ func questionCallback(question alpm.QuestionAny) {
 		numberBuf, overflow, err := reader.ReadLine()
 
 		if err != nil {
-			fmt.Fprintln(os.Stderr, err)
+			text.Errorln(err)
 			break
 		}
 
 		if overflow {
-			fmt.Fprintln(os.Stderr, "Input too long")
+			text.Errorln(gotext.Get(" Input too long"))
 			continue
 		}
 
@@ -77,12 +79,12 @@ func questionCallback(question alpm.QuestionAny) {
 
 		num, err := strconv.Atoi(string(numberBuf))
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "%s invalid number: %s\n", red("error:"), string(numberBuf))
+			text.Errorln(gotext.Get("invalid number: %s", string(numberBuf)))
 			continue
 		}
 
 		if num < 1 || num > size {
-			fmt.Fprintf(os.Stderr, "%s invalid value: %d is not between %d and %d\n", red("error:"), num, 1, size)
+			text.Errorln(gotext.Get("invalid value: %d is not between %d and %d", num, 1, size))
 			continue
 		}
 
@@ -94,8 +96,8 @@ func questionCallback(question alpm.QuestionAny) {
 func logCallback(level alpm.LogLevel, str string) {
 	switch level {
 	case alpm.LogWarning:
-		fmt.Print(bold(yellow(smallArrow)), " ", str)
+		text.Warnln(str)
 	case alpm.LogError:
-		fmt.Print(bold(red(smallArrow)), " ", str)
+		text.Errorln(str)
 	}
 }

+ 14 - 10
clean.go

@@ -6,7 +6,10 @@ import (
 	"os"
 	"path/filepath"
 
+	"github.com/leonelquinteros/gotext"
+
 	"github.com/Jguer/yay/v9/pkg/stringset"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 // GetPkgbuild gets the pkgbuild of the package 'pkg' trying the ABS first and then the AUR trying the ABS first and then the AUR.
@@ -83,12 +86,12 @@ func syncClean(parser *arguments) error {
 
 	var question string
 	if removeAll {
-		question = "Do you want to remove ALL AUR packages from cache?"
+		question = gotext.Get("Do you want to remove ALL AUR packages from cache?")
 	} else {
-		question = "Do you want to remove all other AUR packages from cache?"
+		question = gotext.Get("Do you want to remove all other AUR packages from cache?")
 	}
 
-	fmt.Printf("\nBuild directory: %s\n", config.BuildDir)
+	fmt.Println(gotext.Get("\nBuild directory:"), config.BuildDir)
 
 	if continueTask(question, true) {
 		if err := cleanAUR(keepInstalled, keepCurrent, removeAll); err != nil {
@@ -100,7 +103,7 @@ func syncClean(parser *arguments) error {
 		return nil
 	}
 
-	if continueTask("Do you want to remove ALL untracked AUR files?", true) {
+	if continueTask(gotext.Get("Do you want to remove ALL untracked AUR files?"), true) {
 		return cleanUntracked()
 	}
 
@@ -108,7 +111,7 @@ func syncClean(parser *arguments) error {
 }
 
 func cleanAUR(keepInstalled, keepCurrent, removeAll bool) error {
-	fmt.Println("removing AUR packages from cache...")
+	fmt.Println(gotext.Get("removing AUR packages from cache..."))
 
 	installedBases := make(stringset.StringSet)
 	inAURBases := make(stringset.StringSet)
@@ -180,7 +183,7 @@ func cleanAUR(keepInstalled, keepCurrent, removeAll bool) error {
 }
 
 func cleanUntracked() error {
-	fmt.Println("removing Untracked AUR files from cache...")
+	fmt.Println(gotext.Get("removing untracked AUR files from cache..."))
 
 	files, err := ioutil.ReadDir(config.BuildDir)
 	if err != nil {
@@ -202,15 +205,16 @@ func cleanUntracked() error {
 }
 
 func cleanAfter(bases []Base) {
-	fmt.Println("removing Untracked AUR files from cache...")
+	fmt.Println(gotext.Get("removing Untracked AUR files from cache..."))
 
 	for i, base := range bases {
 		dir := filepath.Join(config.BuildDir, base.Pkgbase())
 
-		fmt.Printf(bold(cyan("::")+" Cleaning (%d/%d): %s\n"), i+1, len(bases), cyan(dir))
+		text.OperationInfoln(gotext.Get("Cleaning (%d/%d): %s", i+1, len(bases), cyan(dir)))
+
 		_, stderr, err := capture(passToGit(dir, "reset", "--hard", "HEAD"))
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "error resetting %s: %s", base.String(), stderr)
+			text.Errorln(gotext.Get("error resetting %s: %s", base.String(), stderr))
 		}
 
 		if err := show(passToGit(dir, "clean", "-fx")); err != nil {
@@ -222,7 +226,7 @@ func cleanAfter(bases []Base) {
 func cleanBuilds(bases []Base) {
 	for i, base := range bases {
 		dir := filepath.Join(config.BuildDir, base.Pkgbase())
-		fmt.Printf(bold(cyan("::")+" Deleting (%d/%d): %s\n"), i+1, len(bases), cyan(dir))
+		text.OperationInfoln(gotext.Get("Deleting (%d/%d): %s", i+1, len(bases), cyan(dir)))
 		if err := os.RemoveAll(dir); err != nil {
 			fmt.Fprintln(os.Stderr, err)
 		}

+ 18 - 18
cmd.go

@@ -7,9 +7,11 @@ import (
 	"os"
 
 	alpm "github.com/Jguer/go-alpm"
+	"github.com/leonelquinteros/gotext"
 
 	"github.com/Jguer/yay/v9/pkg/completion"
 	"github.com/Jguer/yay/v9/pkg/intrange"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 var cmdArgs = makeArguments()
@@ -170,7 +172,7 @@ func handleCmd() error {
 		return handleYay()
 	}
 
-	return fmt.Errorf("unhandled operation")
+	return fmt.Errorf(gotext.Get("unhandled operation"))
 }
 
 func handleQuery() error {
@@ -291,7 +293,7 @@ func handleRemove() error {
 }
 
 // NumberMenu presents a CLI for selecting packages to install.
-func displayNumberMenu(pkgS []string) (err error) {
+func displayNumberMenu(pkgS []string) error {
 	var (
 		aurErr, repoErr error
 		aq              aurQuery
@@ -309,12 +311,12 @@ func displayNumberMenu(pkgS []string) (err error) {
 		pq, repoErr = queryRepo(pkgS)
 		lenpq = len(pq)
 		if repoErr != nil {
-			return err
+			return repoErr
 		}
 	}
 
 	if lenpq == 0 && lenaq == 0 {
-		return fmt.Errorf("no packages match search")
+		return fmt.Errorf(gotext.Get("no packages match search"))
 	}
 
 	switch config.SortMode {
@@ -333,16 +335,16 @@ func displayNumberMenu(pkgS []string) (err error) {
 			pq.printSearch()
 		}
 	default:
-		return fmt.Errorf("invalid sort mode. Fix with yay -Y --bottomup --save")
+		return fmt.Errorf(gotext.Get("invalid sort mode. Fix with yay -Y --bottomup --save"))
 	}
 
 	if aurErr != nil {
-		fmt.Fprintf(os.Stderr, "Error during AUR search: %s\n", aurErr)
-		fmt.Fprintln(os.Stderr, "Showing repo packages only")
+		text.Errorln(gotext.Get("Error during AUR search: %s\n", aurErr))
+		text.Warnln(gotext.Get("Showing repo packages only"))
 	}
 
-	fmt.Println(bold(green(arrow + " Packages to install (eg: 1 2 3, 1-3 or ^4)")))
-	fmt.Print(bold(green(arrow + " ")))
+	text.Infoln(gotext.Get("Packages to install (eg: 1 2 3, 1-3 or ^4)"))
+	text.Info()
 
 	reader := bufio.NewReader(os.Stdin)
 
@@ -351,7 +353,7 @@ func displayNumberMenu(pkgS []string) (err error) {
 		return err
 	}
 	if overflow {
-		return fmt.Errorf("input too long")
+		return fmt.Errorf(gotext.Get("input too long"))
 	}
 
 	include, exclude, _, otherExclude := intrange.ParseNumberMenu(string(numberBuf))
@@ -367,7 +369,7 @@ func displayNumberMenu(pkgS []string) (err error) {
 		case bottomUp:
 			target = len(pq) - i
 		default:
-			return fmt.Errorf("invalid sort mode. Fix with yay -Y --bottomup --save")
+			return fmt.Errorf(gotext.Get("invalid sort mode. Fix with yay -Y --bottomup --save"))
 		}
 
 		if (isInclude && include.Get(target)) || (!isInclude && !exclude.Get(target)) {
@@ -384,7 +386,7 @@ func displayNumberMenu(pkgS []string) (err error) {
 		case bottomUp:
 			target = len(aq) - i + len(pq)
 		default:
-			return fmt.Errorf("invalid sort mode. Fix with yay -Y --bottomup --save")
+			return fmt.Errorf(gotext.Get("invalid sort mode. Fix with yay -Y --bottomup --save"))
 		}
 
 		if (isInclude && include.Get(target)) || (!isInclude && !exclude.Get(target)) {
@@ -393,7 +395,7 @@ func displayNumberMenu(pkgS []string) (err error) {
 	}
 
 	if len(arguments.targets) == 0 {
-		fmt.Println("There is nothing to do")
+		fmt.Println(gotext.Get("There is nothing to do"))
 		return nil
 	}
 
@@ -401,9 +403,7 @@ func displayNumberMenu(pkgS []string) (err error) {
 		sudoLoopBackground()
 	}
 
-	err = install(arguments)
-
-	return err
+	return install(arguments)
 }
 
 func syncList(parser *arguments) error {
@@ -436,10 +436,10 @@ func syncList(parser *arguments) error {
 			if cmdArgs.existsArg("q", "quiet") {
 				fmt.Println(name)
 			} else {
-				fmt.Printf("%s %s %s", magenta("aur"), bold(name), bold(green("unknown-version")))
+				fmt.Printf("%s %s %s", magenta("aur"), bold(name), bold(green(gotext.Get("unknown-version"))))
 
 				if localDB.Pkg(name) != nil {
-					fmt.Print(bold(blue(" [Installed]")))
+					fmt.Print(bold(blue(gotext.Get(" [Installed]"))))
 				}
 
 				fmt.Println()

+ 13 - 13
config.go

@@ -11,6 +11,9 @@ import (
 
 	alpm "github.com/Jguer/go-alpm"
 	pacmanconf "github.com/Morganamilo/go-pacmanconf"
+	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 // Verbosity settings for search
@@ -124,6 +127,8 @@ var mode = modeAny
 
 var hideMenus = false
 
+var localePath = "/usr/share/locale"
+
 // SaveConfig writes yay config to file.
 func (config *Configuration) saveConfig() error {
 	marshalledinfo, err := json.MarshalIndent(config, "", "\t")
@@ -255,17 +260,11 @@ func editor() (editor string, args []string) {
 		fallthrough
 	default:
 		fmt.Fprintln(os.Stderr)
-		fmt.Fprintln(os.Stderr, bold(red(arrow)), bold(cyan("$EDITOR")), bold("is not set"))
-		fmt.Fprintln(os.Stderr,
-			bold(red(arrow))+
-				bold(" Please add ")+
-				bold(cyan("$EDITOR"))+
-				bold(" or ")+
-				bold(cyan("$VISUAL"))+
-				bold(" to your environment variables."))
+		text.Errorln(gotext.Get("%s is not set", bold(cyan("$EDITOR"))))
+		text.Warnln(gotext.Get("Add %s or %s to your environment variables", bold(cyan("$EDITOR")), bold(cyan("$VISUAL"))))
 
 		for {
-			fmt.Print(green(bold(arrow + " Edit PKGBUILD with: ")))
+			text.Infoln(gotext.Get("Edit PKGBUILD with?"))
 			editorInput, err := getInput("")
 			if err != nil {
 				fmt.Fprintln(os.Stderr, err)
@@ -296,8 +295,8 @@ func continueTask(s string, cont bool) bool {
 
 	var response string
 	var postFix string
-	yes := "yes"
-	no := "no"
+	yes := gotext.Get("yes")
+	no := gotext.Get("no")
 	y := string([]rune(yes)[0])
 	n := string([]rune(no)[0])
 
@@ -307,7 +306,7 @@ func continueTask(s string, cont bool) bool {
 		postFix = fmt.Sprintf(" [%s/%s] ", y, strings.ToUpper(n))
 	}
 
-	fmt.Print(bold(green(arrow)+" "+s), bold(postFix))
+	text.Info(bold(s), bold(postFix))
 
 	if _, err := fmt.Scanln(&response); err != nil {
 		return cont
@@ -318,6 +317,7 @@ func continueTask(s string, cont bool) bool {
 }
 
 func getInput(defaultValue string) (string, error) {
+	text.Info()
 	if defaultValue != "" || config.NoConfirm {
 		fmt.Println(defaultValue)
 		return defaultValue, nil
@@ -331,7 +331,7 @@ func getInput(defaultValue string) (string, error) {
 	}
 
 	if overflow {
-		return "", fmt.Errorf("input too long")
+		return "", fmt.Errorf(gotext.Get("input too long"))
 	}
 
 	return string(buf), nil

+ 16 - 18
depCheck.go

@@ -7,8 +7,10 @@ import (
 	"sync"
 
 	alpm "github.com/Jguer/go-alpm"
+	"github.com/leonelquinteros/gotext"
 
 	"github.com/Jguer/yay/v9/pkg/stringset"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 func (dp *depPool) checkInnerConflict(name, conflict string, conflicts stringset.MapStringSet) {
@@ -132,14 +134,14 @@ func (dp *depPool) CheckConflicts() (stringset.MapStringSet, error) {
 	conflicts := make(stringset.MapStringSet)
 	wg.Add(2)
 
-	fmt.Println(bold(cyan("::") + bold(" Checking for conflicts...")))
+	text.OperationInfoln(gotext.Get("Checking for conflicts..."))
 	go func() {
 		dp.checkForwardConflicts(conflicts)
 		dp.checkReverseConflicts(conflicts)
 		wg.Done()
 	}()
 
-	fmt.Println(bold(cyan("::") + bold(" Checking for inner conflicts...")))
+	text.OperationInfoln(gotext.Get("Checking for inner conflicts..."))
 	go func() {
 		dp.checkInnerConflicts(innerConflicts)
 		wg.Done()
@@ -148,8 +150,7 @@ func (dp *depPool) CheckConflicts() (stringset.MapStringSet, error) {
 	wg.Wait()
 
 	if len(innerConflicts) != 0 {
-		fmt.Println()
-		fmt.Println(bold(red(arrow)), bold("Inner conflicts found:"))
+		text.Errorln(gotext.Get("\nInner conflicts found:"))
 
 		for name, pkgs := range innerConflicts {
 			str := red(bold(smallArrow)) + " " + name + ":"
@@ -163,11 +164,10 @@ func (dp *depPool) CheckConflicts() (stringset.MapStringSet, error) {
 	}
 
 	if len(conflicts) != 0 {
-		fmt.Println()
-		fmt.Println(bold(red(arrow)), bold("Package conflicts found:"))
+		text.Errorln(gotext.Get("\nPackage conflicts found:"))
 
 		for name, pkgs := range conflicts {
-			str := red(bold(smallArrow)) + " Installing " + cyan(name) + " will remove:"
+			str := gotext.Get("%s Installing %s will remove:", red(bold(smallArrow)), cyan(name))
 			for pkg := range pkgs {
 				str += " " + cyan(pkg) + ","
 			}
@@ -190,12 +190,10 @@ func (dp *depPool) CheckConflicts() (stringset.MapStringSet, error) {
 	if len(conflicts) > 0 {
 		if !config.UseAsk {
 			if config.NoConfirm {
-				return nil, fmt.Errorf("package conflicts can not be resolved with noconfirm, aborting")
+				return nil, fmt.Errorf(gotext.Get("package conflicts can not be resolved with noconfirm, aborting"))
 			}
 
-			fmt.Fprintln(os.Stderr)
-			fmt.Fprintln(os.Stderr, bold(red(arrow)), bold("Conflicting packages will have to be confirmed manually"))
-			fmt.Fprintln(os.Stderr)
+			text.Errorln(gotext.Get("Conflicting packages will have to be confirmed manually"))
 		}
 	}
 
@@ -272,22 +270,22 @@ func (dp *depPool) CheckMissing() error {
 		return nil
 	}
 
-	fmt.Println(bold(red(arrow+" Error: ")) + "Could not find all required packages:")
+	text.Errorln(gotext.Get("Could not find all required packages:"))
 	for dep, trees := range missing.Missing {
 		for _, tree := range trees {
-			fmt.Print("    ", cyan(dep))
+			fmt.Fprintf(os.Stderr, "\t%s", cyan(dep))
 
 			if len(tree) == 0 {
-				fmt.Print(" (Target")
+				fmt.Fprint(os.Stderr, gotext.Get(" (Target"))
 			} else {
-				fmt.Print(" (Wanted by: ")
+				fmt.Fprint(os.Stderr, gotext.Get(" (Wanted by: "))
 				for n := 0; n < len(tree)-1; n++ {
-					fmt.Print(cyan(tree[n]), " -> ")
+					fmt.Fprint(os.Stderr, cyan(tree[n]), " -> ")
 				}
-				fmt.Print(cyan(tree[len(tree)-1]))
+				fmt.Fprint(os.Stderr, cyan(tree[len(tree)-1]))
 			}
 
-			fmt.Println(")")
+			fmt.Fprintln(os.Stderr, ")")
 		}
 	}
 

+ 21 - 17
download.go

@@ -9,8 +9,11 @@ import (
 	"sync"
 
 	alpm "github.com/Jguer/go-alpm"
+	"github.com/leonelquinteros/gotext"
+	"github.com/pkg/errors"
 
 	"github.com/Jguer/yay/v9/pkg/multierror"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 const gitDiffRefName = "AUR_SEEN"
@@ -20,7 +23,7 @@ const gitDiffRefName = "AUR_SEEN"
 func gitUpdateSeenRef(path, name string) error {
 	_, stderr, err := capture(passToGit(filepath.Join(path, name), "update-ref", gitDiffRefName, "HEAD"))
 	if err != nil {
-		return fmt.Errorf("%s%s", stderr, err)
+		return fmt.Errorf("%s %s", stderr, err)
 	}
 	return nil
 }
@@ -38,7 +41,7 @@ func getLastSeenHash(path, name string) (string, error) {
 	if gitHasLastSeenRef(path, name) {
 		stdout, stderr, err := capture(passToGit(filepath.Join(path, name), "rev-parse", gitDiffRefName))
 		if err != nil {
-			return "", fmt.Errorf("%s%s", stderr, err)
+			return "", fmt.Errorf("%s %s", stderr, err)
 		}
 
 		lines := strings.Split(stdout, "\n")
@@ -78,19 +81,19 @@ func gitDownloadABS(url, path, name string) (bool, error) {
 		cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
 		_, stderr, err := capture(cmd)
 		if err != nil {
-			return false, fmt.Errorf("error cloning %s: %s", name, stderr)
+			return false, fmt.Errorf(gotext.Get("error cloning %s: %s", name, stderr))
 		}
 
 		return true, nil
 	} else if errExist != nil {
-		return false, fmt.Errorf("error reading %s", filepath.Join(path, name, ".git"))
+		return false, fmt.Errorf(gotext.Get("error reading %s", filepath.Join(path, name, ".git")))
 	}
 
 	cmd := passToGit(filepath.Join(path, name), "pull", "--ff-only")
 	cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
 	_, stderr, err := capture(cmd)
 	if err != nil {
-		return false, fmt.Errorf("error fetching %s: %s", name, stderr)
+		return false, fmt.Errorf(gotext.Get("error fetching %s: %s", name, stderr))
 	}
 
 	return true, nil
@@ -103,19 +106,19 @@ func gitDownload(url, path, name string) (bool, error) {
 		cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
 		_, stderr, errCapture := capture(cmd)
 		if errCapture != nil {
-			return false, fmt.Errorf("error cloning %s: %s", name, stderr)
+			return false, fmt.Errorf(gotext.Get("error cloning %s: %s", name, stderr))
 		}
 
 		return true, nil
 	} else if err != nil {
-		return false, fmt.Errorf("error reading %s", filepath.Join(path, name, ".git"))
+		return false, fmt.Errorf(gotext.Get("error reading %s", filepath.Join(path, name, ".git")))
 	}
 
 	cmd := passToGit(filepath.Join(path, name), "fetch")
 	cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
 	_, stderr, err := capture(cmd)
 	if err != nil {
-		return false, fmt.Errorf("error fetching %s: %s", name, stderr)
+		return false, fmt.Errorf(gotext.Get("error fetching %s: %s", name, stderr))
 	}
 
 	return false, nil
@@ -124,12 +127,12 @@ func gitDownload(url, path, name string) (bool, error) {
 func gitMerge(path, name string) error {
 	_, stderr, err := capture(passToGit(filepath.Join(path, name), "reset", "--hard", "HEAD"))
 	if err != nil {
-		return fmt.Errorf("error resetting %s: %s", name, stderr)
+		return fmt.Errorf(gotext.Get("error resetting %s: %s", name, stderr))
 	}
 
 	_, stderr, err = capture(passToGit(filepath.Join(path, name), "merge", "--no-edit", "--ff"))
 	if err != nil {
-		return fmt.Errorf("error merging %s: %s", name, stderr)
+		return fmt.Errorf(gotext.Get("error merging %s: %s", name, stderr))
 	}
 
 	return nil
@@ -175,11 +178,11 @@ func getPkgbuilds(pkgs []string) error {
 			_, err = os.Stat(filepath.Join(wd, name))
 			switch {
 			case err != nil && !os.IsNotExist(err):
-				fmt.Fprintln(os.Stderr, bold(red(smallArrow)), err)
+				text.Errorln(err)
 				continue
 			default:
 				if err = os.RemoveAll(filepath.Join(wd, name)); err != nil {
-					fmt.Fprintln(os.Stderr, bold(red(smallArrow)), err)
+					text.Errorln(err)
 					continue
 				}
 			}
@@ -267,7 +270,7 @@ func getPkgbuildsfromABS(pkgs []string, path string) (bool, error) {
 				continue
 			}
 		default:
-			fmt.Printf("%s %s %s\n", yellow(smallArrow), cyan(name), "already downloaded -- use -f to overwrite")
+			text.Warn(gotext.Get("%s already downloaded -- use -f to overwrite", cyan(name)))
 			continue
 		}
 
@@ -275,13 +278,14 @@ func getPkgbuildsfromABS(pkgs []string, path string) (bool, error) {
 	}
 
 	if len(missing) != 0 {
-		fmt.Println(yellow(bold(smallArrow)), "Missing ABS packages: ", cyan(strings.Join(missing, "  ")))
+		text.Warnln(gotext.Get("Missing ABS packages:"),
+			cyan(strings.Join(missing, ", ")))
 	}
 
 	download := func(pkg string, url string) {
 		defer wg.Done()
 		if _, err := gitDownloadABS(url, config.ABSDir, pkg); err != nil {
-			errs.Add(fmt.Errorf("%s Failed to get pkgbuild: %s: %s", bold(red(arrow)), bold(cyan(pkg)), bold(red(err.Error()))))
+			errs.Add(errors.New(gotext.Get("failed to get pkgbuild: %s: %s", cyan(pkg), err.Error())))
 			return
 		}
 
@@ -289,9 +293,9 @@ func getPkgbuildsfromABS(pkgs []string, path string) (bool, error) {
 		mux.Lock()
 		downloaded++
 		if err != nil {
-			errs.Add(fmt.Errorf("%s Failed to link %s: %s", bold(red(arrow)), bold(cyan(pkg)), bold(red(stderr))))
+			errs.Add(errors.New(gotext.Get("failed to link %s: %s", cyan(pkg), stderr)))
 		} else {
-			fmt.Printf(bold(cyan("::"))+" Downloaded PKGBUILD from ABS (%d/%d): %s\n", downloaded, len(names), cyan(pkg))
+			fmt.Fprintln(os.Stdout, gotext.Get("(%d/%d) Downloaded PKGBUILD from ABS: %s", downloaded, len(names), cyan(pkg)))
 		}
 		mux.Unlock()
 	}

+ 5 - 3
exec.go

@@ -9,7 +9,10 @@ import (
 	"strings"
 	"time"
 
+	"github.com/leonelquinteros/gotext"
 	"golang.org/x/crypto/ssh/terminal"
+
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 func show(cmd *exec.Cmd) error {
@@ -64,9 +67,8 @@ func waitLock() {
 		return
 	}
 
-	fmt.Println(bold(yellow(smallArrow)), filepath.Join(pacmanConf.DBPath, "db.lck"), "is present.")
-
-	fmt.Print(bold(yellow(smallArrow)), " There may be another Pacman instance running. Waiting...")
+	text.Warnln(gotext.Get("%s is present.", filepath.Join(pacmanConf.DBPath, "db.lck")))
+	text.Warn(gotext.Get("There may be another Pacman instance running. Waiting..."))
 
 	for {
 		time.Sleep(3 * time.Second)

+ 3 - 1
go.mod

@@ -4,9 +4,11 @@ require (
 	github.com/Jguer/go-alpm v0.0.0-20200405152916-a3feea4322e9
 	github.com/Morganamilo/go-pacmanconf v0.0.0-20180910220353-9c5265e1b14f
 	github.com/Morganamilo/go-srcinfo v1.0.0
+	github.com/leonelquinteros/gotext v1.4.0
 	github.com/mikkeloscar/aur v0.0.0-20200113170522-1cb4e2949656
+	github.com/pkg/errors v0.9.1
 	golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
 	golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect
 )
 
-go 1.13
+go 1.14

+ 6 - 0
go.sum

@@ -4,8 +4,14 @@ github.com/Morganamilo/go-pacmanconf v0.0.0-20180910220353-9c5265e1b14f h1:ptFKy
 github.com/Morganamilo/go-pacmanconf v0.0.0-20180910220353-9c5265e1b14f/go.mod h1:Hk55m330jNiwxRodIlMCvw5iEyoRUCIY64W1p9D+tHc=
 github.com/Morganamilo/go-srcinfo v1.0.0 h1:Wh4nEF+HJWo+29hnxM18Q2hi+DUf0GejS13+Wg+dzmI=
 github.com/Morganamilo/go-srcinfo v1.0.0/go.mod h1:MP6VGY1NNpVUmYIEgoM9acix95KQqIRyqQ0hCLsyYUY=
+github.com/leonelquinteros/gotext v1.4.0 h1:2NHPCto5IoMXbrT0bldPrxj0qM5asOCwtb1aUQZ1tys=
+github.com/leonelquinteros/gotext v1.4.0/go.mod h1:yZGXREmoGTtBvZHNcc+Yfug49G/2spuF/i/Qlsvz1Us=
 github.com/mikkeloscar/aur v0.0.0-20200113170522-1cb4e2949656 h1:j679+jxcDkCFblYk+I+G71HQTFxM3PacYbVCiYmhRhU=
 github.com/mikkeloscar/aur v0.0.0-20200113170522-1cb4e2949656/go.mod h1:nYOKcK8tIj69ZZ8uDOWoiT+L25NvlOQaraDqTec/idA=
+github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
 golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

+ 55 - 55
install.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"os/exec"
@@ -11,11 +12,13 @@ import (
 
 	alpm "github.com/Jguer/go-alpm"
 	gosrc "github.com/Morganamilo/go-srcinfo"
+	"github.com/leonelquinteros/gotext"
 
 	"github.com/Jguer/yay/v9/pkg/completion"
 	"github.com/Jguer/yay/v9/pkg/intrange"
 	"github.com/Jguer/yay/v9/pkg/multierror"
 	"github.com/Jguer/yay/v9/pkg/stringset"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 func asdeps(parser *arguments, pkgs []string) error {
@@ -28,7 +31,7 @@ func asdeps(parser *arguments, pkgs []string) error {
 	parser.addTarget(pkgs...)
 	_, stderr, err := capture(passToPacman(parser))
 	if err != nil {
-		return fmt.Errorf("%s%s", stderr, err)
+		return fmt.Errorf("%s %s", stderr, err)
 	}
 
 	return nil
@@ -44,7 +47,7 @@ func asexp(parser *arguments, pkgs []string) error {
 	parser.addTarget(pkgs...)
 	_, stderr, err := capture(passToPacman(parser))
 	if err != nil {
-		return fmt.Errorf("%s%s", stderr, err)
+		return fmt.Errorf("%s %s", stderr, err)
 	}
 
 	return nil
@@ -67,7 +70,7 @@ func install(parser *arguments) (err error) {
 			if parser.existsArg("y", "refresh") {
 				err = earlyRefresh(parser)
 				if err != nil {
-					return fmt.Errorf("error refreshing databases")
+					return fmt.Errorf(gotext.Get("error refreshing databases"))
 				}
 			}
 		} else if parser.existsArg("y", "refresh") || parser.existsArg("u", "sysupgrade") || len(parser.targets) > 0 {
@@ -158,7 +161,7 @@ func install(parser *arguments) (err error) {
 	if len(dp.Aur) == 0 {
 		if !config.CombinedUpgrade {
 			if parser.existsArg("u", "sysupgrade") {
-				fmt.Println(" there is nothing to do")
+				fmt.Println(gotext.Get(" there is nothing to do"))
 			}
 			return nil
 		}
@@ -170,7 +173,7 @@ func install(parser *arguments) (err error) {
 	}
 
 	if len(dp.Aur) > 0 && os.Geteuid() == 0 {
-		return fmt.Errorf(bold(red(arrow)) + " Refusing to install AUR Packages as root, Aborting.")
+		return fmt.Errorf(gotext.Get("refusing to install AUR packages as root, aborting"))
 	}
 
 	conflicts, err := dp.CheckConflicts()
@@ -192,7 +195,7 @@ func install(parser *arguments) (err error) {
 	}
 
 	if len(do.Aur) == 0 && len(arguments.targets) == 0 && (!parser.existsArg("u", "sysupgrade") || mode == modeAUR) {
-		fmt.Println(" there is nothing to do")
+		fmt.Println(gotext.Get(" there is nothing to do"))
 		return nil
 	}
 
@@ -213,7 +216,7 @@ func install(parser *arguments) (err error) {
 		case "no":
 			break
 		default:
-			if continueTask("Remove make dependencies after install?", false) {
+			if continueTask(gotext.Get("Remove make dependencies after install?"), false) {
 				defer func() {
 					err = removeMake(do)
 				}()
@@ -261,12 +264,12 @@ func install(parser *arguments) (err error) {
 		oldValue := config.NoConfirm
 		config.NoConfirm = false
 		fmt.Println()
-		if !continueTask(bold(green("Proceed with install?")), true) {
-			return fmt.Errorf("aborting due to user")
+		if !continueTask(gotext.Get("Proceed with install?"), true) {
+			return fmt.Errorf(gotext.Get("aborting due to user"))
 		}
 		err = updatePkgbuildSeenRef(toDiff)
 		if err != nil {
-			fmt.Fprintln(os.Stderr, err.Error())
+			text.Errorln(err.Error())
 		}
 
 		config.NoConfirm = oldValue
@@ -301,8 +304,8 @@ func install(parser *arguments) (err error) {
 		oldValue := config.NoConfirm
 		config.NoConfirm = false
 		fmt.Println()
-		if !continueTask(bold(green("Proceed with install?")), true) {
-			return fmt.Errorf("aborting due to user")
+		if !continueTask(gotext.Get("Proceed with install?"), true) {
+			return errors.New(gotext.Get("aborting due to user"))
 		}
 		config.NoConfirm = oldValue
 	}
@@ -325,7 +328,7 @@ func install(parser *arguments) (err error) {
 
 	if len(arguments.targets) > 0 || arguments.existsArg("u") {
 		if errShow := show(passToPacman(arguments)); errShow != nil {
-			return fmt.Errorf("error installing repo packages")
+			return errors.New(gotext.Get("error installing repo packages"))
 		}
 
 		deps := make([]string, 0)
@@ -434,7 +437,7 @@ func earlyPacmanCall(parser *arguments) error {
 	if parser.existsArg("y", "refresh") || parser.existsArg("u", "sysupgrade") || len(arguments.targets) > 0 {
 		err = show(passToPacman(arguments))
 		if err != nil {
-			return fmt.Errorf("error installing repo packages")
+			return errors.New(gotext.Get("error installing repo packages"))
 		}
 	}
 
@@ -473,16 +476,15 @@ nextpkg:
 	}
 
 	if len(incompatible) > 0 {
-		fmt.Println()
-		fmt.Print(bold(yellow(arrow)) + " The following packages are not compatible with your architecture:")
+		text.Warnln(gotext.Get("The following packages are not compatible with your architecture:"))
 		for pkg := range incompatible {
 			fmt.Print("  " + cyan(basesMap[pkg].String()))
 		}
 
 		fmt.Println()
 
-		if !continueTask("Try to build them anyway?", true) {
-			return nil, fmt.Errorf("aborting due to user")
+		if !continueTask(gotext.Get("Try to build them anyway?"), true) {
+			return nil, errors.New(gotext.Get("aborting due to user"))
 		}
 	}
 
@@ -493,7 +495,7 @@ func parsePackageList(dir string) (pkgdests map[string]string, pkgVersion string
 	stdout, stderr, err := capture(passToMakepkg(dir, "--packagelist"))
 
 	if err != nil {
-		return nil, "", fmt.Errorf("%s%s", stderr, err)
+		return nil, "", fmt.Errorf("%s %s", stderr, err)
 	}
 
 	lines := strings.Split(stdout, "\n")
@@ -508,7 +510,7 @@ func parsePackageList(dir string) (pkgdests map[string]string, pkgVersion string
 		split := strings.Split(fileName, "-")
 
 		if len(split) < 4 {
-			return nil, "", fmt.Errorf("cannot find package name : %s", split)
+			return nil, "", errors.New(gotext.Get("cannot find package name: %v", split))
 		}
 
 		// pkgname-pkgver-pkgrel-arch.pkgext
@@ -552,11 +554,11 @@ func pkgbuildNumberMenu(bases []Base, installed stringset.StringSet) bool {
 		}
 
 		if anyInstalled {
-			toPrint += bold(green(" (Installed)"))
+			toPrint += bold(green(gotext.Get(" (Installed)")))
 		}
 
 		if _, err := os.Stat(dir); !os.IsNotExist(err) {
-			toPrint += bold(green(" (Build Files Exist)"))
+			toPrint += bold(green(gotext.Get(" (Build Files Exist)")))
 			askClean = true
 		}
 
@@ -575,9 +577,8 @@ func cleanNumberMenu(bases []Base, installed stringset.StringSet, hasClean bool)
 		return toClean, nil
 	}
 
-	fmt.Println(bold(green(arrow + " Packages to cleanBuild?")))
-	fmt.Println(bold(green(arrow) + cyan(" [N]one ") + "[A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)"))
-	fmt.Print(bold(green(arrow + " ")))
+	text.Infoln(gotext.Get("Packages to cleanBuild?"))
+	text.Infoln(gotext.Get("%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)", cyan(gotext.Get("[N]one"))))
 	cleanInput, err := getInput(config.AnswerClean)
 	if err != nil {
 		return nil, err
@@ -587,7 +588,7 @@ func cleanNumberMenu(bases []Base, installed stringset.StringSet, hasClean bool)
 	cIsInclude := len(cExclude) == 0 && len(cOtherExclude) == 0
 
 	if cOtherInclude.Get("abort") || cOtherInclude.Get("ab") {
-		return nil, fmt.Errorf("aborting due to user")
+		return nil, fmt.Errorf(gotext.Get("aborting due to user"))
 	}
 
 	if !cOtherInclude.Get("n") && !cOtherInclude.Get("none") {
@@ -651,17 +652,15 @@ func editDiffNumberMenu(bases []Base, installed stringset.StringSet, diff bool)
 	var err error
 
 	if diff {
-		fmt.Println(bold(green(arrow + " Diffs to show?")))
-		fmt.Println(bold(green(arrow) + cyan(" [N]one ") + "[A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)"))
-		fmt.Print(bold(green(arrow + " ")))
+		text.Infoln(gotext.Get("Diffs to show?"))
+		text.Infoln(gotext.Get("%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)", cyan(gotext.Get("[N]one"))))
 		editInput, err = getInput(config.AnswerDiff)
 		if err != nil {
 			return nil, err
 		}
 	} else {
-		fmt.Println(bold(green(arrow + " PKGBUILDs to edit?")))
-		fmt.Println(bold(green(arrow) + cyan(" [N]one ") + "[A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)"))
-		fmt.Print(bold(green(arrow + " ")))
+		text.Infoln(gotext.Get("PKGBUILDs to edit?"))
+		text.Infoln(gotext.Get("%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)", cyan(gotext.Get("[N]one"))))
 		editInput, err = getInput(config.AnswerEdit)
 		if err != nil {
 			return nil, err
@@ -672,7 +671,7 @@ func editDiffNumberMenu(bases []Base, installed stringset.StringSet, diff bool)
 	eIsInclude := len(eExclude) == 0 && len(eOtherExclude) == 0
 
 	if eOtherInclude.Get("abort") || eOtherInclude.Get("ab") {
-		return nil, fmt.Errorf("aborting due to user")
+		return nil, fmt.Errorf(gotext.Get("aborting due to user"))
 	}
 
 	if !eOtherInclude.Get("n") && !eOtherInclude.Get("none") {
@@ -748,7 +747,7 @@ func showPkgbuildDiffs(bases []Base, cloned stringset.StringSet) error {
 			}
 
 			if !hasDiff {
-				fmt.Printf("%s %s: %s\n", bold(yellow(arrow)), cyan(base.String()), bold("No changes -- skipping"))
+				text.Warnln(gotext.Get("%s: No changes -- skipping", cyan(base.String())))
 				continue
 			}
 		}
@@ -788,7 +787,7 @@ func editPkgbuilds(bases []Base, srcinfos map[string]*gosrc.Srcinfo) error {
 		editcmd.Stdin, editcmd.Stdout, editcmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 		err := editcmd.Run()
 		if err != nil {
-			return fmt.Errorf("editor did not exit successfully, Aborting: %s", err)
+			return errors.New(gotext.Get("editor did not exit successfully, aborting: %s", err))
 		}
 	}
 
@@ -801,16 +800,15 @@ func parseSrcinfoFiles(bases []Base, errIsFatal bool) (map[string]*gosrc.Srcinfo
 		pkg := base.Pkgbase()
 		dir := filepath.Join(config.BuildDir, pkg)
 
-		str := bold(cyan("::") + " Parsing SRCINFO (%d/%d): %s\n")
-		fmt.Printf(str, k+1, len(bases), cyan(base.String()))
+		text.OperationInfoln(gotext.Get("(%d/%d) Parsing SRCINFO: %s", k+1, len(bases), cyan(base.String())))
 
 		pkgbuild, err := gosrc.ParseFile(filepath.Join(dir, ".SRCINFO"))
 		if err != nil {
 			if !errIsFatal {
-				fmt.Fprintf(os.Stderr, "failed to parse %s -- skipping: %s\n", base.String(), err)
+				text.Warnln(gotext.Get("failed to parse %s -- skipping: %s", base.String(), err))
 				continue
 			}
-			return nil, fmt.Errorf("failed to parse %s: %s", base.String(), err)
+			return nil, errors.New(gotext.Get("failed to parse %s: %s", base.String(), err))
 		}
 
 		srcinfos[pkg] = pkgbuild
@@ -870,8 +868,9 @@ func downloadPkgbuilds(bases []Base, toSkip stringset.StringSet, buildDir string
 		if toSkip.Get(pkg) {
 			mux.Lock()
 			downloaded++
-			str := bold(cyan("::") + " PKGBUILD up to date, Skipping (%d/%d): %s\n")
-			fmt.Printf(str, downloaded, len(bases), cyan(base.String()))
+			text.OperationInfoln(
+				gotext.Get("PKGBUILD up to date, Skipping (%d/%d): %s",
+					downloaded, len(bases), cyan(base.String())))
 			mux.Unlock()
 			return
 		}
@@ -889,8 +888,7 @@ func downloadPkgbuilds(bases []Base, toSkip stringset.StringSet, buildDir string
 
 		mux.Lock()
 		downloaded++
-		str := bold(cyan("::") + " Downloaded PKGBUILD (%d/%d): %s\n")
-		fmt.Printf(str, downloaded, len(bases), cyan(base.String()))
+		text.OperationInfoln(gotext.Get("Downloaded PKGBUILD (%d/%d): %s", downloaded, len(bases), cyan(base.String())))
 		mux.Unlock()
 	}
 
@@ -921,7 +919,7 @@ func downloadPkgbuildsSources(bases []Base, incompatible stringset.StringSet) (e
 
 		err = show(passToMakepkg(dir, args...))
 		if err != nil {
-			return fmt.Errorf("error downloading sources: %s", cyan(base.String()))
+			return errors.New(gotext.Get("error downloading sources: %s", cyan(base.String())))
 		}
 	}
 
@@ -1005,7 +1003,7 @@ func buildInstallPkgbuilds(
 				for _, dep := range deps {
 					if _, errSatisfier := dp.LocalDB.PkgCache().FindSatisfier(dep); errSatisfier != nil {
 						satisfied = false
-						fmt.Printf("%s not satisfied, flushing install queue\n", dep)
+						text.Warnln(gotext.Get("%s not satisfied, flushing install queue", dep))
 						break all
 					}
 				}
@@ -1029,7 +1027,7 @@ func buildInstallPkgbuilds(
 
 		// pkgver bump
 		if err = show(passToMakepkg(dir, args...)); err != nil {
-			return fmt.Errorf("error making: %s", base.String())
+			return errors.New(gotext.Get("error making: %s", base.String()))
 		}
 
 		pkgdests, pkgVersion, errList := parsePackageList(dir)
@@ -1045,7 +1043,7 @@ func buildInstallPkgbuilds(
 			for _, split := range base {
 				pkgdest, ok := pkgdests[split.Name]
 				if !ok {
-					return fmt.Errorf("could not find PKGDEST for: %s", split.Name)
+					return errors.New(gotext.Get("could not find PKGDEST for: %s", split.Name))
 				}
 
 				if _, errStat := os.Stat(pkgdest); os.IsNotExist(errStat) {
@@ -1069,10 +1067,10 @@ func buildInstallPkgbuilds(
 			if installed {
 				err = show(passToMakepkg(dir, "-c", "--nobuild", "--noextract", "--ignorearch"))
 				if err != nil {
-					return fmt.Errorf("error making: %s", err)
+					return errors.New(gotext.Get("error making: %s", err))
 				}
 
-				fmt.Println(cyan(pkg+"-"+pkgVersion) + bold(" is up to date -- skipping"))
+				fmt.Fprintln(os.Stdout, gotext.Get("%s is up to date -- skipping"), cyan(pkg+"-"+pkgVersion))
 				continue
 			}
 		}
@@ -1080,11 +1078,10 @@ func buildInstallPkgbuilds(
 		if built {
 			err = show(passToMakepkg(dir, "-c", "--nobuild", "--noextract", "--ignorearch"))
 			if err != nil {
-				return fmt.Errorf("error making: %s", err)
+				return errors.New(gotext.Get("error making: %s", err))
 			}
 
-			fmt.Println(bold(yellow(arrow)),
-				cyan(pkg+"-"+pkgVersion)+bold(" already made -- skipping build"))
+			text.Warnln(gotext.Get("%s already made -- skipping build", cyan(pkg+"-"+pkgVersion)))
 		} else {
 			args := []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"}
 
@@ -1093,7 +1090,7 @@ func buildInstallPkgbuilds(
 			}
 
 			if errMake := show(passToMakepkg(dir, args...)); errMake != nil {
-				return fmt.Errorf("error making: %s", base.String())
+				return errors.New(gotext.Get("error making: %s", base.String))
 			}
 		}
 
@@ -1118,7 +1115,7 @@ func buildInstallPkgbuilds(
 					return nil
 				}
 
-				return fmt.Errorf("could not find PKGDEST for: %s", name)
+				return errors.New(gotext.Get("could not find PKGDEST for: %s", name))
 			}
 
 			if _, errStat := os.Stat(pkgdest); os.IsNotExist(errStat) {
@@ -1126,7 +1123,10 @@ func buildInstallPkgbuilds(
 					return nil
 				}
 
-				return fmt.Errorf("the PKGDEST for %s listed by makepkg, but does not exist: %s", name, pkgdest)
+				return errors.New(
+					gotext.Get(
+						"the PKGDEST for %s is listed by makepkg but does not exist: %s",
+						name, pkgdest))
 			}
 
 			arguments.addTarget(pkgdest)

+ 11 - 7
keys.go

@@ -2,12 +2,16 @@ package main
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"os"
 	"os/exec"
 	"strings"
 
 	gosrc "github.com/Morganamilo/go-srcinfo"
+	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 // pgpKeySet maps a PGP key with a list of PKGBUILDs that require it.
@@ -77,7 +81,7 @@ func checkPgpKeys(bases []Base, srcinfos map[string]*gosrc.Srcinfo) error {
 	fmt.Println()
 	fmt.Println(str)
 
-	if continueTask(bold(green("Import?")), true) {
+	if continueTask(gotext.Get("Import?"), true) {
 		return importKeys(problematic.toSlice())
 	}
 
@@ -90,11 +94,11 @@ func importKeys(keys []string) error {
 	cmd := exec.Command(config.GpgBin, append(args, keys...)...)
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 
-	fmt.Printf("%s %s...\n", bold(cyan("::")), bold("Importing keys with gpg..."))
+	text.OperationInfoln(gotext.Get("Importing keys with gpg..."))
 	err := cmd.Run()
 
 	if err != nil {
-		return fmt.Errorf("%s Problem importing keys", bold(red(arrow+" Error:")))
+		return errors.New(gotext.Get("problem importing keys"))
 	}
 	return nil
 }
@@ -103,19 +107,19 @@ func importKeys(keys []string) error {
 // question asking the user wants to import the problematic keys.
 func formatKeysToImport(keys pgpKeySet) (string, error) {
 	if len(keys) == 0 {
-		return "", fmt.Errorf("%s No keys to import", bold(red(arrow+" Error:")))
+		return "", errors.New(gotext.Get("no keys to import"))
 	}
 
 	var buffer bytes.Buffer
-	buffer.WriteString(bold(green(arrow)))
-	buffer.WriteString(bold(green(" PGP keys need importing:")))
+	buffer.WriteString(bold(green(arrow) + " "))
+	buffer.WriteString(bold(green(gotext.Get("PGP keys need importing:"))))
 	for key, bases := range keys {
 		pkglist := ""
 		for _, base := range bases {
 			pkglist += base.String() + "  "
 		}
 		pkglist = strings.TrimRight(pkglist, " ")
-		buffer.WriteString(fmt.Sprintf("\n%s %s, required by: %s", yellow(bold(smallArrow)), cyan(key), cyan(pkglist)))
+		buffer.WriteString(gotext.Get("\n%s %s, required by: %s", yellow(bold(smallArrow)), cyan(key), cyan(pkglist)))
 	}
 	return buffer.String(), nil
 }

+ 20 - 11
main.go

@@ -2,6 +2,7 @@ package main // import "github.com/Jguer/yay"
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -9,6 +10,9 @@ import (
 
 	alpm "github.com/Jguer/go-alpm"
 	pacmanconf "github.com/Morganamilo/go-pacmanconf"
+	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 func setPaths() error {
@@ -17,7 +21,7 @@ func setPaths() error {
 	} else if configHome = os.Getenv("HOME"); configHome != "" {
 		configHome = filepath.Join(configHome, ".config/yay")
 	} else {
-		return fmt.Errorf("XDG_CONFIG_HOME and HOME unset")
+		return errors.New(gotext.Get("XDG_CONFIG_HOME and HOME unset"))
 	}
 
 	if cacheHome = os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
@@ -25,7 +29,7 @@ func setPaths() error {
 	} else if cacheHome = os.Getenv("HOME"); cacheHome != "" {
 		cacheHome = filepath.Join(cacheHome, ".cache/yay")
 	} else {
-		return fmt.Errorf("XDG_CACHE_HOME and HOME unset")
+		return errors.New(gotext.Get("XDG_CACHE_HOME and HOME unset"))
 	}
 
 	configFile = filepath.Join(configHome, configFileName)
@@ -34,17 +38,21 @@ func setPaths() error {
 	return nil
 }
 
+func initGotext() {
+	gotext.Configure(localePath, os.Getenv("LANG"), "yay")
+}
+
 func initConfig() error {
 	cfile, err := os.Open(configFile)
 	if !os.IsNotExist(err) && err != nil {
-		return fmt.Errorf("failed to open config file '%s': %s", configFile, err)
+		return errors.New(gotext.Get("failed to open config file '%s': %s", configFile, err))
 	}
 
 	defer cfile.Close()
 	if !os.IsNotExist(err) {
 		decoder := json.NewDecoder(cfile)
 		if err = decoder.Decode(&config); err != nil {
-			return fmt.Errorf("failed to read config '%s': %s", configFile, err)
+			return errors.New(gotext.Get("failed to read config file '%s': %s", configFile, err))
 		}
 	}
 
@@ -59,14 +67,14 @@ func initConfig() error {
 func initVCS() error {
 	vfile, err := os.Open(vcsFile)
 	if !os.IsNotExist(err) && err != nil {
-		return fmt.Errorf("failed to open vcs file '%s': %s", vcsFile, err)
+		return errors.New(gotext.Get("failed to open vcs file '%s': %s", vcsFile, err))
 	}
 
 	defer vfile.Close()
 	if !os.IsNotExist(err) {
 		decoder := json.NewDecoder(vfile)
 		if err = decoder.Decode(&savedInfo); err != nil {
-			return fmt.Errorf("failed to read vcs '%s': %s", vcsFile, err)
+			return errors.New(gotext.Get("failed to read vcs file '%s': %s", vcsFile, err))
 		}
 	}
 
@@ -76,7 +84,7 @@ func initVCS() error {
 func initHomeDirs() error {
 	if _, err := os.Stat(configHome); os.IsNotExist(err) {
 		if err = os.MkdirAll(configHome, 0755); err != nil {
-			return fmt.Errorf("failed to create config directory '%s': %s", configHome, err)
+			return errors.New(gotext.Get("failed to create config directory '%s': %s", configHome, err))
 		}
 	} else if err != nil {
 		return err
@@ -84,7 +92,7 @@ func initHomeDirs() error {
 
 	if _, err := os.Stat(cacheHome); os.IsNotExist(err) {
 		if err = os.MkdirAll(cacheHome, 0755); err != nil {
-			return fmt.Errorf("failed to create cache directory '%s': %s", cacheHome, err)
+			return errors.New(gotext.Get("failed to create cache directory '%s': %s", cacheHome, err))
 		}
 	} else if err != nil {
 		return err
@@ -96,7 +104,7 @@ func initHomeDirs() error {
 func initBuildDir() error {
 	if _, err := os.Stat(config.BuildDir); os.IsNotExist(err) {
 		if err = os.MkdirAll(config.BuildDir, 0755); err != nil {
-			return fmt.Errorf("failed to create BuildDir directory '%s': %s", config.BuildDir, err)
+			return errors.New(gotext.Get("failed to create BuildDir directory '%s': %s", config.BuildDir, err))
 		}
 	} else if err != nil {
 		return err
@@ -174,7 +182,7 @@ func initAlpmHandle() error {
 
 	var err error
 	if alpmHandle, err = alpm.Initialize(pacmanConf.RootDir, pacmanConf.DBPath); err != nil {
-		return fmt.Errorf("unable to CreateHandle: %s", err)
+		return errors.New(gotext.Get("unable to CreateHandle: %s", err))
 	}
 
 	if err := configureAlpm(); err != nil {
@@ -208,8 +216,9 @@ func cleanup() int {
 }
 
 func main() {
+	initGotext()
 	if os.Geteuid() == 0 {
-		fmt.Fprintln(os.Stderr, "Please avoid running yay as root/sudo.")
+		text.Warnln(gotext.Get("Avoid running yay as root/sudo."))
 	}
 
 	exitOnError(setPaths())

+ 4 - 3
parser.go

@@ -3,13 +3,14 @@ package main
 import (
 	"bufio"
 	"bytes"
-	"fmt"
 	"html"
 	"os"
 	"strconv"
 	"strings"
 
+	"github.com/leonelquinteros/gotext"
 	rpc "github.com/mikkeloscar/aur"
+	"github.com/pkg/errors"
 
 	"github.com/Jguer/yay/v9/pkg/stringset"
 )
@@ -132,7 +133,7 @@ func (parser *arguments) needRoot() bool {
 
 func (parser *arguments) addOP(op string) (err error) {
 	if parser.op != "" {
-		err = fmt.Errorf("only one operation may be used at a time")
+		err = errors.New(gotext.Get("only one operation may be used at a time"))
 		return
 	}
 
@@ -142,7 +143,7 @@ func (parser *arguments) addOP(op string) (err error) {
 
 func (parser *arguments) addParam(option, arg string) (err error) {
 	if !isArg(option) {
-		return fmt.Errorf("invalid option '%s'", option)
+		return errors.New(gotext.Get("invalid option '%s'", option))
 	}
 
 	if isOp(option) {

+ 57 - 0
pkg/text/color.go

@@ -0,0 +1,57 @@
+package text
+
+import "fmt"
+
+const (
+	redCode    = "\x1b[31m"
+	greenCode  = "\x1b[32m"
+	yellowCode = "\x1b[33m"
+	cyanCode   = "\x1b[36m"
+	boldCode   = "\x1b[1m"
+
+	resetCode = "\x1b[0m"
+)
+
+// UseColor determines if package will emit colors
+var UseColor = true
+
+func stylize(startCode, in string) string {
+	if UseColor {
+		return startCode + in + resetCode
+	}
+
+	return in
+}
+
+func red(in string) string {
+	return stylize(redCode, in)
+}
+
+func green(in string) string {
+	return stylize(greenCode, in)
+}
+
+func yellow(in string) string {
+	return stylize(yellowCode, in)
+}
+
+func cyan(in string) string {
+	return stylize(cyanCode, in)
+}
+
+func bold(in string) string {
+	return stylize(boldCode, in)
+}
+
+// ColorHash Colors text using a hashing algorithm. The same text will always produce the
+// same color while different text will produce a different color.
+func ColorHash(name string) (output string) {
+	if !UseColor {
+		return name
+	}
+	var hash uint = 5381
+	for i := 0; i < len(name); i++ {
+		hash = uint(name[i]) + ((hash << 5) + (hash))
+	}
+	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", hash%6+31, name)
+}

+ 52 - 0
pkg/text/print.go

@@ -0,0 +1,52 @@
+package text
+
+import (
+	"fmt"
+	"os"
+)
+
+const arrow = "==>"
+const smallArrow = " ->"
+const opSymbol = ":: "
+
+func OperationInfoln(a ...interface{}) {
+	fmt.Fprint(os.Stdout, append([]interface{}{boldCode, cyan(opSymbol), boldCode}, a...)...)
+	fmt.Fprintln(os.Stdout, resetCode)
+}
+
+func OperationInfo(a ...interface{}) {
+	fmt.Fprint(os.Stdout, append([]interface{}{boldCode, cyan(opSymbol), boldCode}, a...)...)
+	fmt.Fprint(os.Stdout, resetCode+" ")
+}
+
+func Info(a ...interface{}) {
+	fmt.Fprint(os.Stdout, append([]interface{}{bold(green(arrow + " "))}, a...)...)
+}
+
+func Infoln(a ...interface{}) {
+	fmt.Fprintln(os.Stdout, append([]interface{}{bold(green(arrow))}, a...)...)
+}
+
+func Warn(a ...interface{}) {
+	fmt.Fprint(os.Stdout, append([]interface{}{bold(yellow(smallArrow))}, a...)...)
+}
+
+func Warnln(a ...interface{}) {
+	fmt.Fprintln(os.Stdout, append([]interface{}{bold(yellow(smallArrow))}, a...)...)
+}
+
+func Error(a ...interface{}) {
+	fmt.Fprint(os.Stderr, append([]interface{}{bold(red(smallArrow))}, a...)...)
+}
+
+func Errorln(a ...interface{}) {
+	fmt.Fprintln(os.Stderr, append([]interface{}{bold(red(smallArrow))}, a...)...)
+}
+
+func PrintInfoValue(str, value string) {
+	if value == "" {
+		value = "None"
+	}
+
+	fmt.Fprintln(os.Stdout, bold("%-16s%s")+" %s\n", str, ":", value)
+}

+ 54 - 61
print.go

@@ -12,10 +12,12 @@ import (
 	"strings"
 	"time"
 
+	"github.com/leonelquinteros/gotext"
 	rpc "github.com/mikkeloscar/aur"
 
 	"github.com/Jguer/yay/v9/pkg/intrange"
 	"github.com/Jguer/yay/v9/pkg/stringset"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 const arrow = "==>"
@@ -23,7 +25,7 @@ const smallArrow = " ->"
 
 func (warnings *aurWarnings) print() {
 	if len(warnings.Missing) > 0 {
-		fmt.Print(bold(yellow(smallArrow)) + " Missing AUR Packages:")
+		text.Warn(gotext.Get("Missing AUR Packages:"))
 		for _, name := range warnings.Missing {
 			fmt.Print("  " + cyan(name))
 		}
@@ -31,7 +33,7 @@ func (warnings *aurWarnings) print() {
 	}
 
 	if len(warnings.Orphans) > 0 {
-		fmt.Print(bold(yellow(smallArrow)) + " Orphaned AUR Packages:")
+		text.Warn(gotext.Get("Orphaned AUR Packages:"))
 		for _, name := range warnings.Orphans {
 			fmt.Print("  " + cyan(name))
 		}
@@ -39,7 +41,7 @@ func (warnings *aurWarnings) print() {
 	}
 
 	if len(warnings.OutOfDate) > 0 {
-		fmt.Print(bold(yellow(smallArrow)) + " Flagged Out Of Date AUR Packages:")
+		text.Warn(gotext.Get("Flagged Out Of Date AUR Packages:"))
 		for _, name := range warnings.OutOfDate {
 			fmt.Print("  " + cyan(name))
 		}
@@ -73,7 +75,7 @@ func (q aurQuery) printSearch(start int) {
 			case bottomUp:
 				toprint += magenta(strconv.Itoa(len(q)+start-i-1) + " ")
 			default:
-				fmt.Println("Invalid Sort Mode. Fix with yay -Y --bottomup --save")
+				text.Warnln(gotext.Get("Invalid Sort Mode. Fix with yay -Y --bottomup --save"))
 			}
 		} else if config.SearchMode == minimal {
 			fmt.Println(q[i].Name)
@@ -86,18 +88,18 @@ func (q aurQuery) printSearch(start int) {
 			" " + bold(strconv.FormatFloat(q[i].Popularity, 'f', 2, 64)+"%) ")
 
 		if q[i].Maintainer == "" {
-			toprint += bold(red("(Orphaned)")) + " "
+			toprint += bold(red(gotext.Get("(Orphaned)"))) + " "
 		}
 
 		if q[i].OutOfDate != 0 {
-			toprint += bold(red("(Out-of-date "+formatTime(q[i].OutOfDate)+")")) + " "
+			toprint += bold(red(gotext.Get("(Out-of-date: %s)", formatTime(q[i].OutOfDate)))) + " "
 		}
 
 		if pkg := localDB.Pkg(q[i].Name); pkg != nil {
 			if pkg.Version() != q[i].Version {
-				toprint += bold(green("(Installed: " + pkg.Version() + ")"))
+				toprint += bold(green(gotext.Get("(Installed: %)", pkg.Version())))
 			} else {
-				toprint += bold(green("(Installed)"))
+				toprint += bold(green(gotext.Get("(Installed)")))
 			}
 		}
 		toprint += "\n    " + q[i].Description
@@ -116,7 +118,7 @@ func (s repoQuery) printSearch() {
 			case bottomUp:
 				toprint += magenta(strconv.Itoa(len(s)-i) + " ")
 			default:
-				fmt.Println("Invalid Sort Mode. Fix with yay -Y --bottomup --save")
+				text.Warnln(gotext.Get("Invalid Sort Mode. Fix with yay -Y --bottomup --save"))
 			}
 		} else if config.SearchMode == minimal {
 			fmt.Println(res.Name())
@@ -136,9 +138,9 @@ func (s repoQuery) printSearch() {
 		if err == nil {
 			if pkg := localDB.Pkg(res.Name()); pkg != nil {
 				if pkg.Version() != res.Version() {
-					toprint += bold(green("(Installed: " + pkg.Version() + ")"))
+					toprint += bold(green(gotext.Get("(Installed: %s)", pkg.Version())))
 				} else {
-					toprint += bold(green("(Installed)"))
+					toprint += bold(green(gotext.Get("(Installed)")))
 				}
 			}
 		}
@@ -278,48 +280,40 @@ func printDownloads(repoName string, length int, packages string) {
 	fmt.Println(repoInfo + cyan(packages))
 }
 
-func printInfoValue(str, value string) {
-	if value == "" {
-		value = "None"
-	}
-
-	fmt.Printf(bold("%-16s%s")+" %s\n", str, ":", value)
-}
-
 // PrintInfo prints package info like pacman -Si.
 func PrintInfo(a *rpc.Pkg) {
-	printInfoValue("Repository", "aur")
-	printInfoValue("Name", a.Name)
-	printInfoValue("Keywords", strings.Join(a.Keywords, "  "))
-	printInfoValue("Version", a.Version)
-	printInfoValue("Description", a.Description)
-	printInfoValue("URL", a.URL)
-	printInfoValue("AUR URL", config.AURURL+"/packages/"+a.Name)
-	printInfoValue("Groups", strings.Join(a.Groups, "  "))
-	printInfoValue("Licenses", strings.Join(a.License, "  "))
-	printInfoValue("Provides", strings.Join(a.Provides, "  "))
-	printInfoValue("Depends On", strings.Join(a.Depends, "  "))
-	printInfoValue("Make Deps", strings.Join(a.MakeDepends, "  "))
-	printInfoValue("Check Deps", strings.Join(a.CheckDepends, "  "))
-	printInfoValue("Optional Deps", strings.Join(a.OptDepends, "  "))
-	printInfoValue("Conflicts With", strings.Join(a.Conflicts, "  "))
-	printInfoValue("Maintainer", a.Maintainer)
-	printInfoValue("Votes", fmt.Sprintf("%d", a.NumVotes))
-	printInfoValue("Popularity", fmt.Sprintf("%f", a.Popularity))
-	printInfoValue("First Submitted", formatTimeQuery(a.FirstSubmitted))
-	printInfoValue("Last Modified", formatTimeQuery(a.LastModified))
+	text.PrintInfoValue(gotext.Get("Repository"), "aur")
+	text.PrintInfoValue(gotext.Get("Name"), a.Name)
+	text.PrintInfoValue(gotext.Get("Keywords"), strings.Join(a.Keywords, "  "))
+	text.PrintInfoValue(gotext.Get("Version"), a.Version)
+	text.PrintInfoValue(gotext.Get("Description"), a.Description)
+	text.PrintInfoValue(gotext.Get("URL"), a.URL)
+	text.PrintInfoValue(gotext.Get("AUR URL"), config.AURURL+"/packages/"+a.Name)
+	text.PrintInfoValue(gotext.Get("Groups"), strings.Join(a.Groups, "  "))
+	text.PrintInfoValue(gotext.Get("Licenses"), strings.Join(a.License, "  "))
+	text.PrintInfoValue(gotext.Get("Provides"), strings.Join(a.Provides, "  "))
+	text.PrintInfoValue(gotext.Get("Depends On"), strings.Join(a.Depends, "  "))
+	text.PrintInfoValue(gotext.Get("Make Deps"), strings.Join(a.MakeDepends, "  "))
+	text.PrintInfoValue(gotext.Get("Check Deps"), strings.Join(a.CheckDepends, "  "))
+	text.PrintInfoValue(gotext.Get("Optional Deps"), strings.Join(a.OptDepends, "  "))
+	text.PrintInfoValue(gotext.Get("Conflicts With"), strings.Join(a.Conflicts, "  "))
+	text.PrintInfoValue(gotext.Get("Maintainer"), a.Maintainer)
+	text.PrintInfoValue(gotext.Get("Votes"), fmt.Sprintf("%d", a.NumVotes))
+	text.PrintInfoValue(gotext.Get("Popularity"), fmt.Sprintf("%f", a.Popularity))
+	text.PrintInfoValue(gotext.Get("First Submitted"), formatTimeQuery(a.FirstSubmitted))
+	text.PrintInfoValue(gotext.Get("Last Modified"), formatTimeQuery(a.LastModified))
 
 	if a.OutOfDate != 0 {
-		printInfoValue("Out-of-date", formatTimeQuery(a.OutOfDate))
+		text.PrintInfoValue(gotext.Get("Out-of-date"), formatTimeQuery(a.OutOfDate))
 	} else {
-		printInfoValue("Out-of-date", "No")
+		text.PrintInfoValue(gotext.Get("Out-of-date"), "No")
 	}
 
 	if cmdArgs.existsDouble("i") {
-		printInfoValue("ID", fmt.Sprintf("%d", a.ID))
-		printInfoValue("Package Base ID", fmt.Sprintf("%d", a.PackageBaseID))
-		printInfoValue("Package Base", a.PackageBase)
-		printInfoValue("Snapshot URL", config.AURURL+a.URLPath)
+		text.PrintInfoValue("ID", fmt.Sprintf("%d", a.ID))
+		text.PrintInfoValue(gotext.Get("Package Base ID"), fmt.Sprintf("%d", a.PackageBaseID))
+		text.PrintInfoValue(gotext.Get("Package Base"), a.PackageBase)
+		text.PrintInfoValue(gotext.Get("Snapshot URL"), config.AURURL+a.URLPath)
 	}
 
 	fmt.Println()
@@ -340,7 +334,7 @@ func biggestPackages() {
 	}
 
 	for i := 0; i < 10; i++ {
-		fmt.Println(bold(pkgS[i].Name()) + ": " + cyan(human(pkgS[i].ISize())))
+		fmt.Printf("%s: %s\n", bold(pkgS[i].Name()), cyan(human(pkgS[i].ISize())))
 	}
 	// Could implement size here as well, but we just want the general idea
 }
@@ -357,14 +351,14 @@ func localStatistics() error {
 		return err
 	}
 
-	fmt.Printf(bold("Yay version v%s\n"), yayVersion)
+	text.Infoln(gotext.Get("Yay version v%s", yayVersion))
 	fmt.Println(bold(cyan("===========================================")))
-	fmt.Println(bold(green("Total installed packages: ")) + cyan(strconv.Itoa(info.Totaln)))
-	fmt.Println(bold(green("Total foreign installed packages: ")) + cyan(strconv.Itoa(len(remoteNames))))
-	fmt.Println(bold(green("Explicitly installed packages: ")) + cyan(strconv.Itoa(info.Expln)))
-	fmt.Println(bold(green("Total Size occupied by packages: ")) + cyan(human(info.TotalSize)))
+	text.Infoln(gotext.Get("Total installed packages: %s", cyan(strconv.Itoa(info.Totaln))))
+	text.Infoln(gotext.Get("Total foreign installed packages: %s", cyan(strconv.Itoa(len(remoteNames)))))
+	text.Infoln(gotext.Get("Explicitly installed packages: %s", cyan(strconv.Itoa(info.Expln))))
+	text.Infoln(gotext.Get("Total Size occupied by packages: %s", cyan(human(info.TotalSize))))
 	fmt.Println(bold(cyan("===========================================")))
-	fmt.Println(bold(green("Ten biggest packages:")))
+	text.Infoln(gotext.Get("Ten biggest packages:"))
 	biggestPackages()
 	fmt.Println(bold(cyan("===========================================")))
 
@@ -449,7 +443,7 @@ outer:
 			}
 		}
 
-		fmt.Fprintln(os.Stderr, red(bold("error:")), "package '"+pkg+"' was not found")
+		text.Errorln(gotext.Get("package '%s' was not found", pkg))
 		missing = true
 	}
 
@@ -618,21 +612,20 @@ func colorHash(name string) (output string) {
 func providerMenu(dep string, providers providers) *rpc.Pkg {
 	size := providers.Len()
 
-	fmt.Print(bold(cyan(":: ")))
-	str := bold(fmt.Sprintf(bold("There are %d providers available for %s:"), size, dep))
+	str := bold(gotext.Get("There are %d providers available for %s:", size, dep))
 
 	size = 1
-	str += bold(cyan("\n:: ")) + bold("Repository AUR\n    ")
+	str += bold(cyan("\n:: ")) + bold(gotext.Get("Repository AUR")) + "\n    "
 
 	for _, pkg := range providers.Pkgs {
 		str += fmt.Sprintf("%d) %s ", size, pkg.Name)
 		size++
 	}
 
-	fmt.Fprintln(os.Stderr, str)
+	text.OperationInfoln(str)
 
 	for {
-		fmt.Print("\nEnter a number (default=1): ")
+		fmt.Print(gotext.Get("\nEnter a number (default=1): "))
 
 		if config.NoConfirm {
 			fmt.Println("1")
@@ -648,7 +641,7 @@ func providerMenu(dep string, providers providers) *rpc.Pkg {
 		}
 
 		if overflow {
-			fmt.Fprintln(os.Stderr, "Input too long")
+			text.Errorln(gotext.Get("input too long"))
 			continue
 		}
 
@@ -658,12 +651,12 @@ func providerMenu(dep string, providers providers) *rpc.Pkg {
 
 		num, err := strconv.Atoi(string(numberBuf))
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "%s invalid number: %s\n", red("error:"), string(numberBuf))
+			text.Errorln(gotext.Get("invalid number: %s", string(numberBuf)))
 			continue
 		}
 
 		if num < 1 || num >= size {
-			fmt.Fprintf(os.Stderr, "%s invalid value: %d is not between %d and %d\n", red("error:"), num, 1, size-1)
+			text.Errorln(gotext.Get("invalid value: %d is not between %d and %d", num, 1, size-1))
 			continue
 		}
 

+ 7 - 4
query.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"sort"
@@ -9,11 +10,13 @@ import (
 	"time"
 
 	alpm "github.com/Jguer/go-alpm"
+	"github.com/leonelquinteros/gotext"
 	rpc "github.com/mikkeloscar/aur"
 
 	"github.com/Jguer/yay/v9/pkg/intrange"
 	"github.com/Jguer/yay/v9/pkg/multierror"
 	"github.com/Jguer/yay/v9/pkg/stringset"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 type aurWarnings struct {
@@ -224,12 +227,12 @@ func syncSearch(pkgS []string) (err error) {
 			pq.printSearch()
 		}
 	default:
-		return fmt.Errorf("invalid Sort Mode. Fix with yay -Y --bottomup --save")
+		return errors.New(gotext.Get("invalid sort mode. Fix with yay -Y --bottomup --save"))
 	}
 
 	if aurErr != nil {
-		fmt.Fprintf(os.Stderr, "Error during AUR search: %s\n", aurErr)
-		fmt.Fprintln(os.Stderr, "Showing Repo packages only")
+		text.Errorln(gotext.Get("error during AUR search: %s", aurErr))
+		text.Warnln(gotext.Get("Showing repo packages only"))
 	}
 
 	return nil
@@ -562,7 +565,7 @@ func aurInfo(names []string, warnings *aurWarnings) ([]*rpc.Pkg, error) {
 }
 
 func aurInfoPrint(names []string) ([]*rpc.Pkg, error) {
-	fmt.Println(bold(cyan("::") + bold(" Querying AUR...")))
+	text.OperationInfoln(gotext.Get("Querying AUR..."))
 
 	warnings := &aurWarnings{}
 	info, err := aurInfo(names, warnings)

+ 10 - 11
upgrade.go

@@ -7,8 +7,10 @@ import (
 	"unicode"
 
 	alpm "github.com/Jguer/go-alpm"
+	"github.com/leonelquinteros/gotext"
 
 	"github.com/Jguer/yay/v9/pkg/intrange"
+	"github.com/Jguer/yay/v9/pkg/text"
 
 	rpc "github.com/mikkeloscar/aur"
 
@@ -133,7 +135,7 @@ func upList(warnings *aurWarnings) (aurUp, repoUp upSlice, err error) {
 	}
 
 	if mode == modeAny || mode == modeRepo {
-		fmt.Println(bold(cyan("::") + bold(" Searching databases for updates...")))
+		text.OperationInfoln(gotext.Get("Searching databases for updates..."))
 		wg.Add(1)
 		go func() {
 			repoUp, err = upRepo()
@@ -143,7 +145,7 @@ func upList(warnings *aurWarnings) (aurUp, repoUp upSlice, err error) {
 	}
 
 	if mode == modeAny || mode == modeAUR {
-		fmt.Println(bold(cyan("::") + bold(" Searching AUR for updates...")))
+		text.OperationInfoln(gotext.Get("Searching AUR for updates..."))
 
 		var _aurdata []*rpc.Pkg
 		_aurdata, err = aurInfo(remoteNames, warnings)
@@ -160,7 +162,7 @@ func upList(warnings *aurWarnings) (aurUp, repoUp upSlice, err error) {
 			}()
 
 			if config.Devel {
-				fmt.Println(bold(cyan("::") + bold(" Checking development packages...")))
+				text.OperationInfoln(gotext.Get("Checking development packages..."))
 				wg.Add(1)
 				go func() {
 					develUp = upDevel(remote, aurdata)
@@ -267,11 +269,10 @@ func upAUR(remote []alpm.Package, aurdata map[string]*rpc.Pkg) upSlice {
 func printIgnoringPackage(pkg alpm.Package, newPkgVersion string) {
 	left, right := getVersionDiff(pkg.Version(), newPkgVersion)
 
-	fmt.Printf("%s %s: ignoring package upgrade (%s => %s)\n",
-		yellow(bold(smallArrow)),
+	text.Warnln(gotext.Get("%s: ignoring package upgrade (%s => %s)",
 		cyan(pkg.Name()),
 		left, right,
-	)
+	))
 }
 
 func printLocalNewerThanAUR(
@@ -285,11 +286,10 @@ func printLocalNewerThanAUR(
 		left, right := getVersionDiff(pkg.Version(), aurPkg.Version)
 
 		if !isDevelPackage(pkg) && alpm.VerCmp(pkg.Version(), aurPkg.Version) > 0 {
-			fmt.Printf("%s %s: local (%s) is newer than AUR (%s)\n",
-				yellow(bold(smallArrow)),
+			text.Warnln(gotext.Get("%s: local (%s) is newer than AUR (%s)",
 				cyan(pkg.Name()),
 				left, right,
-			)
+			))
 		}
 	}
 }
@@ -360,8 +360,7 @@ func upgradePkgs(aurUp, repoUp upSlice) (ignore, aurNames stringset.StringSet, e
 	fmt.Printf("%s"+bold(" %d ")+"%s\n", bold(cyan("::")), allUpLen, bold("Packages to upgrade."))
 	allUp.print()
 
-	fmt.Println(bold(green(arrow + " Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)")))
-	fmt.Print(bold(green(arrow + " ")))
+	text.Infoln(gotext.Get("Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)"))
 
 	numbers, err := getInput(config.AnswerUpgrade)
 	if err != nil {

+ 6 - 3
utils.go

@@ -1,8 +1,11 @@
 package main
 
 import (
-	"fmt"
 	"unicode"
+
+	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 const gitEmptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
@@ -36,12 +39,12 @@ func removeInvalidTargets(targets []string) []string {
 		db, _ := splitDBFromName(target)
 
 		if db == "aur" && mode == modeRepo {
-			fmt.Printf("%s %s %s\n", bold(yellow(arrow)), cyan(target), bold("Can't use target with option --repo -- skipping"))
+			text.Warnln(gotext.Get("%s: can't use target with option --repo -- skipping", cyan(target)))
 			continue
 		}
 
 		if db != "aur" && db != "" && mode == modeAUR {
-			fmt.Printf("%s %s %s\n", bold(yellow(arrow)), cyan(target), bold("Can't use target with option --aur -- skipping"))
+			text.Warnln(gotext.Get("%s: can't use target with option --aur -- skipping", cyan(target)))
 			continue
 		}
 

+ 5 - 3
vcs.go

@@ -10,8 +10,10 @@ import (
 	"time"
 
 	gosrc "github.com/Morganamilo/go-srcinfo"
+	"github.com/leonelquinteros/gotext"
 
 	"github.com/Jguer/yay/v9/pkg/stringset"
+	"github.com/Jguer/yay/v9/pkg/text"
 )
 
 // Info contains the last commit sha of a repo
@@ -58,7 +60,7 @@ func createDevelDB() error {
 	}
 
 	wg.Wait()
-	fmt.Println(bold(yellow(arrow) + bold(" GenDB finished. No packages were installed")))
+	text.OperationInfoln(gotext.Get("GenDB finished. No packages were installed"))
 	return err
 }
 
@@ -141,7 +143,7 @@ func updateVCSData(pkgName string, sources []gosrc.ArchString, mux sync.Locker,
 		}
 
 		savedInfo[pkgName] = info
-		fmt.Println(bold(yellow(arrow)) + " Found git repo: " + cyan(url))
+		text.Warnln(gotext.Get("Found git repo: %s", cyan(url)))
 		err := saveVCSInfo()
 		if err != nil {
 			fmt.Fprintln(os.Stderr, err)
@@ -176,7 +178,7 @@ func getCommit(url, branch string, protocols []string) string {
 		timer := time.AfterFunc(5*time.Second, func() {
 			err = cmd.Process.Kill()
 			if err != nil {
-				fmt.Fprintln(os.Stderr, err)
+				text.Errorln(err)
 			}
 		})