Fix locale detection on Windows and Linux

This commit is contained in:
Trevor Slocum 2024-09-07 14:48:06 -07:00
parent 7b39bc5df8
commit ab5a48b3c1
11 changed files with 172 additions and 34 deletions

View file

@ -1,3 +1,6 @@
1.4.1:
- Fix locale detection on Windows and Linux
1.4.0:
- Auto-scale font size to fit text
- Support returning to the main menu

View file

@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"strconv"
"strings"
"time"
"code.rocket9labs.com/tslocum/bgammon"
@ -87,14 +88,21 @@ func parseFlags() *game.Game {
var forceLanguage *language.Tag
if locale == "" {
locale = game.DefaultLocale()
var err error
locale, err = game.GetLocale()
if err != nil {
locale = ""
}
}
if locale != "" {
tag, err := language.Parse(locale)
if err != nil {
log.Fatalf("unknown locale: %s", locale)
dotIndex := strings.IndexByte(locale, '.')
if dotIndex != -1 {
locale = locale[:dotIndex]
}
tag, err := language.Parse(locale)
if err == nil {
forceLanguage = &tag
}
forceLanguage = &tag
}
game.LoadLocale(forceLanguage)

View file

@ -9,8 +9,8 @@ import (
func parseFlags() *game.Game {
var forceLanguage *language.Tag
locale := game.DefaultLocale()
if locale != "" {
locale, err := game.GetLocale()
if err == nil && locale != "" {
tag, err := language.Parse(locale)
if err == nil {
forceLanguage = &tag

View file

@ -7,7 +7,6 @@ import (
"fmt"
"log"
"net"
"runtime/debug"
"strings"
"time"
@ -89,7 +88,6 @@ func (c *Client) LoggedIn() bool {
func (c *Client) connectWebSocket() {
if !c.connecting {
debug.PrintStack()
return
}
@ -101,7 +99,6 @@ func (c *Client) connectWebSocket() {
}
for {
if !c.connecting {
debug.PrintStack()
return
}
if !focused() {

View file

@ -15,7 +15,6 @@ import (
"time"
"code.rocket9labs.com/tslocum/gotext"
"golang.org/x/text/language"
)
const (
@ -61,21 +60,6 @@ func init() {
return
}
time.Local = tz
// Detect locale.
out, err = exec.Command("/system/bin/getprop", "persist.sys.locale").Output()
if err != nil {
return
}
tag, err := language.Parse(strings.TrimSpace(string(out)))
if err != nil {
return
}
LoadLocale(&tag)
}
func DefaultLocale() string {
return ""
}
func focused() bool {

View file

@ -21,10 +21,6 @@ const (
targetFPS = 144
)
func DefaultLocale() string {
return ""
}
func focused() bool {
return true
}

View file

@ -36,10 +36,6 @@ func init() {
}
}
func DefaultLocale() string {
return js.Global().Get("navigator").Get("language").String()
}
func focused() bool {
document := js.Global().Get("document")
hasFocus := document.Call("hasFocus", nil)

31
game/locale_android.go Normal file
View file

@ -0,0 +1,31 @@
//go:build android
package game
import (
"os/exec"
"strings"
"golang.org/x/text/language"
)
func init() {
// Load locale early on Android.
locale, err := GetLocale()
if err != nil {
return
}
tag, err := language.Parse(strings.TrimSpace(locale))
if err != nil {
return
}
LoadLocale(&tag)
}
func GetLocale() (string, error) {
out, err := exec.Command("/system/bin/getprop", "persist.sys.locale").Output()
if err != nil {
return "", err
}
return string(out), nil
}

11
game/locale_linux.go Normal file
View file

@ -0,0 +1,11 @@
//go:build linux && !android
package game
import (
"os"
)
func GetLocale() (string, error) {
return os.Getenv("LANG"), nil
}

9
game/locale_wasm.go Normal file
View file

@ -0,0 +1,9 @@
//go:build js && wasm
package game
import "syscall/js"
func GetLocale() (string, error) {
return js.Global().Get("navigator").Get("language").String(), nil
}

103
game/locale_windows.go Normal file
View file

@ -0,0 +1,103 @@
//go:build windows
package game
import (
"fmt"
"strings"
"unsafe"
"golang.org/x/sys/windows"
)
// Source code in this file was copied from https://github.com/jeandeaual/go-locale
// The following license applies to the source code in this file:
//
// MIT License
//
// Copyright (c) 2020 Alexis Jeandeau
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// LocaleNameMaxLength is the maximum length of a locale name on Windows.
// See https://docs.microsoft.com/en-us/windows/win32/intl/locale-name-constants.
const LocaleNameMaxLength uint32 = 85
func splitLocale(locale string) (string, string) {
// Remove the encoding, if present.
formattedLocale, _, _ := strings.Cut(locale, ".")
// Normalize by replacing the hyphens with underscores
formattedLocale = strings.ReplaceAll(formattedLocale, "-", "_")
// Split at the underscore.
language, territory, _ := strings.Cut(formattedLocale, "_")
return language, territory
}
func getWindowsLocaleFromProc(syscall string) (string, error) {
dll, err := windows.LoadDLL("kernel32")
if err != nil {
return "", fmt.Errorf("could not find the kernel32 DLL: %v", err)
}
proc, err := dll.FindProc(syscall)
if err != nil {
return "", fmt.Errorf("could not find the %s proc in kernel32: %v", syscall, err)
}
buffer := make([]uint16, LocaleNameMaxLength)
// See https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserdefaultlocalename
// and https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getsystemdefaultlocalename
// GetUserDefaultLocaleName and GetSystemDefaultLocaleName both take a buffer and a buffer size,
// and return the length of the locale name (0 if not found).
ret, _, err := proc.Call(uintptr(unsafe.Pointer(&buffer[0])), uintptr(LocaleNameMaxLength))
if ret == 0 {
return "", fmt.Errorf("locale not found when calling %s: %v", syscall, err)
}
return windows.UTF16ToString(buffer), nil
}
func getWindowsLocale() (string, error) {
var (
locale string
err error
)
for _, proc := range [...]string{"GetUserDefaultLocaleName", "GetSystemDefaultLocaleName"} {
locale, err = getWindowsLocaleFromProc(proc)
if err == nil {
return locale, err
}
}
return locale, err
}
// GetLocale retrieves the IETF BCP 47 language tag set on the system.
func GetLocale() (string, error) {
locale, err := getWindowsLocale()
if err != nil {
return "", fmt.Errorf("cannot determine locale: %v", err)
}
return locale, err
}