aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorSimon Rozman <simon@rozman.si>2019-11-14 09:27:05 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2020-03-15 14:49:27 -0600
commitf0d22956ccfb958d2f1e2bd4f542b70c4460ce9b (patch)
tree3fac4f2940c3012734a4147ef627e2b52a27a859
parentmanager: chdir into unelevated profile before execing (diff)
downloadwireguard-windows-f0d22956ccfb958d2f1e2bd4f542b70c4460ce9b.tar.xz
wireguard-windows-f0d22956ccfb958d2f1e2bd4f542b70c4460ce9b.zip
l18n: add localization support
Revise the messages to make them localizable. Note: The log messages are not marked for localization. Probably, we want to keep log files in English for easier global troubleshooting. Having a user run `go generate` requires a valid and up-to-date Go environment. Rather than instructing users how to setup the environment correctly, the `go generate` was integrated into build.bat. This reuses the Go building environment downloaded and prepared by build.bat to provide controllable and consistent result. Use `make generate` on Linux. As the zgotext.go output varies for GOARCH=386 and amd64, one had to be chosen to provide stable output. The former is the first one to build in build.bat. Signed-off-by: Simon Rozman <simon@rozman.si>
-rw-r--r--.gitignore1
-rw-r--r--Makefile5
-rw-r--r--README.md22
-rw-r--r--build.bat4
-rw-r--r--conf/config.go44
-rw-r--r--conf/parser.go63
-rw-r--r--go.sum1
-rw-r--r--l18n/l18n.go65
-rw-r--r--locales/en/messages.gotext.json1949
-rw-r--r--main.go38
-rw-r--r--resources.rc64
-rw-r--r--ui/aboutdialog.go12
-rw-r--r--ui/confview.go54
-rw-r--r--ui/editdialog.go34
-rw-r--r--ui/filesave.go7
-rw-r--r--ui/iconprovider.go11
-rw-r--r--ui/logpage.go19
-rw-r--r--ui/managewindow.go7
-rw-r--r--ui/raise.go5
-rw-r--r--ui/tray.go41
-rw-r--r--ui/tunnelspage.go83
-rw-r--r--ui/ui.go7
-rw-r--r--ui/updatepage.go23
-rw-r--r--zgotext.go357
24 files changed, 2669 insertions, 247 deletions
diff --git a/.gitignore b/.gitignore
index ad8d0739..3883ded0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
/amd64
# Misc
+/locales/*/out.gotext.json
/sign.bat
*.swp
*.bak
diff --git a/Makefile b/Makefile
index 1fee2326..129964a0 100644
--- a/Makefile
+++ b/Makefile
@@ -46,6 +46,11 @@ fmt: export GOARCH := amd64
fmt:
go fmt ./...
+generate: export CC := i686-w64-mingw32-gcc
+generate: export GOARCH := 386
+generate:
+ go generate
+
deploy: amd64/wireguard.exe
-ssh $(DEPLOYMENT_HOST) -- 'taskkill /im wireguard.exe /f'
scp $< $(DEPLOYMENT_HOST):$(DEPLOYMENT_PATH)
diff --git a/README.md b/README.md
index 940f4b33..f0b18455 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,28 @@ C:\Projects\wireguard-windows> amd64\wireguard.exe
Since WireGuard requires the Wintun driver to be installed, and this generally requires a valid Microsoft signature, you may benefit from first installing a release of WireGuard for Windows from the official [wireguard.com](https://www.wireguard.com/install/) builds, which bundles a Microsoft-signed Wintun, and then subsequently run your own wireguard.exe. Alternatively, you can craft your own installer using the `quickinstall.bat` script.
+### Optional: Localizing
+
+To translate WireGuard UI to your language:
+
+1. Upgrade `resources.rc` accordingly. Follow the pattern.
+
+2. Add your language ID to the `//go:generate go run golang.org/x/text/cmd/gotext ... -lang=en,<langID>...` line in `main.go`.
+
+3. Configure and run `build` to prepare initial `locales\<langID>\messages.gotext.json` file:
+
+ ```
+ C:\Projects\wireguard-windows> set GenerateLocalizations=yes
+ C:\Projects\wireguard-windows> build
+ C:\Projects\wireguard-windows> copy locales\<langID>\out.gotext.json locales\<langID>\messages.gotext.json
+ ```
+
+4. Translate `locales\<langID>\messages.gotext.json`. See other language message files how to translate messages and how to tackle plural.
+
+5. Run `build` from the step 3 again, and test.
+
+6. Repeat from step 4.
+
### Optional: Creating the Installer
The installer build script will take care of downloading, verifying, and extracting the right versions of the various dependencies:
diff --git a/build.bat b/build.bat
index 53cc5531..f951f069 100644
--- a/build.bat
+++ b/build.bat
@@ -70,6 +70,10 @@ if exist .deps\prepared goto :render
mkdir %1 >NUL 2>&1
echo [+] Assembling resources %1
windres -i resources.rc -o resources.syso -O coff || exit /b %errorlevel%
+ if "%GenerateLocalizations%|%1"=="yes|x86" (
+ echo [+] Generating localizations %1
+ go generate || exit /b 1
+ )
echo [+] Building program %1
go build -ldflags="-H windowsgui -s -w" -tags walk_use_cgo -trimpath -v -o "%~1\wireguard.exe" || exit /b 1
if not exist "%~1\wg.exe" (
diff --git a/conf/config.go b/conf/config.go
index a84dc418..f4d8478a 100644
--- a/conf/config.go
+++ b/conf/config.go
@@ -16,6 +16,8 @@ import (
"time"
"golang.org/x/crypto/curve25519"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
const KeyLength = 32
@@ -131,18 +133,6 @@ func NewPrivateKeyFromString(b64 string) (*Key, error) {
return parseKeyBase64(b64)
}
-func formatInterval(i int64, n string, l int) string {
- r := ""
- if l > 0 {
- r += ", "
- }
- r += fmt.Sprintf("%d %s", i, n)
- if i != 1 {
- r += "s"
- }
- return r
-}
-
func (t HandshakeTime) IsEmpty() bool {
return t == HandshakeTime(0)
}
@@ -151,9 +141,9 @@ func (t HandshakeTime) String() string {
u := time.Unix(0, 0).Add(time.Duration(t)).Unix()
n := time.Now().Unix()
if u == n {
- return "Now"
+ return l18n.Sprintf("Now")
} else if u > n {
- return "System clock wound backward!"
+ return l18n.Sprintf("System clock wound backward!")
}
left := n - u
years := left / (365 * 24 * 60 * 60)
@@ -164,37 +154,37 @@ func (t HandshakeTime) String() string {
left = left % (60 * 60)
minutes := left / 60
seconds := left % 60
- s := ""
+ s := make([]string, 0, 5)
if years > 0 {
- s += formatInterval(years, "year", len(s))
+ s = append(s, l18n.Sprintf("%d year(s)", years))
}
if days > 0 {
- s += formatInterval(days, "day", len(s))
+ s = append(s, l18n.Sprintf("%d day(s)", days))
}
if hours > 0 {
- s += formatInterval(hours, "hour", len(s))
+ s = append(s, l18n.Sprintf("%d hour(s)", hours))
}
if minutes > 0 {
- s += formatInterval(minutes, "minute", len(s))
+ s = append(s, l18n.Sprintf("%d minute(s)", minutes))
}
if seconds > 0 {
- s += formatInterval(seconds, "second", len(s))
+ s = append(s, l18n.Sprintf("%d second(s)", seconds))
}
- s += " ago"
- return s
+ timestamp := strings.Join(s, l18n.EnumerationSeparator())
+ return l18n.Sprintf("%s ago", timestamp)
}
func (b Bytes) String() string {
if b < 1024 {
- return fmt.Sprintf("%d B", b)
+ return l18n.Sprintf("%d\u00a0B", b)
} else if b < 1024*1024 {
- return fmt.Sprintf("%.2f KiB", float64(b)/1024)
+ return l18n.Sprintf("%.2f\u00a0KiB", float64(b)/1024)
} else if b < 1024*1024*1024 {
- return fmt.Sprintf("%.2f MiB", float64(b)/(1024*1024))
+ return l18n.Sprintf("%.2f\u00a0MiB", float64(b)/(1024*1024))
} else if b < 1024*1024*1024*1024 {
- return fmt.Sprintf("%.2f GiB", float64(b)/(1024*1024*1024))
+ return l18n.Sprintf("%.2f\u00a0GiB", float64(b)/(1024*1024*1024))
}
- return fmt.Sprintf("%.2f TiB", float64(b)/(1024*1024*1024)/1024)
+ return l18n.Sprintf("%.2f\u00a0TiB", float64(b)/(1024*1024*1024)/1024)
}
func (conf *Config) DeduplicateNetworkEntries() {
diff --git a/conf/parser.go b/conf/parser.go
index 3f64677b..5f44edb2 100644
--- a/conf/parser.go
+++ b/conf/parser.go
@@ -8,13 +8,14 @@ package conf
import (
"encoding/base64"
"encoding/hex"
- "fmt"
"net"
"strconv"
"strings"
"time"
"golang.org/x/text/encoding/unicode"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
type ParseError struct {
@@ -23,7 +24,7 @@ type ParseError struct {
}
func (e *ParseError) Error() string {
- return fmt.Sprintf("%s: %q", e.why, e.offender)
+ return l18n.Sprintf("%s: %q", e.why, e.offender)
}
func parseIPCidr(s string) (ipcidr *IPCidr, err error) {
@@ -37,7 +38,7 @@ func parseIPCidr(s string) (ipcidr *IPCidr, err error) {
addrStr, cidrStr = s[:i], s[i+1:]
}
- err = &ParseError{"Invalid IP address", s}
+ err = &ParseError{l18n.Sprintf("Invalid IP address"), s}
addr := net.ParseIP(addrStr)
if addr == nil {
return
@@ -47,7 +48,7 @@ func parseIPCidr(s string) (ipcidr *IPCidr, err error) {
addr = maybeV4
}
if len(cidrStr) > 0 {
- err = &ParseError{"Invalid network prefix length", s}
+ err = &ParseError{l18n.Sprintf("Invalid network prefix length"), s}
cidr, err = strconv.Atoi(cidrStr)
if err != nil || cidr < 0 || cidr > 128 {
return
@@ -68,11 +69,11 @@ func parseIPCidr(s string) (ipcidr *IPCidr, err error) {
func parseEndpoint(s string) (*Endpoint, error) {
i := strings.LastIndexByte(s, ':')
if i < 0 {
- return nil, &ParseError{"Missing port from endpoint", s}
+ return nil, &ParseError{l18n.Sprintf("Missing port from endpoint"), s}
}
host, portStr := s[:i], s[i+1:]
if len(host) < 1 {
- return nil, &ParseError{"Invalid endpoint host", host}
+ return nil, &ParseError{l18n.Sprintf("Invalid endpoint host"), host}
}
port, err := parsePort(portStr)
if err != nil {
@@ -80,7 +81,7 @@ func parseEndpoint(s string) (*Endpoint, error) {
}
hostColon := strings.IndexByte(host, ':')
if host[0] == '[' || host[len(host)-1] == ']' || hostColon > 0 {
- err := &ParseError{"Brackets must contain an IPv6 address", host}
+ err := &ParseError{l18n.Sprintf("Brackets must contain an IPv6 address"), host}
if len(host) > 3 && host[0] == '[' && host[len(host)-1] == ']' && hostColon > 0 {
end := len(host) - 1
if i := strings.LastIndexByte(host, '%'); i > 1 {
@@ -104,7 +105,7 @@ func parseMTU(s string) (uint16, error) {
return 0, err
}
if m < 576 || m > 65535 {
- return 0, &ParseError{"Invalid MTU", s}
+ return 0, &ParseError{l18n.Sprintf("Invalid MTU"), s}
}
return uint16(m), nil
}
@@ -115,7 +116,7 @@ func parsePort(s string) (uint16, error) {
return 0, err
}
if m < 0 || m > 65535 {
- return 0, &ParseError{"Invalid port", s}
+ return 0, &ParseError{l18n.Sprintf("Invalid port"), s}
}
return uint16(m), nil
}
@@ -129,7 +130,7 @@ func parsePersistentKeepalive(s string) (uint16, error) {
return 0, err
}
if m < 0 || m > 65535 {
- return 0, &ParseError{"Invalid persistent keepalive", s}
+ return 0, &ParseError{l18n.Sprintf("Invalid persistent keepalive"), s}
}
return uint16(m), nil
}
@@ -137,10 +138,10 @@ func parsePersistentKeepalive(s string) (uint16, error) {
func parseKeyBase64(s string) (*Key, error) {
k, err := base64.StdEncoding.DecodeString(s)
if err != nil {
- return nil, &ParseError{"Invalid key: " + err.Error(), s}
+ return nil, &ParseError{l18n.Sprintf("Invalid key: %v", err), s}
}
if len(k) != KeyLength {
- return nil, &ParseError{"Keys must decode to exactly 32 bytes", s}
+ return nil, &ParseError{l18n.Sprintf("Keys must decode to exactly 32 bytes"), s}
}
var key Key
copy(key[:], k)
@@ -150,10 +151,10 @@ func parseKeyBase64(s string) (*Key, error) {
func parseKeyHex(s string) (*Key, error) {
k, err := hex.DecodeString(s)
if err != nil {
- return nil, &ParseError{"Invalid key: " + err.Error(), s}
+ return nil, &ParseError{l18n.Sprintf("Invalid key: %v", err), s}
}
if len(k) != KeyLength {
- return nil, &ParseError{"Keys must decode to exactly 32 bytes", s}
+ return nil, &ParseError{l18n.Sprintf("Keys must decode to exactly 32 bytes"), s}
}
var key Key
copy(key[:], k)
@@ -163,7 +164,7 @@ func parseKeyHex(s string) (*Key, error) {
func parseBytesOrStamp(s string) (uint64, error) {
b, err := strconv.ParseUint(s, 10, 64)
if err != nil {
- return 0, &ParseError{"Number must be a number between 0 and 2^64-1: " + err.Error(), s}
+ return 0, &ParseError{l18n.Sprintf("Number must be a number between 0 and 2^64-1: %v", err), s}
}
return b, nil
}
@@ -173,7 +174,7 @@ func splitList(s string) ([]string, error) {
for _, split := range strings.Split(s, ",") {
trim := strings.TrimSpace(split)
if len(trim) == 0 {
- return nil, &ParseError{"Two commas in a row", s}
+ return nil, &ParseError{l18n.Sprintf("Two commas in a row"), s}
}
out = append(out, trim)
}
@@ -196,7 +197,7 @@ func (c *Config) maybeAddPeer(p *Peer) {
func FromWgQuick(s string, name string) (*Config, error) {
if !TunnelNameIsValid(name) {
- return nil, &ParseError{"Tunnel name is not valid", name}
+ return nil, &ParseError{l18n.Sprintf("Tunnel name is not valid"), name}
}
lines := strings.Split(s, "\n")
parserState := notInASection
@@ -225,15 +226,15 @@ func FromWgQuick(s string, name string) (*Config, error) {
continue
}
if parserState == notInASection {
- return nil, &ParseError{"Line must occur in a section", line}
+ return nil, &ParseError{l18n.Sprintf("Line must occur in a section"), line}
}
equals := strings.IndexByte(line, '=')
if equals < 0 {
- return nil, &ParseError{"Invalid config key is missing an equals separator", line}
+ return nil, &ParseError{l18n.Sprintf("Invalid config key is missing an equals separator"), line}
}
key, val := strings.TrimSpace(lineLower[:equals]), strings.TrimSpace(line[equals+1:])
if len(val) == 0 {
- return nil, &ParseError{"Key must have a value", line}
+ return nil, &ParseError{l18n.Sprintf("Key must have a value"), line}
}
if parserState == inInterfaceSection {
switch key {
@@ -276,12 +277,12 @@ func FromWgQuick(s string, name string) (*Config, error) {
for _, address := range addresses {
a := net.ParseIP(address)
if a == nil {
- return nil, &ParseError{"Invalid IP address", address}
+ return nil, &ParseError{l18n.Sprintf("Invalid IP address"), address}
}
conf.Interface.DNS = append(conf.Interface.DNS, a)
}
default:
- return nil, &ParseError{"Invalid key for [Interface] section", key}
+ return nil, &ParseError{l18n.Sprintf("Invalid key for [Interface] section"), key}
}
} else if parserState == inPeerSection {
switch key {
@@ -322,18 +323,18 @@ func FromWgQuick(s string, name string) (*Config, error) {
}
peer.Endpoint = *e
default:
- return nil, &ParseError{"Invalid key for [Peer] section", key}
+ return nil, &ParseError{l18n.Sprintf("Invalid key for [Peer] section"), key}
}
}
}
conf.maybeAddPeer(peer)
if !sawPrivateKey {
- return nil, &ParseError{"An interface must have a private key", "[none specified]"}
+ return nil, &ParseError{l18n.Sprintf("An interface must have a private key"), l18n.Sprintf("[none specified]")}
}
for _, p := range conf.Peers {
if p.PublicKey.IsZero() {
- return nil, &ParseError{"All peers must have public keys", "[none specified]"}
+ return nil, &ParseError{l18n.Sprintf("All peers must have public keys"), l18n.Sprintf("[none specified]")}
}
}
@@ -375,11 +376,11 @@ func FromUAPI(s string, existingConfig *Config) (*Config, error) {
}
equals := strings.IndexByte(line, '=')
if equals < 0 {
- return nil, &ParseError{"Invalid config key is missing an equals separator", line}
+ return nil, &ParseError{l18n.Sprintf("Invalid config key is missing an equals separator"), line}
}
key, val := line[:equals], line[equals+1:]
if len(val) == 0 {
- return nil, &ParseError{"Key must have a value", line}
+ return nil, &ParseError{l18n.Sprintf("Key must have a value"), line}
}
switch key {
case "public_key":
@@ -390,7 +391,7 @@ func FromUAPI(s string, existingConfig *Config) (*Config, error) {
if val == "0" {
continue
} else {
- return nil, &ParseError{"Error in getting configuration", val}
+ return nil, &ParseError{l18n.Sprintf("Error in getting configuration"), val}
}
}
if parserState == inInterfaceSection {
@@ -411,7 +412,7 @@ func FromUAPI(s string, existingConfig *Config) (*Config, error) {
// Ignored for now.
default:
- return nil, &ParseError{"Invalid key for interface section", key}
+ return nil, &ParseError{l18n.Sprintf("Invalid key for interface section"), key}
}
} else if parserState == inPeerSection {
switch key {
@@ -429,7 +430,7 @@ func FromUAPI(s string, existingConfig *Config) (*Config, error) {
peer.PresharedKey = *k
case "protocol_version":
if val != "1" {
- return nil, &ParseError{"Protocol version must be 1", val}
+ return nil, &ParseError{l18n.Sprintf("Protocol version must be 1"), val}
}
case "allowed_ip":
a, err := parseIPCidr(val)
@@ -474,7 +475,7 @@ func FromUAPI(s string, existingConfig *Config) (*Config, error) {
}
peer.LastHandshakeTime += HandshakeTime(time.Duration(t) * time.Nanosecond)
default:
- return nil, &ParseError{"Invalid key for peer section", key}
+ return nil, &ParseError{l18n.Sprintf("Invalid key for peer section"), key}
}
}
}
diff --git a/go.sum b/go.sum
index 7f6dbede..63880f9b 100644
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de h1:aYKJLPSrddB2N7/6OKyFqJ337SXpo61bBuvO5p1+7iY=
golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.zx2c4.com/wireguard v0.0.20200122-0.20200214175355-9cbcff10dd3e h1:cEeaW64u6+FcYcO4/M+rpqJhb9pFl/9CGs5oqlN6ps8=
golang.zx2c4.com/wireguard v0.0.20200122-0.20200214175355-9cbcff10dd3e/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
diff --git a/l18n/l18n.go b/l18n/l18n.go
new file mode 100644
index 00000000..717e4861
--- /dev/null
+++ b/l18n/l18n.go
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package l18n
+
+import (
+ "sync"
+
+ "golang.org/x/sys/windows"
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
+)
+
+var printer *message.Printer
+var printerLock sync.Mutex
+
+// prn returns the printer for user preferred UI language.
+func prn() *message.Printer {
+ if printer != nil {
+ return printer
+ }
+ printerLock.Lock()
+ if printer != nil {
+ printerLock.Unlock()
+ return printer
+ }
+ printer = message.NewPrinter(lang())
+ printerLock.Unlock()
+ return printer
+}
+
+// lang returns the user preferred UI language we have most confident translation in the default catalog available.
+func lang() (tag language.Tag) {
+ tag = language.English
+ confidence := language.No
+ languages, err := windows.GetUserPreferredUILanguages(windows.MUI_LANGUAGE_NAME)
+ if err != nil {
+ return
+ }
+ for i := range languages {
+ t, _, c := message.DefaultCatalog.Matcher().Match(message.MatchLanguage(languages[i]))
+ if c > confidence {
+ tag = t
+ confidence = c
+ }
+ }
+ return
+}
+
+// Sprintf is like fmt.Sprintf, but using language-specific formatting.
+func Sprintf(key message.Reference, a ...interface{}) string {
+ return prn().Sprintf(key, a...)
+}
+
+// EnumerationSeparator returns enumeration separator. For English and western languages,
+// enumeration separator is a comma followed by a space (i.e. ", "). For Chinese, it returns
+// "\u3001".
+func EnumerationSeparator() string {
+ // BUG: We could just use `Sprintf(", " /* ...translator instructions... */)` and let the
+ // individual locale catalog handle its translation. Unfortunately, the gotext utility tries to
+ // be nice to translators and skips all strings without letters when updating catalogs.
+ return Sprintf("[EnumerationSeparator]" /* Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’. */)
+}
diff --git a/locales/en/messages.gotext.json b/locales/en/messages.gotext.json
new file mode 100644
index 00000000..cda1f59d
--- /dev/null
+++ b/locales/en/messages.gotext.json
@@ -0,0 +1,1949 @@
+{
+ "language": "en",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(no argument): elevate and install manager service",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Usage: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Command Line Options",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "You must use the 64-bit version of WireGuard on this computer.",
+ "message": "You must use the 64-bit version of WireGuard on this computer.",
+ "translation": "You must use the 64-bit version of WireGuard on this computer.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Unable to open current process token: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Now",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "System clock wound backward!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} year"
+ },
+ "other": {
+ "msg": "{Years} years"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} day"
+ },
+ "other": {
+ "msg": "{Days} days"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} hour"
+ },
+ "other": {
+ "msg": "{Hours} hours"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minute"
+ },
+ "other": {
+ "msg": "{Minutes} minutes"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} second"
+ },
+ "other": {
+ "msg": "{Seconds} seconds"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} ago",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Invalid IP address",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Invalid network prefix length",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Missing port from endpoint",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Invalid endpoint host",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Brackets must contain an IPv6 address",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Invalid MTU",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Invalid port",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Invalid persistent keepalive",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Invalid key: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Keys must decode to exactly 32 bytes",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Two commas in a row",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Tunnel name is not valid",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Line must occur in a section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid config key is missing an equals separator",
+ "message": "Invalid config key is missing an equals separator",
+ "translation": "Invalid config key is missing an equals separator",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Key must have a value",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Invalid key for [Interface] section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Invalid key for [Peer] section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "An interface must have a private key",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[none specified]",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "All peers must have public keys",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Error in getting configuration",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Invalid key for interface section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Protocol version must be 1",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Invalid key for peer section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "About WireGuard",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard logo image",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}\nOperating system: {OsName}\nArchitecture: {GOARCH}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}\nOperating system: {OsName}\nArchitecture: {GOARCH}",
+ "translation": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}\nOperating system: {OsName}\nArchitecture: {GOARCH}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "OsName",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "runtime.GOARCH"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Close",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "♥ \u0026Donate!",
+ "message": "♥ \u0026Donate!",
+ "translation": "♥ \u0026Donate!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Status:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Deactivate",
+ "message": "\u0026Deactivate",
+ "translation": "\u0026Deactivate",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Activate",
+ "message": "\u0026Activate",
+ "translation": "\u0026Activate",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Public key:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Listen port:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Addresses:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS servers:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Preshared key:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Allowed IPs:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Endpoint:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Persistent keepalive:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Latest handshake:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Transfer:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "enabled",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} received, {String_1} sent",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Failed to determine tunnel state",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Failed to activate tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Failed to deactivate tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Interface: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Peer",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Create new tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Edit tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Name:",
+ "message": "\u0026Name:",
+ "translation": "\u0026Name:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Public key:",
+ "message": "\u0026Public key:",
+ "translation": "\u0026Public key:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(unknown)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Block untunneled traffic (kill-switch)",
+ "message": "\u0026Block untunneled traffic (kill-switch)",
+ "translation": "\u0026Block untunneled traffic (kill-switch)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.",
+ "translation": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Save",
+ "message": "\u0026Save",
+ "translation": "\u0026Save",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Cancel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Configuration:",
+ "message": "\u0026Configuration:",
+ "translation": "\u0026Configuration:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Invalid name",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "A name is required.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Tunnel name ‘{NewName}’ is invalid.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Unable to list existing tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunnel already exists",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Unable to create new configuration",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Writing file failed",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Active",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Activating",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Inactive",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Deactivating",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Unknown state",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Log",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Copy",
+ "message": "\u0026Copy",
+ "translation": "\u0026Copy",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Select \u0026all",
+ "message": "Select \u0026all",
+ "translation": "Select \u0026all",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Save to file…",
+ "message": "\u0026Save to file…",
+ "translation": "\u0026Save to file…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Time",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Log message",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Export log to file",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026About WireGuard…",
+ "message": "\u0026About WireGuard…",
+ "translation": "\u0026About WireGuard…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Tunnel Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (out of date)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "WireGuard Detection Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Unable to wait for WireGuard window to appear: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Deactivated",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Status: Unknown",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Addresses: None",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Manage tunnels…",
+ "message": "\u0026Manage tunnels…",
+ "translation": "\u0026Manage tunnels…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Import tunnel(s) from file…",
+ "message": "\u0026Import tunnel(s) from file…",
+ "translation": "\u0026Import tunnel(s) from file…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "E\u0026xit",
+ "message": "E\u0026xit",
+ "translation": "E\u0026xit",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard Tunnel Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Status: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Addresses: {String}",
+ "message": "Addresses: {String}",
+ "translation": "Addresses: {String}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "sb.String()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard Activated",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "The {Name} tunnel has been activated.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard Deactivated",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "The {Name} tunnel has been deactivated.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "An Update is Available!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard Update Available",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Edit",
+ "message": "\u0026Edit",
+ "translation": "\u0026Edit",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Add \u0026empty tunnel…",
+ "message": "Add \u0026empty tunnel…",
+ "translation": "Add \u0026empty tunnel…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Add Tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Remove selected tunnel(s)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Export all tunnels to zip",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Toggle",
+ "message": "\u0026Toggle",
+ "translation": "\u0026Toggle",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export all tunnels to \u0026zip…",
+ "message": "Export all tunnels to \u0026zip…",
+ "translation": "Export all tunnels to \u0026zip…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Edit \u0026selected tunnel…",
+ "message": "Edit \u0026selected tunnel…",
+ "translation": "Edit \u0026selected tunnel…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Remove selected tunnel(s)",
+ "message": "\u0026Remove selected tunnel(s)",
+ "translation": "\u0026Remove selected tunnel(s)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Could not import selected configuration: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Could not enumerate existing tunnels: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Another tunnel already exists with the name ‘{Name}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Unable to import configuration: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Imported tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Imported {M} tunnel"
+ },
+ "other": {
+ "msg": "Imported {M} tunnels"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Imported {M} of {N} tunnel"
+ },
+ "other": {
+ "msg": "Imported {M} of {N} tunnels"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Unable to create tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Delete {TunnelCount} tunnel"
+ },
+ "other": {
+ "msg": "Delete {TunnelCount} tunnels"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Are you sure you would like to delete {TunnelCount} tunnel?"
+ },
+ "other": {
+ "msg": "Are you sure you would like to delete {TunnelCount} tunnels?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Delete tunnel ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} You cannot undo this action.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Unable to delete tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "A tunnel was unable to be removed: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Unable to delete tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tunnel was unable to be removed."
+ },
+ "other": {
+ "msg": "{Lenerrors} tunnels were unable to be removed."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Import tunnel(s) from file",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Configuration ZIP Files (*.zip)|*.zip",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Export tunnels to zip",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (unsigned build, no updates)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Error Exiting WireGuard",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Status: Waiting for user",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Update Now",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Status: Waiting for updater service",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Error: {Err}. Please try again.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Status: Complete!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: failed to decode just-written frame",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: decoded hpack field {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ],
+ "fuzzy": true
+ }
+ ]
+} \ No newline at end of file
diff --git a/main.go b/main.go
index a84b4205..56c0e91e 100644
--- a/main.go
+++ b/main.go
@@ -5,6 +5,8 @@
package main
+//go:generate go run golang.org/x/text/cmd/gotext -srclang=en update -out=zgotext.go -lang=en
+
import (
"fmt"
"os"
@@ -15,43 +17,43 @@ import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/elevate"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/ringlogger"
"golang.zx2c4.com/wireguard/windows/tunnel"
"golang.zx2c4.com/wireguard/windows/ui"
)
-var flags = [...]string{
- "(no argument): elevate and install manager service for current user",
- "/installmanagerservice",
- "/installtunnelservice CONFIG_PATH",
- "/uninstallmanagerservice",
- "/uninstalltunnelservice TUNNEL_NAME",
- "/managerservice",
- "/tunnelservice CONFIG_PATH",
- "/ui CMD_READ_HANDLE CMD_WRITE_HANDLE CMD_EVENT_HANDLE LOG_MAPPING_HANDLE",
- "/dumplog OUTPUT_PATH",
-}
-
func fatal(v ...interface{}) {
- windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprint(v...)), windows.StringToUTF16Ptr("Error"), windows.MB_ICONERROR)
+ windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprint(v...)), windows.StringToUTF16Ptr(l18n.Sprintf("Error")), windows.MB_ICONERROR)
os.Exit(1)
}
func fatalf(format string, v ...interface{}) {
- fatal(fmt.Sprintf(format, v...))
+ fatal(l18n.Sprintf(format, v...))
}
func info(title string, format string, v ...interface{}) {
- windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprintf(format, v...)), windows.StringToUTF16Ptr(title), windows.MB_ICONINFORMATION)
+ windows.MessageBox(0, windows.StringToUTF16Ptr(l18n.Sprintf(format, v...)), windows.StringToUTF16Ptr(title), windows.MB_ICONINFORMATION)
}
func usage() {
+ var flags = [...]string{
+ l18n.Sprintf("(no argument): elevate and install manager service"),
+ "/installmanagerservice",
+ "/installtunnelservice CONFIG_PATH",
+ "/uninstallmanagerservice",
+ "/uninstalltunnelservice TUNNEL_NAME",
+ "/managerservice",
+ "/tunnelservice CONFIG_PATH",
+ "/ui CMD_READ_HANDLE CMD_WRITE_HANDLE CMD_EVENT_HANDLE LOG_MAPPING_HANDLE",
+ "/dumplog OUTPUT_PATH",
+ }
builder := strings.Builder{}
for _, flag := range flags {
builder.WriteString(fmt.Sprintf(" %s\n", flag))
}
- info("Command Line Options", "Usage: %s [\n%s]", os.Args[0], builder.String())
+ info(l18n.Sprintf("Command Line Options"), "Usage: %s [\n%s]", os.Args[0], builder.String())
os.Exit(1)
}
@@ -62,7 +64,7 @@ func checkForWow64() {
fatalf("Unable to determine whether the process is running under WOW64: %v", err)
}
if b {
- fatal("You must use the 64-bit version of WireGuard on this computer.")
+ fatalf("You must use the 64-bit version of WireGuard on this computer.")
}
}
@@ -136,7 +138,7 @@ func main() {
}
checkForAdminDesktop()
time.Sleep(30 * time.Second)
- fatal("WireGuard system tray icon did not appear after 30 seconds.")
+ fatalf("WireGuard system tray icon did not appear after 30 seconds.")
return
case "/uninstallmanagerservice":
if len(os.Args) != 2 {
diff --git a/resources.rc b/resources.rc
index 845e9684..9ea7ef58 100644
--- a/resources.rc
+++ b/resources.rc
@@ -6,35 +6,45 @@
#include <windows.h>
#include "version/version.h"
-CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
+#pragma code_page(65001) // UTF-8
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
$wireguard.ico ICON ui/icon/wireguard.ico
dot-gray.ico ICON ui/icon/dot-gray.ico
-VS_VERSION_INFO VERSIONINFO
-FILEVERSION WIREGUARD_WINDOWS_VERSION_ARRAY
-PRODUCTVERSION WIREGUARD_WINDOWS_VERSION_ARRAY
-FILEOS VOS_NT_WINDOWS32
-FILETYPE VFT_APP
-FILESUBTYPE VFT2_UNKNOWN
-BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "040904b0"
- BEGIN
- VALUE "CompanyName", "WireGuard LLC"
- VALUE "FileDescription", "WireGuard: Fast, Modern, Secure VPN Tunnel"
- VALUE "FileVersion", WIREGUARD_WINDOWS_VERSION_STRING
- VALUE "InternalName", "wireguard"
- VALUE "LegalCopyright", "Copyright \xa9 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved."
- VALUE "OriginalFilename", "wireguard.exe"
- VALUE "ProductName", "WireGuard"
- VALUE "ProductVersion", WIREGUARD_WINDOWS_VERSION_STRING
- VALUE "Comments", "https://www.wireguard.com/"
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x409, 1200
- END
+#define VERSIONINFO_TEMPLATE(block_id, lang_id, codepage_id, file_desc, comments) \
+VS_VERSION_INFO VERSIONINFO \
+FILEVERSION WIREGUARD_WINDOWS_VERSION_ARRAY \
+PRODUCTVERSION WIREGUARD_WINDOWS_VERSION_ARRAY \
+FILEOS VOS_NT_WINDOWS32 \
+FILETYPE VFT_APP \
+FILESUBTYPE VFT2_UNKNOWN \
+BEGIN \
+ BLOCK "StringFileInfo" \
+ BEGIN \
+ BLOCK block_id \
+ BEGIN \
+ VALUE "CompanyName", "WireGuard LLC" \
+ VALUE "FileDescription", file_desc \
+ VALUE "FileVersion", WIREGUARD_WINDOWS_VERSION_STRING \
+ VALUE "InternalName", "wireguard-windows" \
+ VALUE "LegalCopyright", "Copyright © 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved." \
+ VALUE "OriginalFilename", "wireguard.exe" \
+ VALUE "ProductName", "WireGuard" \
+ VALUE "ProductVersion", WIREGUARD_WINDOWS_VERSION_STRING \
+ VALUE "Comments", comments \
+ END \
+ END \
+ BLOCK "VarFileInfo" \
+ BEGIN \
+ VALUE "Translation", lang_id, codepage_id \
+ END \
END
+
+LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
+VERSIONINFO_TEMPLATE(
+ "040904b0", 0x409, 0x4b0,
+ "WireGuard: Fast, Modern, Secure VPN Tunnel",
+ "https://www.wireguard.com/"
+)
diff --git a/ui/aboutdialog.go b/ui/aboutdialog.go
index d87727aa..238ba5e2 100644
--- a/ui/aboutdialog.go
+++ b/ui/aboutdialog.go
@@ -6,7 +6,6 @@
package ui
import (
- "fmt"
"runtime"
"strings"
@@ -14,6 +13,7 @@ import (
"github.com/lxn/win"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/device"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/version"
)
@@ -47,7 +47,7 @@ func runAboutDialog(owner walk.Form) error {
showingAboutDialog = nil
}()
disposables.Add(showingAboutDialog)
- showingAboutDialog.SetTitle("About WireGuard")
+ showingAboutDialog.SetTitle(l18n.Sprintf("About WireGuard"))
showingAboutDialog.SetLayout(vbl)
if icon, err := loadLogoIcon(32); err == nil {
showingAboutDialog.SetIcon(icon)
@@ -79,7 +79,7 @@ func runAboutDialog(owner walk.Form) error {
if logo, err := loadLogoIcon(128); err == nil {
iv.SetImage(logo)
}
- iv.Accessibility().SetName("WireGuard logo image")
+ iv.Accessibility().SetName(l18n.Sprintf("WireGuard logo image"))
wgLbl, err := walk.NewTextLabel(showingAboutDialog)
if err != nil {
@@ -95,7 +95,7 @@ func runAboutDialog(owner walk.Form) error {
return err
}
detailsLbl.SetTextAlignment(walk.AlignHCenterVNear)
- detailsLbl.SetText(fmt.Sprintf("App version: %s\nGo backend version: %s\nGo version: %s\nOperating system: %s\nArchitecture: %s", version.Number, device.WireGuardGoVersion, strings.TrimPrefix(runtime.Version(), "go"), version.OsName(), runtime.GOARCH))
+ detailsLbl.SetText(l18n.Sprintf("App version: %s\nGo backend version: %s\nGo version: %s\nOperating system: %s\nArchitecture: %s", version.Number, device.WireGuardGoVersion, strings.TrimPrefix(runtime.Version(), "go"), version.OsName(), runtime.GOARCH))
copyrightLbl, err := walk.NewTextLabel(showingAboutDialog)
if err != nil {
@@ -119,14 +119,14 @@ func runAboutDialog(owner walk.Form) error {
return err
}
closePB.SetAlignment(walk.AlignHCenterVNear)
- closePB.SetText("Close")
+ closePB.SetText(l18n.Sprintf("Close"))
closePB.Clicked().Attach(showingAboutDialog.Accept)
donatePB, err := walk.NewPushButton(buttonCP)
if err != nil {
return err
}
donatePB.SetAlignment(walk.AlignHCenterVNear)
- donatePB.SetText("♥ &Donate!")
+ donatePB.SetText(l18n.Sprintf("♥ &Donate!"))
donatePB.Clicked().Attach(func() {
if easterEggIndex == -1 {
easterEggIndex = 0
diff --git a/ui/confview.go b/ui/confview.go
index 1c378aa1..d080db30 100644
--- a/ui/confview.go
+++ b/ui/confview.go
@@ -6,7 +6,6 @@
package ui
import (
- "fmt"
"strconv"
"strings"
"time"
@@ -15,6 +14,7 @@ import (
"github.com/lxn/win"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -108,7 +108,7 @@ func newLabelStatusLine(parent walk.Container) (*labelStatusLine, error) {
return nil, err
}
disposables.Add(lsl.label)
- lsl.label.SetText("Status:")
+ lsl.label.SetText(l18n.Sprintf("Status:"))
lsl.label.SetTextAlignment(walk.AlignHFarVNear)
if lsl.statusComposite, err = walk.NewComposite(parent); err != nil {
@@ -180,7 +180,7 @@ func newLabelTextLine(fieldName string, parent walk.Container) (*labelTextLine,
return nil, err
}
disposables.Add(lt.label)
- lt.label.SetText(fieldName + ":")
+ lt.label.SetText(fieldName)
lt.label.SetTextAlignment(walk.AlignHFarVNear)
lt.label.SetVisible(false)
@@ -216,9 +216,9 @@ func (tal *toggleActiveLine) update(state manager.TunnelState) {
switch state {
case manager.TunnelStarted:
- text = "&Deactivate"
+ text = l18n.Sprintf("&Deactivate")
case manager.TunnelStopped:
- text = "&Activate"
+ text = l18n.Sprintf("&Activate")
case manager.TunnelStarting, manager.TunnelStopping:
text = textForState(state, true)
default:
@@ -300,11 +300,11 @@ func newInterfaceView(parent walk.Container) (*interfaceView, error) {
disposables.Add(iv.status)
items := []labelTextLineItem{
- {"Public key", &iv.publicKey},
- {"Listen port", &iv.listenPort},
- {"MTU", &iv.mtu},
- {"Addresses", &iv.addresses},
- {"DNS servers", &iv.dns},
+ {l18n.Sprintf("Public key:"), &iv.publicKey},
+ {l18n.Sprintf("Listen port:"), &iv.listenPort},
+ {l18n.Sprintf("MTU:"), &iv.mtu},
+ {l18n.Sprintf("Addresses:"), &iv.addresses},
+ {l18n.Sprintf("DNS servers:"), &iv.dns},
}
if iv.lines, err = createLabelTextLines(items, parent, &disposables); err != nil {
return nil, err
@@ -328,13 +328,13 @@ func newPeerView(parent walk.Container) (*peerView, error) {
pv := new(peerView)
items := []labelTextLineItem{
- {"Public key", &pv.publicKey},
- {"Preshared key", &pv.presharedKey},
- {"Allowed IPs", &pv.allowedIPs},
- {"Endpoint", &pv.endpoint},
- {"Persistent keepalive", &pv.persistentKeepalive},
- {"Latest handshake", &pv.latestHandshake},
- {"Transfer", &pv.transfer},
+ {l18n.Sprintf("Public key:"), &pv.publicKey},
+ {l18n.Sprintf("Preshared key:"), &pv.presharedKey},
+ {l18n.Sprintf("Allowed IPs:"), &pv.allowedIPs},
+ {l18n.Sprintf("Endpoint:"), &pv.endpoint},
+ {l18n.Sprintf("Persistent keepalive:"), &pv.persistentKeepalive},
+ {l18n.Sprintf("Latest handshake:"), &pv.latestHandshake},
+ {l18n.Sprintf("Transfer:"), &pv.transfer},
}
var err error
if pv.lines, err = createLabelTextLines(items, parent, nil); err != nil {
@@ -383,7 +383,7 @@ func (iv *interfaceView) apply(c *conf.Interface) {
for i, address := range c.Addresses {
addrStrings[i] = address.String()
}
- iv.addresses.show(strings.Join(addrStrings[:], ", "))
+ iv.addresses.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
} else {
iv.addresses.hide()
}
@@ -393,7 +393,7 @@ func (iv *interfaceView) apply(c *conf.Interface) {
for i, address := range c.DNS {
addrStrings[i] = address.String()
}
- iv.dns.show(strings.Join(addrStrings[:], ", "))
+ iv.dns.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
} else {
iv.dns.hide()
}
@@ -407,7 +407,7 @@ func (pv *peerView) apply(c *conf.Peer) {
pv.publicKey.show(c.PublicKey.String())
if !c.PresharedKey.IsZero() {
- pv.presharedKey.show("enabled")
+ pv.presharedKey.show(l18n.Sprintf("enabled"))
} else {
pv.presharedKey.hide()
}
@@ -417,7 +417,7 @@ func (pv *peerView) apply(c *conf.Peer) {
for i, address := range c.AllowedIPs {
addrStrings[i] = address.String()
}
- pv.allowedIPs.show(strings.Join(addrStrings[:], ", "))
+ pv.allowedIPs.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
} else {
pv.allowedIPs.hide()
}
@@ -441,7 +441,7 @@ func (pv *peerView) apply(c *conf.Peer) {
}
if c.RxBytes > 0 || c.TxBytes > 0 {
- pv.transfer.show(fmt.Sprintf("%s received, %s sent", c.RxBytes.String(), c.TxBytes.String()))
+ pv.transfer.show(l18n.Sprintf("%s received, %s sent", c.RxBytes.String(), c.TxBytes.String()))
} else {
pv.transfer.hide()
}
@@ -552,11 +552,11 @@ func (cv *ConfView) onToggleActiveClicked() {
if err != nil {
cv.Synchronize(func() {
if oldState == manager.TunnelUnknown {
- showErrorCustom(cv.Form(), "Failed to determine tunnel state", err.Error())
+ showErrorCustom(cv.Form(), l18n.Sprintf("Failed to determine tunnel state"), err.Error())
} else if oldState == manager.TunnelStopped {
- showErrorCustom(cv.Form(), "Failed to activate tunnel", err.Error())
+ showErrorCustom(cv.Form(), l18n.Sprintf("Failed to activate tunnel"), err.Error())
} else if oldState == manager.TunnelStarted {
- showErrorCustom(cv.Form(), "Failed to deactivate tunnel", err.Error())
+ showErrorCustom(cv.Form(), l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
}
})
}
@@ -612,7 +612,7 @@ func (cv *ConfView) setTunnel(tunnel *manager.Tunnel, config *conf.Config, state
return
}
- title := "Interface: " + config.Name
+ title := l18n.Sprintf("Interface: %s", config.Name)
if cv.name.Title() != title {
cv.SetSuspended(true)
defer cv.SetSuspended(false)
@@ -656,7 +656,7 @@ func (cv *ConfView) setTunnel(tunnel *manager.Tunnel, config *conf.Config, state
if err != nil {
continue
}
- group.SetTitle("Peer")
+ group.SetTitle(l18n.Sprintf("Peer"))
pv, err := newPeerView(group)
if err != nil {
group.Dispose()
diff --git a/ui/editdialog.go b/ui/editdialog.go
index 4d25dd79..ade6c498 100644
--- a/ui/editdialog.go
+++ b/ui/editdialog.go
@@ -6,7 +6,6 @@
package ui
import (
- "fmt"
"strings"
"github.com/lxn/walk"
@@ -14,6 +13,7 @@ import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/ui/syntax"
)
@@ -52,9 +52,9 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
var title string
if tunnel == nil {
- title = "Create new tunnel"
+ title = l18n.Sprintf("Create new tunnel")
} else {
- title = "Edit tunnel"
+ title = l18n.Sprintf("Edit tunnel")
}
if tunnel == nil {
@@ -88,7 +88,7 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
}
layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
- nameLabel.SetText("&Name:")
+ nameLabel.SetText(l18n.Sprintf("&Name:"))
if dlg.nameEdit, err = walk.NewLineEdit(dlg); err != nil {
return nil, err
@@ -102,14 +102,14 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
}
layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
- pubkeyLabel.SetText("&Public key:")
+ pubkeyLabel.SetText(l18n.Sprintf("&Public key:"))
if dlg.pubkeyEdit, err = walk.NewLineEdit(dlg); err != nil {
return nil, err
}
layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
dlg.pubkeyEdit.SetReadOnly(true)
- dlg.pubkeyEdit.SetText("(unknown)")
+ dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
dlg.pubkeyEdit.Accessibility().SetRole(walk.AccRoleStatictext)
if dlg.syntaxEdit, err = syntax.NewSyntaxEdit(dlg); err != nil {
@@ -128,8 +128,8 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
if dlg.blockUntunneledTrafficCB, err = walk.NewCheckBox(buttonsContainer); err != nil {
return nil, err
}
- dlg.blockUntunneledTrafficCB.SetText("&Block untunneled traffic (kill-switch)")
- dlg.blockUntunneledTrafficCB.SetToolTipText("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.")
+ dlg.blockUntunneledTrafficCB.SetText(l18n.Sprintf("&Block untunneled traffic (kill-switch)"))
+ dlg.blockUntunneledTrafficCB.SetToolTipText(l18n.Sprintf("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP."))
dlg.blockUntunneledTrafficCB.SetVisible(false)
dlg.blockUntunneledTrafficCB.CheckedChanged().Attach(dlg.onBlockUntunneledTrafficCBCheckedChanged)
@@ -138,14 +138,14 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
if dlg.saveButton, err = walk.NewPushButton(buttonsContainer); err != nil {
return nil, err
}
- dlg.saveButton.SetText("&Save")
+ dlg.saveButton.SetText(l18n.Sprintf("&Save"))
dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked)
cancelButton, err := walk.NewPushButton(buttonsContainer)
if err != nil {
return nil, err
}
- cancelButton.SetText("Cancel")
+ cancelButton.SetText(l18n.Sprintf("Cancel"))
cancelButton.Clicked().Attach(dlg.Cancel)
dlg.SetCancelButton(cancelButton)
@@ -160,7 +160,7 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
syntaxEditWnd := dlg.syntaxEdit.Handle()
parentWnd := win.GetParent(syntaxEditWnd)
labelWnd := win.CreateWindowEx(0,
- windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr("&Configuration:"),
+ windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr(l18n.Sprintf("&Configuration:")),
win.WS_CHILD|win.WS_GROUP|win.SS_LEFT, 0, 0, 0, 0,
parentWnd, win.HMENU(^uintptr(0)), win.HINSTANCE(win.GetWindowLongPtr(parentWnd, win.GWLP_HINSTANCE)), nil)
prevWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDPREV)
@@ -301,18 +301,18 @@ func (dlg *EditDialog) onSyntaxEditPrivateKeyChanged(privateKey string) {
if key != nil {
dlg.pubkeyEdit.SetText(key.Public().String())
} else {
- dlg.pubkeyEdit.SetText("(unknown)")
+ dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
}
}
func (dlg *EditDialog) onSaveButtonClicked() {
newName := dlg.nameEdit.Text()
if newName == "" {
- showWarningCustom(dlg, "Invalid name", "A name is required.")
+ showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("A name is required."))
return
}
if !conf.TunnelNameIsValid(newName) {
- showWarningCustom(dlg, "Invalid name", fmt.Sprintf("Tunnel name ‘%s’ is invalid.", newName))
+ showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("Tunnel name ‘%s’ is invalid.", newName))
return
}
newNameLower := strings.ToLower(newName)
@@ -320,12 +320,12 @@ func (dlg *EditDialog) onSaveButtonClicked() {
if newNameLower != strings.ToLower(dlg.config.Name) {
existingTunnelList, err := manager.IPCClientTunnels()
if err != nil {
- showWarningCustom(dlg, "Unable to list existing tunnels", err.Error())
+ showWarningCustom(dlg, l18n.Sprintf("Unable to list existing tunnels"), err.Error())
return
}
for _, tunnel := range existingTunnelList {
if strings.ToLower(tunnel.Name) == newNameLower {
- showWarningCustom(dlg, "Tunnel already exists", fmt.Sprintf("Another tunnel already exists with the name ‘%s’.", newName))
+ showWarningCustom(dlg, l18n.Sprintf("Tunnel already exists"), l18n.Sprintf("Another tunnel already exists with the name ‘%s’.", newName))
return
}
}
@@ -333,7 +333,7 @@ func (dlg *EditDialog) onSaveButtonClicked() {
cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName)
if err != nil {
- showErrorCustom(dlg, "Unable to create new configuration", err.Error())
+ showErrorCustom(dlg, l18n.Sprintf("Unable to create new configuration"), err.Error())
return
}
diff --git a/ui/filesave.go b/ui/filesave.go
index 4b5c2947..7ca8a2c2 100644
--- a/ui/filesave.go
+++ b/ui/filesave.go
@@ -6,10 +6,11 @@
package ui
import (
- "fmt"
"os"
"github.com/lxn/walk"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func(file *os.File) error) bool {
@@ -18,7 +19,7 @@ func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func
return false
}
- showErrorCustom(owner, "Writing file failed", err.Error())
+ showErrorCustom(owner, l18n.Sprintf("Writing file failed"), err.Error())
return true
}
@@ -26,7 +27,7 @@ func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600)
if err != nil {
if os.IsExist(err) {
- if walk.DlgCmdNo == walk.MsgBox(owner, "Writing file failed", fmt.Sprintf(`File ‘%s’ already exists.
+ if walk.DlgCmdNo == walk.MsgBox(owner, l18n.Sprintf("Writing file failed"), l18n.Sprintf(`File ‘%s’ already exists.
Do you want to overwrite it?`, filePath), walk.MsgBoxYesNo|walk.MsgBoxDefButton2|walk.MsgBoxIconWarning) {
return false
diff --git a/ui/iconprovider.go b/ui/iconprovider.go
index e5177ac3..8db841c5 100644
--- a/ui/iconprovider.go
+++ b/ui/iconprovider.go
@@ -8,6 +8,7 @@ package ui
import (
"github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -87,15 +88,15 @@ func iconForState(state manager.TunnelState, size int) (icon *walk.Icon, err err
func textForState(state manager.TunnelState, withEllipsis bool) (text string) {
switch state {
case manager.TunnelStarted:
- text = "Active"
+ text = l18n.Sprintf("Active")
case manager.TunnelStarting:
- text = "Activating"
+ text = l18n.Sprintf("Activating")
case manager.TunnelStopped:
- text = "Inactive"
+ text = l18n.Sprintf("Inactive")
case manager.TunnelStopping:
- text = "Deactivating"
+ text = l18n.Sprintf("Deactivating")
case manager.TunnelUnknown:
- text = "Unknown state"
+ text = l18n.Sprintf("Unknown state")
}
if withEllipsis {
switch state {
diff --git a/ui/logpage.go b/ui/logpage.go
index b8febcbb..1de5c920 100644
--- a/ui/logpage.go
+++ b/ui/logpage.go
@@ -12,6 +12,7 @@ import (
"time"
"github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/ringlogger"
)
@@ -41,7 +42,7 @@ func NewLogPage() (*LogPage, error) {
lp.model.quit <- true
})
- lp.SetTitle("Log")
+ lp.SetTitle(l18n.Sprintf("Log"))
lp.SetLayout(walk.NewVBoxLayout())
if lp.logView, err = walk.NewTableView(lp); err != nil {
@@ -57,19 +58,19 @@ func NewLogPage() (*LogPage, error) {
}
lp.logView.AddDisposable(contextMenu)
copyAction := walk.NewAction()
- copyAction.SetText("&Copy")
+ copyAction.SetText(l18n.Sprintf("&Copy"))
copyAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyC})
copyAction.Triggered().Attach(lp.onCopy)
contextMenu.Actions().Add(copyAction)
lp.ShortcutActions().Add(copyAction)
selectAllAction := walk.NewAction()
- selectAllAction.SetText("Select &all")
+ selectAllAction.SetText(l18n.Sprintf("Select &all"))
selectAllAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyA})
selectAllAction.Triggered().Attach(lp.onSelectAll)
contextMenu.Actions().Add(selectAllAction)
lp.ShortcutActions().Add(selectAllAction)
saveAction := walk.NewAction()
- saveAction.SetText("&Save to file…")
+ saveAction.SetText(l18n.Sprintf("&Save to file…"))
saveAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyS})
saveAction.Triggered().Attach(lp.onSave)
contextMenu.Actions().Add(saveAction)
@@ -83,14 +84,14 @@ func NewLogPage() (*LogPage, error) {
stampCol := walk.NewTableViewColumn()
stampCol.SetName("Stamp")
- stampCol.SetTitle("Time")
+ stampCol.SetTitle(l18n.Sprintf("Time"))
stampCol.SetFormat("2006-01-02 15:04:05.000")
stampCol.SetWidth(140)
lp.logView.Columns().Add(stampCol)
msgCol := walk.NewTableViewColumn()
msgCol.SetName("Line")
- msgCol.SetTitle("Log message")
+ msgCol.SetTitle(l18n.Sprintf("Log message"))
lp.logView.Columns().Add(msgCol)
lp.model = newLogModel(lp)
@@ -111,7 +112,7 @@ func NewLogPage() (*LogPage, error) {
if err != nil {
return nil, err
}
- saveButton.SetText("&Save")
+ saveButton.SetText(l18n.Sprintf("&Save"))
saveButton.Clicked().Attach(lp.onSave)
disposables.Spare()
@@ -146,9 +147,9 @@ func (lp *LogPage) onSelectAll() {
func (lp *LogPage) onSave() {
fd := walk.FileDialog{
- Filter: "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ Filter: l18n.Sprintf("Text Files (*.txt)|*.txt|All Files (*.*)|*.*"),
FilePath: fmt.Sprintf("wireguard-log-%s.txt", time.Now().Format("2006-01-02T150405")),
- Title: "Export log to file",
+ Title: l18n.Sprintf("Export log to file"),
}
form := lp.Form()
diff --git a/ui/managewindow.go b/ui/managewindow.go
index 542c91b9..e6855d5d 100644
--- a/ui/managewindow.go
+++ b/ui/managewindow.go
@@ -13,6 +13,7 @@ import (
"github.com/lxn/win"
"golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -118,7 +119,7 @@ func NewManageTunnelsWindow() (*ManageTunnelsWindow, error) {
CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
FMask: win.MIIM_ID | win.MIIM_STRING | win.MIIM_FTYPE,
FType: win.MIIM_STRING,
- DwTypeData: windows.StringToUTF16Ptr("&About WireGuard…"),
+ DwTypeData: windows.StringToUTF16Ptr(l18n.Sprintf("&About WireGuard…")),
WID: uint32(aboutWireGuardCmd),
})
win.InsertMenuItem(systemMenu, 1, true, &win.MENUITEMINFO{
@@ -169,7 +170,7 @@ func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state man
if len(errMsg) > 0 && errMsg[len(errMsg)-1] != '.' {
errMsg += "."
}
- showWarningCustom(mtw, "Tunnel Error", errMsg+"\n\nPlease consult the log for more information.")
+ showWarningCustom(mtw, l18n.Sprintf("Tunnel Error"), l18n.Sprintf("%s\n\nPlease consult the log for more information.", errMsg))
}
})
}
@@ -178,7 +179,7 @@ func (mtw *ManageTunnelsWindow) UpdateFound() {
if mtw.updatePage != nil {
return
}
- mtw.SetTitle(mtw.Title() + " (out of date)")
+ mtw.SetTitle(l18n.Sprintf("%s (out of date)", mtw.Title()))
updatePage, err := NewUpdatePage()
if err == nil {
mtw.updatePage = updatePage
diff --git a/ui/raise.go b/ui/raise.go
index 42509994..0eac828a 100644
--- a/ui/raise.go
+++ b/ui/raise.go
@@ -6,12 +6,13 @@
package ui
import (
- "fmt"
"os"
"runtime"
"github.com/lxn/win"
"golang.org/x/sys/windows"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
func raise(hwnd win.HWND) {
@@ -66,7 +67,7 @@ func WaitForRaiseUIThenQuit() {
return 0
}, 0, 0, win.WINEVENT_SKIPOWNPROCESS|win.WINEVENT_OUTOFCONTEXT)
if err != nil {
- showErrorCustom(nil, "WireGuard Detection Error", fmt.Sprintf("Unable to wait for WireGuard window to appear: %v", err))
+ showErrorCustom(nil, l18n.Sprintf("WireGuard Detection Error"), l18n.Sprintf("Unable to wait for WireGuard window to appear: %v", err))
}
for {
var msg win.MSG
diff --git a/ui/tray.go b/ui/tray.go
index 810c759b..f1fb727e 100644
--- a/ui/tray.go
+++ b/ui/tray.go
@@ -6,12 +6,12 @@
package ui
import (
- "fmt"
"sort"
"strings"
"time"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"github.com/lxn/walk"
@@ -53,7 +53,7 @@ func NewTray(mtw *ManageTunnelsWindow) (*Tray, error) {
func (tray *Tray) setup() error {
tray.clicked = tray.onManageTunnels
- tray.SetToolTip("WireGuard: Deactivated")
+ tray.SetToolTip(l18n.Sprintf("WireGuard: Deactivated"))
tray.SetVisible(true)
if icon, err := loadLogoIcon(16); err == nil {
tray.SetIcon(icon)
@@ -76,15 +76,15 @@ func (tray *Tray) setup() error {
separator bool
defawlt bool
}{
- {label: "Status: Unknown"},
- {label: "Addresses: None", hidden: true},
+ {label: l18n.Sprintf("Status: Unknown")},
+ {label: l18n.Sprintf("Addresses: None"), hidden: true},
{separator: true},
{separator: true},
- {label: "&Manage tunnels…", handler: tray.onManageTunnels, enabled: true, defawlt: true},
- {label: "&Import tunnel(s) from file…", handler: tray.onImport, enabled: true},
+ {label: l18n.Sprintf("&Manage tunnels…"), handler: tray.onManageTunnels, enabled: true, defawlt: true},
+ {label: l18n.Sprintf("&Import tunnel(s) from file…"), handler: tray.onImport, enabled: true},
{separator: true},
- {label: "&About WireGuard…", handler: tray.onAbout, enabled: true},
- {label: "E&xit", handler: onQuit, enabled: true},
+ {label: l18n.Sprintf("&About WireGuard…"), handler: tray.onAbout, enabled: true},
+ {label: l18n.Sprintf("E&xit"), handler: onQuit, enabled: true},
} {
var action *walk.Action
if item.separator {
@@ -160,11 +160,11 @@ func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
tray.mtw.tunnelsPage.listView.selectTunnel(tclosure.Name)
tray.mtw.tabs.SetCurrentIndex(0)
if oldState == manager.TunnelUnknown {
- showErrorCustom(tray.mtw, "Failed to determine tunnel state", err.Error())
+ showErrorCustom(tray.mtw, l18n.Sprintf("Failed to determine tunnel state"), err.Error())
} else if oldState == manager.TunnelStopped {
- showErrorCustom(tray.mtw, "Failed to activate tunnel", err.Error())
+ showErrorCustom(tray.mtw, l18n.Sprintf("Failed to activate tunnel"), err.Error())
} else if oldState == manager.TunnelStarted {
- showErrorCustom(tray.mtw, "Failed to deactivate tunnel", err.Error())
+ showErrorCustom(tray.mtw, l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
}
})
}
@@ -213,7 +213,7 @@ func (tray *Tray) onTunnelChange(tunnel *manager.Tunnel, state manager.TunnelSta
tray.updateGlobalState(globalState)
tray.SetTunnelState(tunnel, state, err == nil)
if !tray.mtw.Visible() && err != nil {
- tray.ShowError("WireGuard Tunnel Error", err.Error())
+ tray.ShowError(l18n.Sprintf("WireGuard Tunnel Error"), err.Error())
}
})
}
@@ -234,8 +234,9 @@ func (tray *Tray) updateGlobalState(globalState manager.TunnelState) {
}
}
- tray.SetToolTip(fmt.Sprintf("WireGuard: %s", textForState(globalState, true)))
- statusAction.SetText(fmt.Sprintf("Status: %s", textForState(globalState, false)))
+ tray.SetToolTip(l18n.Sprintf("WireGuard: %s", textForState(globalState, true)))
+ stateText := textForState(globalState, false)
+ statusAction.SetText(l18n.Sprintf("Status: %s", stateText))
switch globalState {
case manager.TunnelStarting:
@@ -274,13 +275,13 @@ func (tray *Tray) SetTunnelState(tunnel *manager.Tunnel, state manager.TunnelSta
var sb strings.Builder
for i, addr := range config.Interface.Addresses {
if i > 0 {
- sb.WriteString(", ")
+ sb.WriteString(l18n.EnumerationSeparator())
}
sb.WriteString(addr.String())
}
tray.mtw.Synchronize(func() {
- activeCIDRsAction.SetText(fmt.Sprintf("Addresses: %s", sb.String()))
+ activeCIDRsAction.SetText(l18n.Sprintf("Addresses: %s", sb.String()))
})
}
}()
@@ -288,21 +289,21 @@ func (tray *Tray) SetTunnelState(tunnel *manager.Tunnel, state manager.TunnelSta
tunnelAction.SetChecked(true)
if !wasChecked && showNotifications {
icon, _ := iconWithOverlayForState(state, 128)
- tray.ShowCustom("WireGuard Activated", fmt.Sprintf("The %s tunnel has been activated.", tunnel.Name), icon)
+ tray.ShowCustom(l18n.Sprintf("WireGuard Activated"), l18n.Sprintf("The %s tunnel has been activated.", tunnel.Name), icon)
}
case manager.TunnelStopped:
tunnelAction.SetChecked(false)
if wasChecked && showNotifications {
icon, _ := loadSystemIcon("imageres", 26, 128) // TODO: this icon isn't very good...
- tray.ShowCustom("WireGuard Deactivated", fmt.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon)
+ tray.ShowCustom(l18n.Sprintf("WireGuard Deactivated"), l18n.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon)
}
}
}
func (tray *Tray) UpdateFound() {
action := walk.NewAction()
- action.SetText("An Update is Available!")
+ action.SetText(l18n.Sprintf("An Update is Available!"))
menuIcon, _ := loadSystemIcon("imageres", 1, 16)
action.SetImage(menuIcon)
action.SetDefault(true)
@@ -319,7 +320,7 @@ func (tray *Tray) UpdateFound() {
showUpdateBalloon := func() {
icon, _ := loadSystemIcon("imageres", 1, 128)
- tray.ShowCustom("WireGuard Update Available", "An update to WireGuard is now available. You are advised to update as soon as possible.", icon)
+ tray.ShowCustom(l18n.Sprintf("WireGuard Update Available"), l18n.Sprintf("An update to WireGuard is now available. You are advised to update as soon as possible."), icon)
}
timeSinceStart := time.Now().Sub(startTime)
diff --git a/ui/tunnelspage.go b/ui/tunnelspage.go
index aed8d157..d5933143 100644
--- a/ui/tunnelspage.go
+++ b/ui/tunnelspage.go
@@ -7,6 +7,7 @@ package ui
import (
"archive/zip"
+ "errors"
"fmt"
"io/ioutil"
"os"
@@ -17,6 +18,7 @@ import (
"github.com/lxn/walk"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -45,7 +47,7 @@ func NewTunnelsPage() (*TunnelsPage, error) {
}
disposables.Add(tp)
- tp.SetTitle("Tunnels")
+ tp.SetTitle(l18n.Sprintf("Tunnels"))
tp.SetLayout(walk.NewHBoxLayout())
tp.listContainer, _ = walk.NewComposite(tp)
@@ -101,7 +103,7 @@ func NewTunnelsPage() (*TunnelsPage, error) {
tp.listView.CurrentIndexChanged().Attach(func() {
editTunnel.SetEnabled(tp.listView.CurrentIndex() > -1)
})
- editTunnel.SetText("&Edit")
+ editTunnel.SetText(l18n.Sprintf("&Edit"))
editTunnel.Clicked().Attach(tp.onEditTunnel)
disposables.Spare()
@@ -142,7 +144,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
}
tp.AddDisposable(addMenu)
importAction := walk.NewAction()
- importAction.SetText("&Import tunnel(s) from file…")
+ importAction.SetText(l18n.Sprintf("&Import tunnel(s) from file…"))
importActionIcon, _ := loadSystemIcon("imageres", 3, 16)
importAction.SetImage(importActionIcon)
importAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyO})
@@ -150,7 +152,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
importAction.Triggered().Attach(tp.onImport)
addMenu.Actions().Add(importAction)
addAction := walk.NewAction()
- addAction.SetText("Add &empty tunnel…")
+ addAction.SetText(l18n.Sprintf("Add &empty tunnel…"))
addActionIcon, _ := loadSystemIcon("imageres", 2, 16)
addAction.SetImage(addActionIcon)
addAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyN})
@@ -159,7 +161,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
addMenuAction := walk.NewMenuAction(addMenu)
addMenuActionIcon, _ := loadSystemIcon("shell32", 149, 16)
addMenuAction.SetImage(addMenuActionIcon)
- addMenuAction.SetText("Add Tunnel")
+ addMenuAction.SetText(l18n.Sprintf("Add Tunnel"))
addMenuAction.SetToolTip(importAction.Text())
addMenuAction.Triggered().Attach(tp.onImport)
tp.listToolbar.Actions().Add(addMenuAction)
@@ -170,7 +172,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
deleteActionIcon, _ := loadSystemIcon("shell32", 131, 16)
deleteAction.SetImage(deleteActionIcon)
deleteAction.SetShortcut(walk.Shortcut{0, walk.KeyDelete})
- deleteAction.SetToolTip("Remove selected tunnel(s)")
+ deleteAction.SetToolTip(l18n.Sprintf("Remove selected tunnel(s)"))
deleteAction.Triggered().Attach(tp.onDelete)
tp.listToolbar.Actions().Add(deleteAction)
tp.listToolbar.Actions().Add(walk.NewSeparatorAction())
@@ -178,7 +180,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
exportAction := walk.NewAction()
exportActionIcon, _ := loadSystemIcon("imageres", 165, 16) // Or "shell32", 45?
exportAction.SetImage(exportActionIcon)
- exportAction.SetToolTip("Export all tunnels to zip…")
+ exportAction.SetToolTip(l18n.Sprintf("Export all tunnels to zip"))
exportAction.Triggered().Attach(tp.onExportTunnels)
tp.listToolbar.Actions().Add(exportAction)
@@ -195,42 +197,42 @@ func (tp *TunnelsPage) CreateToolbar() error {
}
tp.listView.AddDisposable(contextMenu)
toggleAction := walk.NewAction()
- toggleAction.SetText("&Toggle")
+ toggleAction.SetText(l18n.Sprintf("&Toggle"))
toggleAction.SetDefault(true)
toggleAction.Triggered().Attach(tp.onTunnelsViewItemActivated)
contextMenu.Actions().Add(toggleAction)
contextMenu.Actions().Add(walk.NewSeparatorAction())
importAction2 := walk.NewAction()
- importAction2.SetText("&Import tunnel(s) from file…")
+ importAction2.SetText(l18n.Sprintf("&Import tunnel(s) from file…"))
importAction2.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyO})
importAction2.Triggered().Attach(tp.onImport)
contextMenu.Actions().Add(importAction2)
tp.ShortcutActions().Add(importAction2)
addAction2 := walk.NewAction()
- addAction2.SetText("Add &empty tunnel…")
+ addAction2.SetText(l18n.Sprintf("Add &empty tunnel…"))
addAction2.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyN})
addAction2.Triggered().Attach(tp.onAddTunnel)
contextMenu.Actions().Add(addAction2)
tp.ShortcutActions().Add(addAction2)
exportAction2 := walk.NewAction()
- exportAction2.SetText("Export all tunnels to &zip…")
+ exportAction2.SetText(l18n.Sprintf("Export all tunnels to &zip…"))
exportAction2.Triggered().Attach(tp.onExportTunnels)
contextMenu.Actions().Add(exportAction2)
contextMenu.Actions().Add(walk.NewSeparatorAction())
editAction := walk.NewAction()
- editAction.SetText("Edit &selected tunnel…")
+ editAction.SetText(l18n.Sprintf("Edit &selected tunnel…"))
editAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyE})
editAction.Triggered().Attach(tp.onEditTunnel)
contextMenu.Actions().Add(editAction)
tp.ShortcutActions().Add(editAction)
deleteAction2 := walk.NewAction()
- deleteAction2.SetText("&Remove selected tunnel(s)")
+ deleteAction2.SetText(l18n.Sprintf("&Remove selected tunnel(s)"))
deleteAction2.SetShortcut(walk.Shortcut{0, walk.KeyDelete})
deleteAction2.Triggered().Attach(tp.onDelete)
contextMenu.Actions().Add(deleteAction2)
tp.listView.ShortcutActions().Add(deleteAction2)
selectAllAction := walk.NewAction()
- selectAllAction.SetText("Select &all")
+ selectAllAction.SetText(l18n.Sprintf("Select &all"))
selectAllAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyA})
selectAllAction.Triggered().Attach(tp.onSelectAll)
contextMenu.Actions().Add(selectAllAction)
@@ -324,7 +326,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
}
if lastErr != nil || unparsedConfigs == nil {
- syncedMsgBox("Error", fmt.Sprintf("Could not import selected configuration: %v", lastErr), walk.MsgBoxIconWarning)
+ syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Could not import selected configuration: %v", lastErr), walk.MsgBoxIconWarning)
return
}
@@ -335,7 +337,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
existingTunnelList, err := manager.IPCClientTunnels()
if err != nil {
- syncedMsgBox("Error", fmt.Sprintf("Could not enumerate existing tunnels: %v", lastErr), walk.MsgBoxIconWarning)
+ syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Could not enumerate existing tunnels: %v", lastErr), walk.MsgBoxIconWarning)
return
}
existingLowerTunnels := make(map[string]bool, len(existingTunnelList))
@@ -347,7 +349,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
tp.listView.SetSuspendTunnelsUpdate(true)
for _, unparsedConfig := range unparsedConfigs {
if existingLowerTunnels[strings.ToLower(unparsedConfig.Name)] {
- lastErr = fmt.Errorf("Another tunnel already exists with the name ‘%s’", unparsedConfig.Name)
+ lastErr = errors.New(l18n.Sprintf("Another tunnel already exists with the name ‘%s’", unparsedConfig.Name))
continue
}
config, err := conf.FromWgQuickWithUnknownEncoding(unparsedConfig.Config, unparsedConfig.Name)
@@ -367,13 +369,13 @@ func (tp *TunnelsPage) importFiles(paths []string) {
m, n := configCount, len(unparsedConfigs)
switch {
case n == 1 && m != n:
- syncedMsgBox("Error", fmt.Sprintf("Unable to import configuration: %v", lastErr), walk.MsgBoxIconWarning)
+ syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Unable to import configuration: %v", lastErr), walk.MsgBoxIconWarning)
case n == 1 && m == n:
// nothing
case m == n:
- syncedMsgBox("Imported tunnels", fmt.Sprintf("Imported %d tunnels", m), walk.MsgBoxIconInformation)
+ syncedMsgBox(l18n.Sprintf("Imported tunnels"), l18n.Sprintf("Imported %d tunnels", m), walk.MsgBoxIconInformation)
case m != n:
- syncedMsgBox("Imported tunnels", fmt.Sprintf("Imported %d of %d tunnels", m, n), walk.MsgBoxIconWarning)
+ syncedMsgBox(l18n.Sprintf("Imported tunnels"), l18n.Sprintf("Imported %d of %d tunnels", m, n), walk.MsgBoxIconWarning)
}
}()
}
@@ -405,7 +407,7 @@ func (tp *TunnelsPage) exportTunnels(filePath string) {
func (tp *TunnelsPage) addTunnel(config *conf.Config) {
_, err := manager.IPCClientNewTunnel(config)
if err != nil {
- showErrorCustom(tp.Form(), "Unable to create tunnel", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Unable to create tunnel"), err.Error())
}
}
@@ -422,11 +424,11 @@ func (tp *TunnelsPage) onTunnelsViewItemActivated() {
if err != nil {
tp.Synchronize(func() {
if oldState == manager.TunnelUnknown {
- showErrorCustom(tp.Form(), "Failed to determine tunnel state", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Failed to determine tunnel state"), err.Error())
} else if oldState == manager.TunnelStopped {
- showErrorCustom(tp.Form(), "Failed to activate tunnel", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Failed to activate tunnel"), err.Error())
} else if oldState == manager.TunnelStarted {
- showErrorCustom(tp.Form(), "Failed to deactivate tunnel", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
}
})
return
@@ -466,16 +468,20 @@ func (tp *TunnelsPage) onDelete() {
return
}
- var topic string
+ var title, question string
if len(indices) > 1 {
- topic = fmt.Sprintf("%d tunnels", len(indices))
+ tunnelCount := len(indices)
+ title = l18n.Sprintf("Delete %d tunnels", tunnelCount)
+ question = l18n.Sprintf("Are you sure you would like to delete %d tunnels?", tunnelCount)
} else {
- topic = fmt.Sprintf("‘%s’", tp.listView.model.tunnels[indices[0]].Name)
+ tunnelName := tp.listView.model.tunnels[indices[0]].Name
+ title = l18n.Sprintf("Delete tunnel ‘%s’", tunnelName)
+ question = l18n.Sprintf("Are you sure you would like to delete tunnel ‘%s’?", tunnelName)
}
if walk.DlgCmdNo == walk.MsgBox(
tp.Form(),
- fmt.Sprintf("Delete %s", topic),
- fmt.Sprintf("Are you sure you would like to delete %s? You cannot undo this action.", topic),
+ title,
+ l18n.Sprintf("%s You cannot undo this action.", question),
walk.MsgBoxYesNo|walk.MsgBoxIconWarning) {
return
}
@@ -515,9 +521,9 @@ func (tp *TunnelsPage) onDelete() {
if len(errors) > 0 {
tp.listView.Synchronize(func() {
if len(errors) == 1 {
- showErrorCustom(tp.Form(), "Unable to delete tunnel", fmt.Sprintf("A tunnel was unable to be removed: %s", errors[0].Error()))
+ showErrorCustom(tp.Form(), l18n.Sprintf("Unable to delete tunnel"), l18n.Sprintf("A tunnel was unable to be removed: %s", errors[0].Error()))
} else {
- showErrorCustom(tp.Form(), "Unable to delete tunnels", fmt.Sprintf("%d tunnels were unable to be removed.", len(errors)))
+ showErrorCustom(tp.Form(), l18n.Sprintf("Unable to delete tunnels"), l18n.Sprintf("%d tunnels were unable to be removed.", len(errors)))
}
})
}
@@ -530,8 +536,8 @@ func (tp *TunnelsPage) onSelectAll() {
func (tp *TunnelsPage) onImport() {
dlg := walk.FileDialog{
- Filter: "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
- Title: "Import tunnel(s) from file",
+ Filter: l18n.Sprintf("Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*"),
+ Title: l18n.Sprintf("Import tunnel(s) from file"),
}
if ok, _ := dlg.ShowOpenMultiple(tp.Form()); !ok {
@@ -543,8 +549,8 @@ func (tp *TunnelsPage) onImport() {
func (tp *TunnelsPage) onExportTunnels() {
dlg := walk.FileDialog{
- Filter: "Configuration ZIP Files (*.zip)|*.zip",
- Title: "Export tunnels to zip",
+ Filter: l18n.Sprintf("Configuration ZIP Files (*.zip)|*.zip"),
+ Title: l18n.Sprintf("Export tunnels to zip"),
}
if ok, _ := dlg.ShowSave(tp.Form()); !ok {
@@ -571,7 +577,7 @@ func (tp *TunnelsPage) swapFiller(enabled bool) bool {
func (tp *TunnelsPage) onTunnelsChanged() {
if tp.swapFiller(tp.listView.model.RowCount() == 0) {
- tp.fillerButton.SetText("Import tunnel(s) from file")
+ tp.fillerButton.SetText(l18n.Sprintf("Import tunnel(s) from file"))
tp.fillerHandler = tp.onImport
}
}
@@ -581,8 +587,9 @@ func (tp *TunnelsPage) onSelectedTunnelsChanged() {
return
}
indices := tp.listView.SelectedIndexes()
- if tp.swapFiller(len(indices) > 1) {
- tp.fillerButton.SetText(fmt.Sprintf("Delete %d tunnels", len(indices)))
+ tunnelCount := len(indices)
+ if tp.swapFiller(tunnelCount > 1) {
+ tp.fillerButton.SetText(l18n.Sprintf("Delete %d tunnels", tunnelCount))
tp.fillerHandler = tp.onDelete
}
}
diff --git a/ui/ui.go b/ui/ui.go
index 22354d12..0279a8dd 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -15,6 +15,7 @@ import (
"github.com/lxn/win"
"golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/version"
)
@@ -75,7 +76,7 @@ func RunUI() {
tray.UpdateFound()
}
case manager.UpdateStateUpdatesDisabledUnofficialBuild:
- mtw.SetTitle(mtw.Title() + " (unsigned build, no updates)")
+ mtw.SetTitle(l18n.Sprintf("%s (unsigned build, no updates)", mtw.Title()))
}
})
}
@@ -100,7 +101,7 @@ func RunUI() {
if shouldQuitManagerWhenExiting {
_, err := manager.IPCClientQuit(true)
if err != nil {
- showErrorCustom(nil, "Error Exiting WireGuard", fmt.Sprintf("Unable to exit service due to: %v. You may want to stop WireGuard from the service manager.", err))
+ showErrorCustom(nil, l18n.Sprintf("Error Exiting WireGuard"), l18n.Sprintf("Unable to exit service due to: %v. You may want to stop WireGuard from the service manager.", err))
}
}
}
@@ -115,7 +116,7 @@ func showError(err error, owner walk.Form) bool {
return false
}
- showErrorCustom(owner, "Error", err.Error())
+ showErrorCustom(owner, l18n.Sprintf("Error"), err.Error())
return true
}
diff --git a/ui/updatepage.go b/ui/updatepage.go
index 9e73781f..1ed0b74c 100644
--- a/ui/updatepage.go
+++ b/ui/updatepage.go
@@ -6,10 +6,9 @@
package ui
import (
- "fmt"
-
"github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/updater"
)
@@ -30,7 +29,7 @@ func NewUpdatePage() (*UpdatePage, error) {
}
disposables.Add(up)
- up.SetTitle("An Update is Available!")
+ up.SetTitle(l18n.Sprintf("An Update is Available!"))
tabIcon, _ := loadSystemIcon("imageres", 1, 16)
up.SetImage(tabIcon)
@@ -41,14 +40,14 @@ func NewUpdatePage() (*UpdatePage, error) {
if err != nil {
return nil, err
}
- instructions.SetText("An update to WireGuard is available. It is highly advisable to update without delay.")
+ instructions.SetText(l18n.Sprintf("An update to WireGuard is available. It is highly advisable to update without delay."))
instructions.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0})
status, err := walk.NewTextLabel(up)
if err != nil {
return nil, err
}
- status.SetText("Status: Waiting for user")
+ status.SetText(l18n.Sprintf("Status: Waiting for user"))
status.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0})
bar, err := walk.NewProgressBar(up)
@@ -63,7 +62,7 @@ func NewUpdatePage() (*UpdatePage, error) {
}
updateIcon, _ := loadSystemIcon("shell32", 46, 32)
button.SetImage(updateIcon)
- button.SetText("Update Now")
+ button.SetText(l18n.Sprintf("Update Now"))
walk.NewVSpacer(up)
@@ -75,7 +74,7 @@ func NewUpdatePage() (*UpdatePage, error) {
bar.SetVisible(true)
bar.SetMarqueeMode(true)
up.SetSuspended(false)
- status.SetText("Status: Waiting for updater service")
+ status.SetText(l18n.Sprintf("Status: Waiting for updater service"))
}
}
@@ -97,7 +96,7 @@ func NewUpdatePage() (*UpdatePage, error) {
err := manager.IPCClientUpdate()
if err != nil {
switchToReadyState()
- status.SetText(fmt.Sprintf("Error: %v. Please try again.", err))
+ status.SetText(l18n.Sprintf("Error: %v. Please try again.", err))
}
})
@@ -106,11 +105,13 @@ func NewUpdatePage() (*UpdatePage, error) {
switchToUpdatingState()
if dp.Error != nil {
switchToReadyState()
- status.SetText(fmt.Sprintf("Error: %v. Please try again.", dp.Error))
+ err := dp.Error
+ status.SetText(l18n.Sprintf("Error: %v. Please try again.", err))
return
}
if len(dp.Activity) > 0 {
- status.SetText(fmt.Sprintf("Status: %s", dp.Activity))
+ stateText := dp.Activity
+ status.SetText(l18n.Sprintf("Status: %s", stateText))
}
if dp.BytesTotal > 0 {
bar.SetMarqueeMode(false)
@@ -123,7 +124,7 @@ func NewUpdatePage() (*UpdatePage, error) {
}
if dp.Complete {
switchToReadyState()
- status.SetText("Status: Complete!")
+ status.SetText(l18n.Sprintf("Status: Complete!"))
return
}
})
diff --git a/zgotext.go b/zgotext.go
new file mode 100644
index 00000000..4e96cb3f
--- /dev/null
+++ b/zgotext.go
@@ -0,0 +1,357 @@
+// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
+
+package main
+
+import (
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
+ "golang.org/x/text/message/catalog"
+)
+
+type dictionary struct {
+ index []uint32
+ data string
+}
+
+func (d *dictionary) Lookup(key string) (data string, ok bool) {
+ p, ok := messageKeyToIndex[key]
+ if !ok {
+ return "", false
+ }
+ start, end := d.index[p], d.index[p+1]
+ if start == end {
+ return "", false
+ }
+ return d.data[start:end], true
+}
+
+func init() {
+ dict := map[string]catalog.Dictionary{
+ "en": &dictionary{index: enIndex, data: enData},
+ }
+ fallback := language.MustParse("en")
+ cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
+ if err != nil {
+ panic(err)
+ }
+ message.DefaultCatalog = cat
+}
+
+var messageKeyToIndex = map[string]int{
+ "%.2f\u00a0GiB": 21,
+ "%.2f\u00a0KiB": 19,
+ "%.2f\u00a0MiB": 20,
+ "%.2f\u00a0TiB": 22,
+ "%d day(s)": 13,
+ "%d hour(s)": 14,
+ "%d minute(s)": 15,
+ "%d second(s)": 16,
+ "%d tunnels were unable to be removed.": 157,
+ "%d year(s)": 12,
+ "%d\u00a0B": 18,
+ "%s\n\nPlease consult the log for more information.": 110,
+ "%s (out of date)": 111,
+ "%s (unsigned build, no updates)": 162,
+ "%s You cannot undo this action.": 153,
+ "%s ago": 17,
+ "%s received, %s sent": 70,
+ "%s: %q": 23,
+ "&About WireGuard…": 108,
+ "&Activate": 57,
+ "&Block untunneled traffic (kill-switch)": 81,
+ "&Configuration:": 85,
+ "&Copy": 101,
+ "&Deactivate": 56,
+ "&Edit": 132,
+ "&Import tunnel(s) from file…": 118,
+ "&Manage tunnels…": 117,
+ "&Name:": 78,
+ "&Public key:": 79,
+ "&Remove selected tunnel(s)": 140,
+ "&Save": 83,
+ "&Save to file…": 103,
+ "&Toggle": 137,
+ "(no argument): elevate and install manager service": 1,
+ "(unknown)": 80,
+ "A name is required.": 87,
+ "A tunnel was unable to be removed: %s": 155,
+ "About WireGuard": 50,
+ "Activating": 96,
+ "Active": 95,
+ "Add &empty tunnel…": 133,
+ "Add Tunnel": 134,
+ "Addresses:": 61,
+ "Addresses: %s": 123,
+ "Addresses: None": 116,
+ "All peers must have public keys": 44,
+ "Allowed IPs:": 64,
+ "An Update is Available!": 128,
+ "An interface must have a private key": 42,
+ "An update to WireGuard is available. It is highly advisable to update without delay.": 165,
+ "An update to WireGuard is now available. You are advised to update as soon as possible.": 130,
+ "Another tunnel already exists with the name ‘%s’": 143,
+ "Another tunnel already exists with the name ‘%s’.": 91,
+ "App version: %s\nGo backend version: %s\nGo version: %s\nOperating system: %s\nArchitecture: %s": 52,
+ "Are you sure you would like to delete %d tunnels?": 150,
+ "Are you sure you would like to delete tunnel ‘%s’?": 152,
+ "Brackets must contain an IPv6 address": 28,
+ "Cancel": 84,
+ "Close": 53,
+ "Command Line Options": 3,
+ "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*": 158,
+ "Configuration ZIP Files (*.zip)|*.zip": 160,
+ "Could not enumerate existing tunnels: %v": 142,
+ "Could not import selected configuration: %v": 141,
+ "Create new tunnel": 76,
+ "DNS servers:": 62,
+ "Deactivating": 98,
+ "Delete %d tunnels": 149,
+ "Delete tunnel ‘%s’": 151,
+ "E&xit": 119,
+ "Edit &selected tunnel…": 139,
+ "Edit tunnel": 77,
+ "Endpoint:": 65,
+ "Error": 0,
+ "Error Exiting WireGuard": 163,
+ "Error in getting configuration": 45,
+ "Error: %v. Please try again.": 169,
+ "Export all tunnels to &zip…": 138,
+ "Export all tunnels to zip": 136,
+ "Export log to file": 107,
+ "Export tunnels to zip": 161,
+ "Failed to activate tunnel": 72,
+ "Failed to deactivate tunnel": 73,
+ "Failed to determine tunnel state": 71,
+ "File ‘%s’ already exists.\n\nDo you want to overwrite it?": 94,
+ "Import tunnel(s) from file": 159,
+ "Imported %d of %d tunnels": 147,
+ "Imported %d tunnels": 146,
+ "Imported tunnels": 145,
+ "Inactive": 97,
+ "Interface: %s": 74,
+ "Invalid IP address": 24,
+ "Invalid MTU": 29,
+ "Invalid config key is missing an equals separator": 38,
+ "Invalid endpoint host": 27,
+ "Invalid key for [Interface] section": 40,
+ "Invalid key for [Peer] section": 41,
+ "Invalid key for interface section": 46,
+ "Invalid key for peer section": 48,
+ "Invalid key: %v": 32,
+ "Invalid name": 86,
+ "Invalid network prefix length": 25,
+ "Invalid persistent keepalive": 31,
+ "Invalid port": 30,
+ "Key must have a value": 39,
+ "Keys must decode to exactly 32 bytes": 33,
+ "Latest handshake:": 67,
+ "Line must occur in a section": 37,
+ "Listen port:": 59,
+ "Log": 100,
+ "Log message": 105,
+ "MTU:": 60,
+ "Missing port from endpoint": 26,
+ "Now": 10,
+ "Number must be a number between 0 and 2^64-1: %v": 34,
+ "Peer": 75,
+ "Persistent keepalive:": 66,
+ "Preshared key:": 63,
+ "Protocol version must be 1": 47,
+ "Public key:": 58,
+ "Remove selected tunnel(s)": 135,
+ "Select &all": 102,
+ "Status:": 55,
+ "Status: %s": 122,
+ "Status: Complete!": 170,
+ "Status: Unknown": 115,
+ "Status: Waiting for updater service": 168,
+ "Status: Waiting for user": 166,
+ "System clock wound backward!": 11,
+ "Text Files (*.txt)|*.txt|All Files (*.*)|*.*": 106,
+ "The %s tunnel has been activated.": 125,
+ "The %s tunnel has been deactivated.": 127,
+ "Time": 104,
+ "Transfer:": 68,
+ "Tunnel Error": 109,
+ "Tunnel already exists": 90,
+ "Tunnel name is not valid": 36,
+ "Tunnel name ‘%s’ is invalid.": 88,
+ "Tunnels": 131,
+ "Two commas in a row": 35,
+ "Unable to create new configuration": 92,
+ "Unable to create tunnel": 148,
+ "Unable to delete tunnel": 154,
+ "Unable to delete tunnels": 156,
+ "Unable to determine whether the process is running under WOW64: %v": 4,
+ "Unable to exit service due to: %v. You may want to stop WireGuard from the service manager.": 164,
+ "Unable to import configuration: %v": 144,
+ "Unable to list existing tunnels": 89,
+ "Unable to open current process token: %v": 6,
+ "Unable to wait for WireGuard window to appear: %v": 113,
+ "Unknown state": 99,
+ "Update Now": 167,
+ "Usage: %s [\n%s]": 2,
+ "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.": 82,
+ "WireGuard Activated": 124,
+ "WireGuard Deactivated": 126,
+ "WireGuard Detection Error": 112,
+ "WireGuard Tunnel Error": 120,
+ "WireGuard Update Available": 129,
+ "WireGuard is running, but the UI is only accessible from desktops of the Builtin %s group.": 8,
+ "WireGuard logo image": 51,
+ "WireGuard may only be used by users who are a member of the Builtin %s group.": 7,
+ "WireGuard system tray icon did not appear after 30 seconds.": 9,
+ "WireGuard: %s": 121,
+ "WireGuard: Deactivated": 114,
+ "Writing file failed": 93,
+ "You must use the 64-bit version of WireGuard on this computer.": 5,
+ "[EnumerationSeparator]": 49,
+ "[none specified]": 43,
+ "enabled": 69,
+ "http2: Framer %p: failed to decode just-written frame": 171,
+ "http2: Framer %p: read %v": 173,
+ "http2: Framer %p: wrote %v": 172,
+ "http2: decoded hpack field %+v": 174,
+ "♥ &Donate!": 54,
+}
+
+var enIndex = []uint32{ // 176 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x00000039, 0x0000004f,
+ 0x00000064, 0x000000aa, 0x000000e9, 0x00000115,
+ 0x00000166, 0x000001c4, 0x00000200, 0x00000204,
+ 0x00000221, 0x00000241, 0x0000025f, 0x0000027f,
+ 0x000002a3, 0x000002c7, 0x000002d1, 0x000002da,
+ 0x000002e7, 0x000002f4, 0x00000301, 0x0000030e,
+ 0x0000031b, 0x0000032e, 0x0000034c, 0x00000367,
+ 0x0000037d, 0x000003a3, 0x000003af, 0x000003bc,
+ // Entry 20 - 3F
+ 0x000003d9, 0x000003ec, 0x00000411, 0x00000445,
+ 0x00000459, 0x00000472, 0x0000048f, 0x000004c1,
+ 0x000004d7, 0x000004fb, 0x0000051a, 0x0000053f,
+ 0x00000550, 0x00000570, 0x0000058f, 0x000005b1,
+ 0x000005cc, 0x000005e9, 0x000005ec, 0x000005fc,
+ 0x00000611, 0x0000067c, 0x00000682, 0x0000068f,
+ 0x00000697, 0x000006a3, 0x000006ad, 0x000006b9,
+ 0x000006c6, 0x000006cb, 0x000006d6, 0x000006e3,
+ // Entry 40 - 5F
+ 0x000006f2, 0x000006ff, 0x00000709, 0x0000071f,
+ 0x00000731, 0x0000073b, 0x00000743, 0x0000075e,
+ 0x0000077f, 0x00000799, 0x000007b5, 0x000007c6,
+ 0x000007cb, 0x000007dd, 0x000007e9, 0x000007f0,
+ 0x000007fd, 0x00000807, 0x0000082f, 0x0000094d,
+ 0x00000953, 0x0000095a, 0x0000096a, 0x00000977,
+ 0x0000098b, 0x000009af, 0x000009cf, 0x000009e5,
+ 0x00000a1e, 0x00000a41, 0x00000a55, 0x00000a94,
+ // Entry 60 - 7F
+ 0x00000a9b, 0x00000aa6, 0x00000aaf, 0x00000abc,
+ 0x00000aca, 0x00000ace, 0x00000ad4, 0x00000ae0,
+ 0x00000af1, 0x00000af6, 0x00000b02, 0x00000b2f,
+ 0x00000b42, 0x00000b56, 0x00000b63, 0x00000b97,
+ 0x00000bab, 0x00000bc5, 0x00000bfa, 0x00000c11,
+ 0x00000c21, 0x00000c31, 0x00000c44, 0x00000c63,
+ 0x00000c69, 0x00000c80, 0x00000c91, 0x00000c9f,
+ 0x00000cb0, 0x00000cc4, 0x00000ce9, 0x00000cff,
+ // Entry 80 - 9F
+ 0x00000d26, 0x00000d3e, 0x00000d59, 0x00000db1,
+ 0x00000db9, 0x00000dbf, 0x00000dd4, 0x00000ddf,
+ 0x00000df9, 0x00000e13, 0x00000e1b, 0x00000e39,
+ 0x00000e52, 0x00000e6d, 0x00000e9c, 0x00000ec8,
+ 0x00000f00, 0x00000f26, 0x00000f37, 0x00000f6d,
+ 0x00000fb4, 0x00000fcc, 0x00000ffe, 0x00001070,
+ 0x0000108a, 0x000010c4, 0x000010e7, 0x000010ff,
+ 0x00001128, 0x00001141, 0x0000119a, 0x000011df,
+ // Entry A0 - BF
+ 0x000011fa, 0x00001220, 0x00001236, 0x00001259,
+ 0x00001271, 0x000012d0, 0x00001325, 0x0000133e,
+ 0x00001349, 0x0000136d, 0x0000138d, 0x0000139f,
+ 0x000013d8, 0x000013f9, 0x00001419, 0x0000143b,
+} // Size: 716 bytes
+
+const enData string = "" + // Size: 5179 bytes
+ "\x02Error\x02(no argument): elevate and install manager service\x02Usage" +
+ ": %[1]s [\x0a%[2]s]\x02Command Line Options\x02Unable to determine wheth" +
+ "er the process is running under WOW64: %[1]v\x02You must use the 64-bit " +
+ "version of WireGuard on this computer.\x02Unable to open current process" +
+ " token: %[1]v\x02WireGuard may only be used by users who are a member of" +
+ " the Builtin %[1]s group.\x02WireGuard is running, but the UI is only ac" +
+ "cessible from desktops of the Builtin %[1]s group.\x02WireGuard system t" +
+ "ray icon did not appear after 30 seconds.\x02Now\x02System clock wound b" +
+ "ackward!\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d year\x00\x0c\x02%[1]d year" +
+ "s\x14\x01\x81\x01\x00\x02\x0a\x02%[1]d day\x00\x0b\x02%[1]d days\x14\x01" +
+ "\x81\x01\x00\x02\x0b\x02%[1]d hour\x00\x0c\x02%[1]d hours\x14\x01\x81" +
+ "\x01\x00\x02\x0d\x02%[1]d minute\x00\x0e\x02%[1]d minutes\x14\x01\x81" +
+ "\x01\x00\x02\x0d\x02%[1]d second\x00\x0e\x02%[1]d seconds\x02%[1]s ago" +
+ "\x02%[1]d\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f" +
+ "\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Invalid IP address\x02I" +
+ "nvalid network prefix length\x02Missing port from endpoint\x02Invalid en" +
+ "dpoint host\x02Brackets must contain an IPv6 address\x02Invalid MTU\x02I" +
+ "nvalid port\x02Invalid persistent keepalive\x02Invalid key: %[1]v\x02Key" +
+ "s must decode to exactly 32 bytes\x02Number must be a number between 0 a" +
+ "nd 2^64-1: %[1]v\x02Two commas in a row\x02Tunnel name is not valid\x02L" +
+ "ine must occur in a section\x02Invalid config key is missing an equals s" +
+ "eparator\x02Key must have a value\x02Invalid key for [Interface] section" +
+ "\x02Invalid key for [Peer] section\x02An interface must have a private k" +
+ "ey\x02[none specified]\x02All peers must have public keys\x02Error in ge" +
+ "tting configuration\x02Invalid key for interface section\x02Protocol ver" +
+ "sion must be 1\x02Invalid key for peer section\x02, \x02About WireGuard" +
+ "\x02WireGuard logo image\x02App version: %[1]s\x0aGo backend version: %[" +
+ "2]s\x0aGo version: %[3]s\x0aOperating system: %[4]s\x0aArchitecture: %[5" +
+ "]s\x02Close\x02♥ &Donate!\x02Status:\x02&Deactivate\x02&Activate\x02Publ" +
+ "ic key:\x02Listen port:\x02MTU:\x02Addresses:\x02DNS servers:\x02Preshar" +
+ "ed key:\x02Allowed IPs:\x02Endpoint:\x02Persistent keepalive:\x02Latest " +
+ "handshake:\x02Transfer:\x02enabled\x02%[1]s received, %[2]s sent\x02Fail" +
+ "ed to determine tunnel state\x02Failed to activate tunnel\x02Failed to d" +
+ "eactivate tunnel\x02Interface: %[1]s\x02Peer\x02Create new tunnel\x02Edi" +
+ "t tunnel\x02&Name:\x02&Public key:\x02(unknown)\x02&Block untunneled tra" +
+ "ffic (kill-switch)\x02When a configuration has exactly one peer, and tha" +
+ "t peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, " +
+ "then the tunnel service engages a firewall ruleset to block all traffic " +
+ "that is neither to nor from the tunnel interface, with special exception" +
+ "s for DHCP and NDP.\x02&Save\x02Cancel\x02&Configuration:\x02Invalid nam" +
+ "e\x02A name is required.\x02Tunnel name ‘%[1]s’ is invalid.\x02Unable to" +
+ " list existing tunnels\x02Tunnel already exists\x02Another tunnel alread" +
+ "y exists with the name ‘%[1]s’.\x02Unable to create new configuration" +
+ "\x02Writing file failed\x02File ‘%[1]s’ already exists.\x0a\x0aDo you wa" +
+ "nt to overwrite it?\x02Active\x02Activating\x02Inactive\x02Deactivating" +
+ "\x02Unknown state\x02Log\x02&Copy\x02Select &all\x02&Save to file…\x02Ti" +
+ "me\x02Log message\x02Text Files (*.txt)|*.txt|All Files (*.*)|*.*\x02Exp" +
+ "ort log to file\x02&About WireGuard…\x02Tunnel Error\x02%[1]s\x0a\x0aPle" +
+ "ase consult the log for more information.\x02%[1]s (out of date)\x02Wire" +
+ "Guard Detection Error\x02Unable to wait for WireGuard window to appear: " +
+ "%[1]v\x02WireGuard: Deactivated\x02Status: Unknown\x02Addresses: None" +
+ "\x02&Manage tunnels…\x02&Import tunnel(s) from file…\x02E&xit\x02WireGua" +
+ "rd Tunnel Error\x02WireGuard: %[1]s\x02Status: %[1]s\x02Addresses: %[1]s" +
+ "\x02WireGuard Activated\x02The %[1]s tunnel has been activated.\x02WireG" +
+ "uard Deactivated\x02The %[1]s tunnel has been deactivated.\x02An Update " +
+ "is Available!\x02WireGuard Update Available\x02An update to WireGuard is" +
+ " now available. You are advised to update as soon as possible.\x02Tunnel" +
+ "s\x02&Edit\x02Add &empty tunnel…\x02Add Tunnel\x02Remove selected tunnel" +
+ "(s)\x02Export all tunnels to zip\x02&Toggle\x02Export all tunnels to &zi" +
+ "p…\x02Edit &selected tunnel…\x02&Remove selected tunnel(s)\x02Could not " +
+ "import selected configuration: %[1]v\x02Could not enumerate existing tun" +
+ "nels: %[1]v\x02Another tunnel already exists with the name ‘%[1]s’\x02Un" +
+ "able to import configuration: %[1]v\x02Imported tunnels\x14\x01\x81\x01" +
+ "\x00\x02\x16\x02Imported %[1]d tunnel\x00\x17\x02Imported %[1]d tunnels" +
+ "\x14\x02\x80\x01\x02\x1f\x02Imported %[1]d of %[2]d tunnel\x00 \x02Impor" +
+ "ted %[1]d of %[2]d tunnels\x02Unable to create tunnel\x14\x01\x81\x01" +
+ "\x00\x02\x14\x02Delete %[1]d tunnel\x00\x15\x02Delete %[1]d tunnels\x14" +
+ "\x01\x81\x01\x00\x024\x02Are you sure you would like to delete %[1]d tun" +
+ "nel?\x005\x02Are you sure you would like to delete %[1]d tunnels?\x02Del" +
+ "ete tunnel ‘%[1]s’\x02Are you sure you would like to delete tunnel ‘%[1]" +
+ "s’?\x02%[1]s You cannot undo this action.\x02Unable to delete tunnel\x02" +
+ "A tunnel was unable to be removed: %[1]s\x02Unable to delete tunnels\x14" +
+ "\x01\x81\x01\x00\x02'\x02%[1]d tunnel was unable to be removed.\x00)\x02" +
+ "%[1]d tunnels were unable to be removed.\x02Configuration Files (*.zip, " +
+ "*.conf)|*.zip;*.conf|All Files (*.*)|*.*\x02Import tunnel(s) from file" +
+ "\x02Configuration ZIP Files (*.zip)|*.zip\x02Export tunnels to zip\x02%[" +
+ "1]s (unsigned build, no updates)\x02Error Exiting WireGuard\x02Unable to" +
+ " exit service due to: %[1]v. You may want to stop WireGuard from the ser" +
+ "vice manager.\x02An update to WireGuard is available. It is highly advis" +
+ "able to update without delay.\x02Status: Waiting for user\x02Update Now" +
+ "\x02Status: Waiting for updater service\x02Error: %[1]v. Please try agai" +
+ "n.\x02Status: Complete!\x02http2: Framer %[1]p: failed to decode just-wr" +
+ "itten frame\x02http2: Framer %[1]p: wrote %[2]v\x02http2: Framer %[1]p: " +
+ "read %[2]v\x02http2: decoded hpack field %+[1]v"
+
+ // Total table size 5895 bytes (5KiB); checksum: ED8BBF53