diff --git a/cmd/msvc-go-wine/download.go b/cmd/msvc-go-wine/download.go index 84f04c2..a797fd5 100644 --- a/cmd/msvc-go-wine/download.go +++ b/cmd/msvc-go-wine/download.go @@ -9,6 +9,7 @@ import ( "runtime" "github.com/Cheviiot/msvc-go-wine/internal/download" + "github.com/Cheviiot/msvc-go-wine/internal/i18n" ) func runDownload(args []string) int { @@ -79,16 +80,16 @@ func runDownload(args []string) int { if opts.HostArch == "" { opts.HostArch = detectHostArch() } - fmt.Println("Install packages for", opts.HostArch, "host architecture") + fmt.Println(i18n.T("download.host_arch", opts.HostArch)) idx := download.BuildIndex(manifest, opts.HostArch, opts.Language) if *listWorkloads || *listComponents { if *listWorkloads { - printPackageList("Workload", download.PackagesByType(idx, "Workload"), opts.Language) + printPackageList("download.workloads_header", download.PackagesByType(idx, "Workload"), opts.Language) } if *listComponents { - printPackageList("Component", download.PackagesByType(idx, "Component"), opts.Language) + printPackageList("download.components_header", download.PackagesByType(idx, "Component"), opts.Language) } return 0 } @@ -123,8 +124,8 @@ func runDownload(args []string) int { downloadSize += p.DownloadSize() installSize += p.InstalledSize() } - fmt.Printf("Selected %d packages, for a total download size of %s, install size of %s\n", - len(selected), download.HumanizeBytes(downloadSize), download.HumanizeBytes(installSize)) + fmt.Print(i18n.T("download.selected", + len(selected), download.HumanizeBytes(downloadSize), download.HumanizeBytes(installSize))) cache := *cacheDir removeCache := false @@ -148,7 +149,7 @@ func runDownload(args []string) int { return 1 } *dest = def - fmt.Println("--dest not set, using default:", *dest) + fmt.Println(i18n.T("download.default_dest", *dest)) } if err := download.FetchPayloads(selected, cache, *onlyDownload); err != nil { @@ -207,7 +208,7 @@ func runDownload(args []string) int { } } - fmt.Println("Done. Next: msvc-go-wine install", destAbs) + fmt.Println(i18n.T("download.done", destAbs)) return 0 } @@ -226,7 +227,7 @@ func downloadWDK(opts *download.Options, selected []*download.Package, cache, de } } if len(archs) == 0 { - fmt.Println("--with-wdk: no x64/arm64 target architecture selected, skipping (no WDK package exists for x86/arm)") + fmt.Println(i18n.T("download.wdk_skip")) return nil } for _, arch := range archs { @@ -238,7 +239,7 @@ func downloadWDK(opts *download.Options, selected []*download.Package, cache, de if err != nil { return err } - fmt.Printf("Installed WDK (%s) %s at %s\n", arch, version, wdkDir) + fmt.Print(i18n.T("download.wdk_installed", arch, version, wdkDir)) } return nil } @@ -254,8 +255,9 @@ func contains(list []string, v string) bool { // printPackageList prints one line per package: its ID, and (when the // manifest carries one) its human-readable title in the requested language. -func printPackageList(kind string, pkgs []*download.Package, language string) { - fmt.Printf("Available %ss (%d):\n", kind, len(pkgs)) +// headerKey is an i18n catalog key taking the package count as its one arg. +func printPackageList(headerKey string, pkgs []*download.Package, language string) { + fmt.Print(i18n.T(headerKey, len(pkgs))) for _, p := range pkgs { if lr := p.Localized(language); lr != nil && lr.Title != "" { fmt.Printf(" %-65s %s\n", p.ID, lr.Title) @@ -273,7 +275,7 @@ func detectHostArch() string { } func promptAcceptLicense(license string) bool { - fmt.Printf("Do you accept the license at %s (yes/no)? ", license) + fmt.Print(i18n.T("download.license_prompt", license)) scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { switch scanner.Text() { @@ -282,7 +284,7 @@ func promptAcceptLicense(license string) bool { case "no": return false } - fmt.Print("Do you accept the license? Answer \"yes\" or \"no\": ") + fmt.Print(i18n.T("download.license_reprompt")) } return false } diff --git a/cmd/msvc-go-wine/env.go b/cmd/msvc-go-wine/env.go index 535ab8c..a4923d2 100644 --- a/cmd/msvc-go-wine/env.go +++ b/cmd/msvc-go-wine/env.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/Cheviiot/msvc-go-wine/internal/i18n" "github.com/Cheviiot/msvc-go-wine/internal/wineenv" ) @@ -20,7 +21,7 @@ func runEnv(args []string) int { return 2 } if *bin == "" { - fmt.Fprintln(os.Stderr, "usage: msvc-go-wine env --bin /bin/") + fmt.Fprintln(os.Stderr, i18n.T("env.usage")) return 1 } @@ -38,7 +39,7 @@ func runEnv(args []string) int { triple, ok := targetTriples[cfg.Arch] if !ok { - fmt.Fprintf(os.Stderr, "msvc-go-wine env: unknown arch %q\n", cfg.Arch) + fmt.Fprint(os.Stderr, i18n.T("env.unknown_arch", cfg.Arch)) return 1 } diff --git a/cmd/msvc-go-wine/install.go b/cmd/msvc-go-wine/install.go index 5a19e71..21949f4 100644 --- a/cmd/msvc-go-wine/install.go +++ b/cmd/msvc-go-wine/install.go @@ -4,12 +4,13 @@ import ( "fmt" "os" + "github.com/Cheviiot/msvc-go-wine/internal/i18n" "github.com/Cheviiot/msvc-go-wine/internal/install" ) func runInstall(args []string) int { if len(args) > 1 || (len(args) == 1 && (args[0] == "-h" || args[0] == "--help")) { - fmt.Fprintln(os.Stderr, "usage: msvc-go-wine install [dest] (default: ~/.msvc-go-wine)") + fmt.Fprintln(os.Stderr, i18n.T("install.usage")) return 1 } @@ -23,7 +24,7 @@ func runInstall(args []string) int { return 1 } dest = def - fmt.Println("No directory given, using default:", dest) + fmt.Println(i18n.T("install.default_dir", dest)) } self, err := os.Executable() @@ -36,6 +37,6 @@ func runInstall(args []string) int { fmt.Fprintln(os.Stderr, "msvc-go-wine install:", err) return 1 } - fmt.Println("Done. Add", dest+"/bin/ to PATH to use cl, link, lib, ...") + fmt.Println(i18n.T("install.done", dest+"/bin/")) return 0 } diff --git a/cmd/msvc-go-wine/main.go b/cmd/msvc-go-wine/main.go index ffbb4a2..c32e5f3 100644 --- a/cmd/msvc-go-wine/main.go +++ b/cmd/msvc-go-wine/main.go @@ -12,6 +12,7 @@ import ( "path/filepath" "strings" + "github.com/Cheviiot/msvc-go-wine/internal/i18n" "github.com/Cheviiot/msvc-go-wine/internal/wrapper" ) @@ -40,38 +41,25 @@ func runCLI(args []string) int { } switch args[0] { - case "download": + case "download", "dl": return runDownload(args[1:]) - case "install": + case "install", "i": return runInstall(args[1:]) - case "env": + case "env", "e": return runEnv(args[1:]) - case "version": + case "version", "v", "--version": fmt.Println("msvc-go-wine " + version) return 0 - case "-h", "--help", "help": + case "-h", "--help", "help", "h": printUsage() return 0 default: - fmt.Fprintf(os.Stderr, "msvc-go-wine: unknown subcommand %q\n\n", args[0]) + fmt.Fprint(os.Stderr, i18n.T("main.unknown_subcommand", args[0])) printUsage() return 1 } } func printUsage() { - fmt.Fprint(os.Stderr, `msvc-go-wine - cross compile with MSVC on Linux via Wine - -Usage: - msvc-go-wine download --accept-license [--dest ] [options] - fetch and unpack MSVC/WinSDK - msvc-go-wine install [dir] wire up wrappers for a downloaded MSVC - msvc-go-wine env --bin print INCLUDE/LIB for native clang-cl/lld-link use - msvc-go-wine version print the version - ---dest/[dir] default to ~/.msvc-go-wine if omitted. - -Once installed, add /bin/ to PATH and invoke the tools directly: - cl, link, lib, ml, ml64, mc, midl, mt, rc, dumpbin, msbuild, nmake, armasm, armasm64, cmd, findstr -`) + fmt.Fprint(os.Stderr, i18n.T("main.usage")) } diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go new file mode 100644 index 0000000..e57c692 --- /dev/null +++ b/internal/i18n/i18n.go @@ -0,0 +1,176 @@ +// Package i18n provides minimal message localization for msvc-go-wine's own +// CLI chrome (usage text, progress lines, prompts). It does not localize +// error messages bubbled up from deeper packages - those stay in English, +// matching how most cross-platform dev tools keep diagnostic text technical +// regardless of UI language. +package i18n + +import ( + "fmt" + "os" + "strings" +) + +type Lang string + +const ( + EN Lang = "en" + RU Lang = "ru" +) + +var current = detect() + +// detect picks the active language from MSVC_GO_WINE_LANG (checked first, so +// it always overrides the locale), falling back to the POSIX locale +// variables in their usual priority order (LC_ALL, LC_MESSAGES, LANG). Any +// value starting with "ru" (case-insensitive) selects Russian; anything else +// falls back to English. +func detect() Lang { + for _, key := range []string{"MSVC_GO_WINE_LANG", "LC_ALL", "LC_MESSAGES", "LANG"} { + v := os.Getenv(key) + if v == "" { + continue + } + if strings.HasPrefix(strings.ToLower(v), "ru") { + return RU + } + return EN + } + return EN +} + +// Current returns the active language, for callers that need to branch +// beyond a simple T() lookup. +func Current() Lang { return current } + +// T looks up key in the message catalog and formats it (via fmt.Sprintf) +// with args, if any. Keys with no translation for the current language fall +// back to English; keys missing from the catalog entirely are returned +// as-is, so a missing translation degrades to a visible-but-harmless string +// rather than a panic. +func T(key string, args ...any) string { + msg := key + if entry, ok := catalog[key]; ok { + if m, ok := entry[current]; ok { + msg = m + } else { + msg = entry[EN] + } + } + if len(args) == 0 { + return msg + } + return fmt.Sprintf(msg, args...) +} + +var catalog = map[string]map[Lang]string{ + "main.usage": { + EN: `msvc-go-wine - cross compile with MSVC on Linux via Wine + +Usage: + msvc-go-wine download (dl) --accept-license [--dest ] [options] + fetch and unpack MSVC/WinSDK/WDK + msvc-go-wine install (i) [dir] wire up wrappers for a downloaded MSVC + msvc-go-wine env (e) --bin print INCLUDE/LIB for native clang-cl/lld-link use + msvc-go-wine version (v) print the version + msvc-go-wine help (h) show this message + +Run "msvc-go-wine --help" for that command's own options - download +has many, including --with-wdk, --list-workloads, --list-components and +--print-deps-tree. + +--dest/[dir] default to ~/.msvc-go-wine if omitted. +Language: set MSVC_GO_WINE_LANG=ru (or LANG=ru_RU...) for Russian output. + +Once installed, add /bin/ to PATH and invoke the tools directly: + cl, link, lib, ml, ml64, mc, midl, mt, rc, dumpbin, msbuild, nmake, armasm, armasm64, cmd, findstr +`, + RU: `msvc-go-wine — кросс-компиляция настоящим MSVC на Linux через Wine + +Использование: + msvc-go-wine download (dl) --accept-license [--dest <каталог>] [опции] + скачать и распаковать MSVC/WinSDK/WDK + msvc-go-wine install (i) [каталог] настроить обёртки для скачанного MSVC + msvc-go-wine env (e) --bin вывести INCLUDE/LIB для clang-cl/lld-link напрямую + msvc-go-wine version (v) показать версию + msvc-go-wine help (h) показать эту справку + +Запустите «msvc-go-wine <команда> --help» для параметров конкретной команды — +у download их много, включая --with-wdk, --list-workloads, --list-components +и --print-deps-tree. + +--dest/[каталог] по умолчанию — ~/.msvc-go-wine. +Язык: установите MSVC_GO_WINE_LANG=en (или LANG=en_US...) для вывода на английском. + +После установки добавьте /bin/ в PATH и вызывайте инструменты напрямую: + cl, link, lib, ml, ml64, mc, midl, mt, rc, dumpbin, msbuild, nmake, armasm, armasm64, cmd, findstr +`, + }, + "main.unknown_subcommand": { + EN: "msvc-go-wine: unknown subcommand %q\n\n", + RU: "msvc-go-wine: неизвестная подкоманда %q\n\n", + }, + + "install.usage": { + EN: "usage: msvc-go-wine install (i) [dest] (default: ~/.msvc-go-wine)", + RU: "использование: msvc-go-wine install (i) [каталог] (по умолчанию: ~/.msvc-go-wine)", + }, + "install.default_dir": { + EN: "No directory given, using default: %s", + RU: "Каталог не указан, используется значение по умолчанию: %s", + }, + "install.done": { + EN: "Done. Add %s to PATH to use cl, link, lib, ...", + RU: "Готово. Добавьте %s в PATH, чтобы использовать cl, link, lib и т.д.", + }, + + "env.usage": { + EN: "usage: msvc-go-wine env (e) --bin /bin/", + RU: "использование: msvc-go-wine env (e) --bin /bin/", + }, + "env.unknown_arch": { + EN: "msvc-go-wine env: unknown arch %q\n", + RU: "msvc-go-wine env: неизвестная архитектура %q\n", + }, + + "download.host_arch": { + EN: "Install packages for %s host architecture", + RU: "Установка пакетов для архитектуры хоста %s", + }, + "download.selected": { + EN: "Selected %d packages, for a total download size of %s, install size of %s\n", + RU: "Выбрано пакетов: %d, общий размер загрузки %s, размер после установки %s\n", + }, + "download.default_dest": { + EN: "--dest not set, using default: %s", + RU: "--dest не указан, используется значение по умолчанию: %s", + }, + "download.done": { + EN: "Done. Next: msvc-go-wine install %s", + RU: "Готово. Далее: msvc-go-wine install %s", + }, + "download.wdk_skip": { + EN: "--with-wdk: no x64/arm64 target architecture selected, skipping (no WDK package exists for x86/arm)", + RU: "--with-wdk: не выбрана целевая архитектура x64/arm64, пропускаем (для x86/arm пакета WDK не существует)", + }, + "download.wdk_installed": { + EN: "Installed WDK (%s) %s at %s\n", + RU: "WDK (%s) %s установлен в %s\n", + }, + "download.workloads_header": { + EN: "Available Workloads (%d):\n", + RU: "Доступные рабочие нагрузки (Workload) (%d):\n", + }, + "download.components_header": { + EN: "Available Components (%d):\n", + RU: "Доступные компоненты (Component) (%d):\n", + }, + "download.license_prompt": { + EN: "Do you accept the license at %s (yes/no)? ", + RU: "Вы принимаете лицензию по адресу %s (yes/no)? ", + }, + "download.license_reprompt": { + EN: "Do you accept the license? Answer \"yes\" or \"no\": ", + RU: "Вы принимаете лицензию? Ответьте «yes» или «no»: ", + }, +}