mirror of https://github.com/miguelmota/cointop
Add github.com/jeandeaual/go-locale for locale-detection across platform
parent
11bf5e23df
commit
1cf12fd173
@ -0,0 +1 @@
|
||||
* text eol=lf
|
@ -0,0 +1,44 @@
|
||||
### Go ###
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# Fyne Cross build folder
|
||||
fyne-cross/
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history
|
@ -0,0 +1,40 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
# Default
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
# Manually enabled
|
||||
- dupl
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- golint
|
||||
- goprintffuncname
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- prealloc
|
||||
- unconvert
|
||||
- whitespace
|
||||
linter-settings:
|
||||
gocyclo:
|
||||
# Minimal code complexity to report, 30 by default
|
||||
min-complexity: 15
|
||||
govet:
|
||||
# Report about shadowed variables
|
||||
check-shadowing: true
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
# govet: Common false positives
|
||||
- (possible misuse of unsafe.Pointer|should have signature)
|
@ -0,0 +1,21 @@
|
||||
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.
|
@ -0,0 +1,135 @@
|
||||
# go-locale
|
||||
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/jeandeaual/go-locale)](https://pkg.go.dev/github.com/jeandeaual/go-locale)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/jeandeaual/go-locale)](https://goreportcard.com/report/github.com/jeandeaual/go-locale)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/jeandeaual/go-locale/badge.svg?branch=master)](https://coveralls.io/github/jeandeaual/go-locale?branch=master)
|
||||
[![test](https://github.com/jeandeaual/go-locale/workflows/test/badge.svg)](https://github.com/jeandeaual/go-locale/actions?query=workflow%3Atest)
|
||||
|
||||
Go library used to retrieve the current locale(s) of the operating system.
|
||||
|
||||
## OS Support
|
||||
|
||||
* Windows\
|
||||
Using [`GetUserDefaultLocaleName`](https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserdefaultlocalename) and [`GetSystemDefaultLocaleName`](https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getsystemdefaultlocalename).
|
||||
* macOS\
|
||||
Using `defaults read -g AppleLocale` and `defaults read -g AppleLanguages` (since environment variables like `LANG` are not usually set on macOS).
|
||||
* Unix-like systems (Linux, BSD, etc.)\
|
||||
Using the `LANGUAGE`, `LC_ALL`, `LC_MESSAGES` and `LANG` environment variables.
|
||||
* WASM (JavaScript)\
|
||||
Using [`navigator.language`](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/language) and [`navigator.languages`](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages).
|
||||
* iOS\
|
||||
Using [`[NSLocale preferredLanguages]`](https://developer.apple.com/documentation/foundation/nslocale/1415614-preferredlanguages).
|
||||
* Android\
|
||||
Using [`getResources().getConfiguration().getLocales`](https://developer.android.com/reference/android/content/res/Configuration#getLocales()) for Android N or later, or [`getResources().getConfiguration().locale`](https://developer.android.com/reference/android/content/res/Configuration#locale) otherwise.
|
||||
|
||||
Note: for Android, you'll first need to call `SetRunOnJVM`, depending on which mobile framework you're using:
|
||||
* For [Fyne](https://fyne.io/):
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/fyne-io/mobile/app"
|
||||
"github.com/jeandeaual/go-locale"
|
||||
)
|
||||
|
||||
func init() {
|
||||
locale.SetRunOnJVM(app.RunOnJVM)
|
||||
}
|
||||
```
|
||||
|
||||
* For [gomobile](https://github.com/golang/go/wiki/Mobile):
|
||||
|
||||
```go
|
||||
import (
|
||||
"golang.org/x/mobile/app"
|
||||
"github.com/jeandeaual/go-locale"
|
||||
)
|
||||
|
||||
func init() {
|
||||
locale.SetRunOnJVM(app.RunOnJVM)
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
## GetLocales
|
||||
|
||||
`GetLocales` returns the user's preferred locales, by order of preference, as a slice of [IETF BCP 47 language tag](https://tools.ietf.org/rfc/bcp/bcp47.txt) (e.g. `[]string{"en-US", "fr-FR", "ja-JP"}`).
|
||||
|
||||
This works if the user set multiple languages on macOS and other Unix systems.
|
||||
Otherwise, it returns a slice with a single locale.
|
||||
|
||||
```go
|
||||
userLocales, err := locale.GetLocales()
|
||||
if err == nil {
|
||||
fmt.Println("Locales:", userLocales)
|
||||
}
|
||||
```
|
||||
|
||||
This can be used with [golang.org/x/text](https://godoc.org/golang.org/x/text) or [go-i18n](https://github.com/nicksnyder/go-i18n) to set the localizer's language preferences:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/jeandeaual/go-locale"
|
||||
"golang.org/x/text/message"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userLocales, _ := locale.GetLocales()
|
||||
p := message.NewPrinter(message.MatchLanguage(userLocales...))
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/jeandeaual/go-locale"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userLocales, _ := locale.GetLocales()
|
||||
bundle := i18n.NewBundle(language.English)
|
||||
localizer := i18n.NewLocalizer(bundle, userLocales...)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
For a complete example, see [here](examples/getlocale-gui/main.go).
|
||||
|
||||
## GetLocale
|
||||
|
||||
`GetLocale` returns the current locale as defined in [IETF BCP 47](https://tools.ietf.org/rfc/bcp/bcp47.txt) (e.g. `"en-US"`).
|
||||
|
||||
```go
|
||||
userLocale, err := locale.GetLocale()
|
||||
if err == nil {
|
||||
fmt.Println("Locale:", userLocale)
|
||||
}
|
||||
```
|
||||
|
||||
## GetLanguage
|
||||
|
||||
`GetLanguage` returns the current language as an [ISO 639](http://en.wikipedia.org/wiki/ISO_639) language code (e.g. `"en"`).
|
||||
|
||||
```go
|
||||
userLanguage, err := locale.GetLanguage()
|
||||
if err == nil {
|
||||
fmt.Println("Language:", userLocale)
|
||||
}
|
||||
```
|
||||
|
||||
## GetRegion
|
||||
|
||||
`GetRegion` returns the current language as an [ISO 3166](http://en.wikipedia.org/wiki/ISO_3166-1) country code (e.g. `"US"`).
|
||||
|
||||
```go
|
||||
userRegion, err := locale.GetRegion()
|
||||
if err == nil {
|
||||
fmt.Println("Region:", userRegion)
|
||||
}
|
||||
```
|
||||
|
||||
## Aknowledgements
|
||||
|
||||
Inspired by [jibber_jabber](https://github.com/cloudfoundry-attic/jibber_jabber).
|
@ -0,0 +1,243 @@
|
||||
// +build android
|
||||
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "GoLog", __VA_ARGS__)
|
||||
|
||||
static const char *jstringToCharCopy(JNIEnv *env, const jstring string)
|
||||
{
|
||||
const char *chars = (*env)->GetStringUTFChars(env, string, NULL);
|
||||
const char *copy = strdup(chars);
|
||||
(*env)->ReleaseStringUTFChars(env, string, chars);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
static jclass findClass(JNIEnv *env, const char *class_name)
|
||||
{
|
||||
jclass clazz = (*env)->FindClass(env, class_name);
|
||||
|
||||
if (clazz == NULL)
|
||||
{
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find %s", class_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return clazz;
|
||||
}
|
||||
|
||||
static jmethodID findMethod(JNIEnv *env, jclass clazz, const char *name, const char *sig)
|
||||
{
|
||||
jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
|
||||
|
||||
if (m == 0)
|
||||
{
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find method %s %s", name, sig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
static jfieldID findField(JNIEnv *env, jclass clazz, const char *name, const char *sig)
|
||||
{
|
||||
jfieldID f = (*env)->GetFieldID(env, clazz, name, sig);
|
||||
|
||||
if (f == 0)
|
||||
{
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find method %s %s", name, sig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
static jfieldID getStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)
|
||||
{
|
||||
jfieldID f = (*env)->GetStaticFieldID(env, clazz, name, sig);
|
||||
|
||||
if (f == 0)
|
||||
{
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find static field %s %s", name, sig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
static const char *toLanguageTag(JNIEnv *env, jobject locale)
|
||||
{
|
||||
const jclass java_util_Locale = findClass(env, "java/util/Locale");
|
||||
|
||||
const jstring localeStr =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
locale,
|
||||
(*env)->GetMethodID(env, java_util_Locale, "toLanguageTag", "()Ljava/lang/String;"));
|
||||
|
||||
return jstringToCharCopy(env, localeStr);
|
||||
}
|
||||
|
||||
static const char *toLanguageTags(JNIEnv *env, jobject locales, jclass android_os_LocaleList)
|
||||
{
|
||||
const jstring localeStr =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
locales,
|
||||
(*env)->GetMethodID(env, android_os_LocaleList, "toLanguageTags", "()Ljava/lang/String;"));
|
||||
|
||||
return jstringToCharCopy(env, localeStr);
|
||||
}
|
||||
|
||||
static int getAPIVersion(JNIEnv *env)
|
||||
{
|
||||
// VERSION is a nested class within android.os.Build (hence "$" rather than "/")
|
||||
const jclass versionClass = findClass(env, "android/os/Build$VERSION");
|
||||
const jfieldID sdkIntFieldID = getStaticFieldID(env, versionClass, "SDK_INT", "I");
|
||||
|
||||
int sdkInt = (*env)->GetStaticIntField(env, versionClass, sdkIntFieldID);
|
||||
|
||||
return sdkInt;
|
||||
}
|
||||
|
||||
static const jobject getConfiguration(JNIEnv *env, jobject context)
|
||||
{
|
||||
const jclass android_content_ContextWrapper = findClass(env, "android/content/ContextWrapper");
|
||||
const jclass android_content_res_Resources = findClass(env, "android/content/res/Resources");
|
||||
|
||||
const jobject resources =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
context,
|
||||
findMethod(env, android_content_ContextWrapper, "getResources", "()Landroid/content/res/Resources;"));
|
||||
const jobject configuration =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
resources,
|
||||
findMethod(env, android_content_res_Resources, "getConfiguration", "()Landroid/content/res/Configuration;"));
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
static const jobject getLocaleObject(JNIEnv *env, jobject context)
|
||||
{
|
||||
const jobject configuration = getConfiguration(env, context);
|
||||
const jclass android_content_res_Configuration = findClass(env, "android/content/res/Configuration");
|
||||
|
||||
int version = getAPIVersion(env);
|
||||
|
||||
// Android N or later
|
||||
// See https://developer.android.com/reference/android/content/res/Configuration#locale
|
||||
if (version >= 24) {
|
||||
const jclass android_os_LocaleList = findClass(env, "android/os/LocaleList");
|
||||
|
||||
const jobject locales =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
configuration,
|
||||
findMethod(env, android_content_res_Configuration, "getLocales", "()Landroid/os/LocaleList;"));
|
||||
|
||||
return (*env)->CallObjectMethod(
|
||||
env,
|
||||
locales,
|
||||
findMethod(env, android_os_LocaleList, "get", "(I)Ljava/util/Locale;"),
|
||||
0);
|
||||
} else {
|
||||
return (*env)->GetObjectField(
|
||||
env,
|
||||
configuration,
|
||||
findField(env, android_content_res_Configuration, "locale", "Ljava/util/Locale;"));
|
||||
}
|
||||
}
|
||||
|
||||
// Basically the same as `getResources().getConfiguration().getLocales()` for Android N and later,
|
||||
// or `getResources().getConfiguration().locale` for earlier Android version.
|
||||
const char *getLocales(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx)
|
||||
{
|
||||
JavaVM *vm = (JavaVM *)java_vm;
|
||||
JNIEnv *env = (JNIEnv *)jni_env;
|
||||
jobject context = (jobject)ctx;
|
||||
|
||||
const jobject configuration = getConfiguration(env, context);
|
||||
const jclass android_content_res_Configuration = findClass(env, "android/content/res/Configuration");
|
||||
|
||||
int version = getAPIVersion(env);
|
||||
|
||||
// Android N or later
|
||||
// See https://developer.android.com/reference/android/content/res/Configuration#locale
|
||||
if (version >= 24) {
|
||||
const jclass android_os_LocaleList = findClass(env, "android/os/LocaleList");
|
||||
|
||||
const jobject locales =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
configuration,
|
||||
findMethod(env, android_content_res_Configuration, "getLocales", "()Landroid/os/LocaleList;"));
|
||||
|
||||
return toLanguageTags(env, locales, android_os_LocaleList);
|
||||
} else {
|
||||
const jobject locale =
|
||||
(*env)->GetObjectField(
|
||||
env,
|
||||
configuration,
|
||||
findField(env, android_content_res_Configuration, "locale", "Ljava/util/Locale;"));
|
||||
|
||||
return toLanguageTag(env, locale);
|
||||
}
|
||||
}
|
||||
|
||||
// Basically the same as `getResources().getConfiguration().getLocales().get(0).toString()` for Android N and later,
|
||||
// or `getResources().getConfiguration().locale` for earlier Android version.
|
||||
const char *getLocale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx)
|
||||
{
|
||||
JavaVM *vm = (JavaVM *)java_vm;
|
||||
JNIEnv *env = (JNIEnv *)jni_env;
|
||||
jobject context = (jobject)ctx;
|
||||
|
||||
const jobject locale = getLocaleObject(env, context);
|
||||
|
||||
return toLanguageTag(env, locale);
|
||||
}
|
||||
|
||||
const char *getLanguage(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx)
|
||||
{
|
||||
JavaVM *vm = (JavaVM *)java_vm;
|
||||
JNIEnv *env = (JNIEnv *)jni_env;
|
||||
jobject context = (jobject)ctx;
|
||||
|
||||
const jobject locale = getLocaleObject(env, context);
|
||||
const jclass java_util_Locale = findClass(env, "java/util/Locale");
|
||||
|
||||
const jstring language =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
locale,
|
||||
(*env)->GetMethodID(env, java_util_Locale, "getLanguage", "()Ljava/lang/String;"));
|
||||
|
||||
return jstringToCharCopy(env, language);
|
||||
}
|
||||
|
||||
const char *getRegion(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx)
|
||||
{
|
||||
JavaVM *vm = (JavaVM *)java_vm;
|
||||
JNIEnv *env = (JNIEnv *)jni_env;
|
||||
jobject context = (jobject)ctx;
|
||||
|
||||
const jobject locale = getLocaleObject(env, context);
|
||||
const jclass java_util_Locale = findClass(env, "java/util/Locale");
|
||||
|
||||
const jstring country =
|
||||
(*env)->CallObjectMethod(
|
||||
env,
|
||||
locale,
|
||||
(*env)->GetMethodID(env, java_util_Locale, "getCountry", "()Ljava/lang/String;"));
|
||||
|
||||
return jstringToCharCopy(env, country);
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
// +build android
|
||||
|
||||
package locale
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -landroid -llog
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
const char *getLocales(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx);
|
||||
const char *getLocale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx);
|
||||
const char *getLanguage(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx);
|
||||
const char *getRegion(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
errRunOnJVMNotSet error = errors.New("you first need to call SetRunOnJVM")
|
||||
runOnJVM func(fn func(vm, env, ctx uintptr) error) error
|
||||
)
|
||||
|
||||
// SetRunOnJVM sets the RunOnJVM function that will be called by this library.
|
||||
// This can either be "golang.org/x/mobile/app".RunOnJVM or "github.com/fyne-io/mobile/app".RunOnJVM,
|
||||
// depending on the mobile framework you're using (both can't be imported at the same time).
|
||||
//
|
||||
// RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.
|
||||
//
|
||||
// RunOnJVM blocks until the call to fn is complete. Any Java
|
||||
// exception or failure to attach to the JVM is returned as an error.
|
||||
//
|
||||
// The function fn takes vm, the current JavaVM*,
|
||||
// env, the current JNIEnv*, and
|
||||
// ctx, a jobject representing the global android.context.Context.
|
||||
func SetRunOnJVM(fn func(fn func(vm, env, ctx uintptr) error) error) {
|
||||
runOnJVM = fn
|
||||
}
|
||||
|
||||
// GetLocale retrieves the IETF BCP 47 language tag set on the system.
|
||||
func GetLocale() (string, error) {
|
||||
if runOnJVM == nil {
|
||||
return "", errRunOnJVMNotSet
|
||||
}
|
||||
|
||||
locale := ""
|
||||
|
||||
err := runOnJVM(func(vm, env, ctx uintptr) error {
|
||||
chars := C.getLocale(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
|
||||
locale = C.GoString(chars)
|
||||
C.free(unsafe.Pointer(chars))
|
||||
return nil
|
||||
})
|
||||
|
||||
return locale, err
|
||||
}
|
||||
|
||||
// GetLocales retrieves the IETF BCP 47 language tags set on the system.
|
||||
func GetLocales() ([]string, error) {
|
||||
if runOnJVM == nil {
|
||||
return nil, errRunOnJVMNotSet
|
||||
}
|
||||
|
||||
locales := ""
|
||||
|
||||
err := runOnJVM(func(vm, env, ctx uintptr) error {
|
||||
chars := C.getLocales(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
|
||||
locales = C.GoString(chars)
|
||||
C.free(unsafe.Pointer(chars))
|
||||
return nil
|
||||
})
|
||||
|
||||
return strings.Split(locales, ","), err
|
||||
}
|
||||
|
||||
// GetLanguage retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the language part of the tag.
|
||||
func GetLanguage() (string, error) {
|
||||
if runOnJVM == nil {
|
||||
return "", errRunOnJVMNotSet
|
||||
}
|
||||
|
||||
language := ""
|
||||
|
||||
err := runOnJVM(func(vm, env, ctx uintptr) error {
|
||||
chars := C.getLanguage(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
|
||||
language = C.GoString(chars)
|
||||
C.free(unsafe.Pointer(chars))
|
||||
return nil
|
||||
})
|
||||
|
||||
return language, err
|
||||
}
|
||||
|
||||
// GetRegion retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the region part of the tag.
|
||||
func GetRegion() (string, error) {
|
||||
if runOnJVM == nil {
|
||||
return "", errRunOnJVMNotSet
|
||||
}
|
||||
|
||||
region := ""
|
||||
|
||||
err := runOnJVM(func(vm, env, ctx uintptr) error {
|
||||
chars := C.getRegion(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
|
||||
region = C.GoString(chars)
|
||||
C.free(unsafe.Pointer(chars))
|
||||
return nil
|
||||
})
|
||||
|
||||
return region, err
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
// +build darwin,!ios
|
||||
|
||||
package locale
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func execCommand(cmd string, args ...string) (status int, out []byte, err error) {
|
||||
status = -1
|
||||
command := exec.Command(cmd, args...)
|
||||
|
||||
// Execute the command and get the standard and error outputs
|
||||
out, err = command.CombinedOutput()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check the status code
|
||||
if w, ok := command.ProcessState.Sys().(syscall.WaitStatus); ok {
|
||||
status = w.ExitStatus()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetLocale retrieves the IETF BCP 47 language tag set on the system.
|
||||
func GetLocale() (string, error) {
|
||||
_, output, err := execCommand("defaults", "read", "-g", "AppleLocale")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot determine locale: %v (output: %s)", err, output)
|
||||
}
|
||||
|
||||
return strings.TrimRight(strings.Replace(string(output), "_", "-", 1), "\n"), nil
|
||||
}
|
||||
|
||||
// appleLanguagesRegex is used to parse the output of "defaults read -g AppleLanguages"
|
||||
// e.g.:
|
||||
// (
|
||||
// "en-US",
|
||||
// "fr-FR",
|
||||
// "ja-JP"
|
||||
// )
|
||||
var appleLanguagesRegex = regexp.MustCompile(`"([a-z]{2}-[A-Z]{2})"`)
|
||||
|
||||
// GetLocales retrieves the IETF BCP 47 language tags set on the system.
|
||||
func GetLocales() ([]string, error) {
|
||||
_, output, err := execCommand("defaults", "read", "-g", "AppleLanguages")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot determine locale: %v (output: %s)", err, output)
|
||||
}
|
||||
|
||||
matches := appleLanguagesRegex.FindAllStringSubmatch(string(output), -1)
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("invalid output from \"defaults read -g AppleLanguages\": %s", output)
|
||||
}
|
||||
|
||||
locales := make([]string, 0, len(matches))
|
||||
|
||||
for _, match := range matches {
|
||||
locales = append(locales, match[1])
|
||||
}
|
||||
|
||||
return locales, nil
|
||||
}
|
||||
|
||||
// GetLanguage retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the language part of the tag.
|
||||
func GetLanguage() (string, error) {
|
||||
language := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
language, _ = splitLocale(locale)
|
||||
}
|
||||
|
||||
return language, err
|
||||
}
|
||||
|
||||
// GetRegion retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the region part of the tag.
|
||||
func GetRegion() (string, error) {
|
||||
region := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
_, region = splitLocale(locale)
|
||||
}
|
||||
|
||||
return region, err
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
// +build ios
|
||||
|
||||
package locale
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework UIKit
|
||||
|
||||
const char *getLocale();
|
||||
const char *getLocales();
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetLocale retrieves the IETF BCP 47 language tag set on the system.
|
||||
func GetLocale() (string, error) {
|
||||
return C.GoString(C.getLocale()), nil
|
||||
}
|
||||
|
||||
// GetLocales retrieves the IETF BCP 47 language tags set on the system.
|
||||
func GetLocales() ([]string, error) {
|
||||
return strings.Split(C.GoString(C.getLocales()), ","), nil
|
||||
}
|
||||
|
||||
// GetLanguage retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the language part of the tag.
|
||||
func GetLanguage() (string, error) {
|
||||
language := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
language, _ = splitLocale(locale)
|
||||
}
|
||||
|
||||
return language, err
|
||||
}
|
||||
|
||||
// GetRegion retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the region part of the tag.
|
||||
func GetRegion() (string, error) {
|
||||
region := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
_, region = splitLocale(locale)
|
||||
}
|
||||
|
||||
return region, err
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// +build ios
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
const char *getLocale()
|
||||
{
|
||||
NSString *locale = [[NSLocale preferredLanguages] firstObject];
|
||||
|
||||
return [locale UTF8String];
|
||||
}
|
||||
|
||||
const char *getLocales()
|
||||
{
|
||||
NSString *locales = [[NSLocale preferredLanguages] componentsJoinedByString:@","];
|
||||
|
||||
return [locales UTF8String];
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// +build js,wasm
|
||||
|
||||
package locale
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
func getNavigatorObject() js.Value {
|
||||
return js.Global().Get("navigator")
|
||||
}
|
||||
|
||||
// GetLocale retrieves the IETF BCP 47 language tag set on the system.
|
||||
func GetLocale() (string, error) {
|
||||
navigator := getNavigatorObject()
|
||||
if navigator.IsUndefined() {
|
||||
return "", errors.New("couldn't get window.navigator")
|
||||
}
|
||||
|
||||
language := navigator.Get("language")
|
||||
if language.IsUndefined() {
|
||||
return "", errors.New("couldn't get window.navigator.language")
|
||||
}
|
||||
|
||||
return language.String(), nil
|
||||
}
|
||||
|
||||
// GetLocales retrieves the IETF BCP 47 language tags set on the system.
|
||||
func GetLocales() ([]string, error) {
|
||||
navigator := getNavigatorObject()
|
||||
if navigator.IsUndefined() {
|
||||
return nil, errors.New("couldn't get window.navigator")
|
||||
}
|
||||
|
||||
languages := navigator.Get("languages")
|
||||
if languages.IsUndefined() {
|
||||
return nil, errors.New("couldn't get window.navigator.languages")
|
||||
}
|
||||
|
||||
locales := make([]string, 0, languages.Length())
|
||||
|
||||
// Convert the Javascript object to a string slice
|
||||
for i := 0; i < languages.Length(); i++ {
|
||||
locales = append(locales, languages.Index(i).String())
|
||||
}
|
||||
|
||||
return locales, nil
|
||||
}
|
||||
|
||||
// GetLanguage retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the language part of the tag.
|
||||
func GetLanguage() (string, error) {
|
||||
language := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
language, _ = splitLocale(locale)
|
||||
}
|
||||
|
||||
return language, err
|
||||
}
|
||||
|
||||
// GetRegion retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the region part of the tag.
|
||||
func GetRegion() (string, error) {
|
||||
region := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
_, region = splitLocale(locale)
|
||||
}
|
||||
|
||||
return region, err
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
// +build !windows,!darwin,!js,!android
|
||||
|
||||
package locale
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func splitLocales(locales string) []string {
|
||||
// If the user set different locales, they might be set in $LANGUAGE,
|
||||
// separated by a colon
|
||||
return strings.Split(locales, ":")
|
||||
}
|
||||
|
||||
func getLangFromEnv() string {
|
||||
locale := ""
|
||||
|
||||
// Check the following environment variables for the language information
|
||||
// See https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
|
||||
for _, env := range [...]string{"LC_ALL", "LC_MESSAGES", "LANG"} {
|
||||
locale = os.Getenv(env)
|
||||
if len(locale) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if locale == "C" || locale == "POSIX" {
|
||||
return locale
|
||||
}
|
||||
|
||||
// Check LANGUAGE if localization is enabled (not set to "C")
|
||||
// See https://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html#The-LANGUAGE-variable
|
||||
languages := os.Getenv("LANGUAGE")
|
||||
if len(languages) > 0 {
|
||||
return languages
|
||||
}
|
||||
|
||||
return locale
|
||||
}
|
||||
|
||||
func getUnixLocales() []string {
|
||||
locale := getLangFromEnv()
|
||||
if locale == "C" || locale == "POSIX" || len(locale) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return splitLocales(locale)
|
||||
}
|
||||
|
||||
// GetLocale retrieves the IETF BCP 47 language tag set on the system.
|
||||
func GetLocale() (string, error) {
|
||||
unixLocales := getUnixLocales()
|
||||
if unixLocales == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
language, region := splitLocale(unixLocales[0])
|
||||
locale := language
|
||||
if len(region) > 0 {
|
||||
locale = strings.Join([]string{language, region}, "-")
|
||||
}
|
||||
|
||||
return locale, nil
|
||||
}
|
||||
|
||||
// GetLocales retrieves the IETF BCP 47 language tags set on the system.
|
||||
func GetLocales() ([]string, error) {
|
||||
unixLocales := getUnixLocales()
|
||||
if unixLocales == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
locales := make([]string, 0, len(unixLocales))
|
||||
|
||||
for _, unixLocale := range unixLocales {
|
||||
language, region := splitLocale(unixLocale)
|
||||
locale := language
|
||||
if len(region) > 0 {
|
||||
locale = strings.Join([]string{language, region}, "-")
|
||||
}
|
||||
locales = append(locales, locale)
|
||||
}
|
||||
|
||||
return locales, nil
|
||||
}
|
||||
|
||||
// GetLanguage retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the language part of the tag.
|
||||
func GetLanguage() (string, error) {
|
||||
language := ""
|
||||
|
||||
unixLocales := getUnixLocales()
|
||||
if unixLocales == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
language, _ = splitLocale(unixLocales[0])
|
||||
|
||||
return language, nil
|
||||
}
|
||||
|
||||
// GetRegion retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the region part of the tag.
|
||||
func GetRegion() (string, error) {
|
||||
region := ""
|
||||
|
||||
unixLocales := getUnixLocales()
|
||||
if unixLocales == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
_, region = splitLocale(unixLocales[0])
|
||||
|
||||
return region, nil
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
// +build windows
|
||||
|
||||
package locale
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// 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 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
|
||||
}
|
||||
|
||||
// GetLocales retrieves the IETF BCP 47 language tags set on the system.
|
||||
func GetLocales() ([]string, error) {
|
||||
locale, err := GetLocale()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []string{locale}, nil
|
||||
}
|
||||
|
||||
// GetLanguage retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the language part of the tag.
|
||||
func GetLanguage() (string, error) {
|
||||
language := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
language, _ = splitLocale(locale)
|
||||
}
|
||||
|
||||
return language, err
|
||||
}
|
||||
|
||||
// GetRegion retrieves the IETF BCP 47 language tag set on the system and
|
||||
// returns the region part of the tag.
|
||||
func GetRegion() (string, error) {
|
||||
region := ""
|
||||
|
||||
locale, err := GetLocale()
|
||||
if err == nil {
|
||||
_, region = splitLocale(locale)
|
||||
}
|
||||
|
||||
return region, err
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// +build !android
|
||||
|
||||
package locale
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SetRunOnJVM is a noop, this function is only valid on Android
|
||||
func SetRunOnJVM(fn func(fn func(vm, env, ctx uintptr) error) error) {}
|
||||
|
||||
func splitLocale(locale string) (string, string) {
|
||||
// Remove the encoding, if present
|
||||
formattedLocale := strings.Split(locale, ".")[0]
|
||||
// Normalize by replacing the hyphens with underscores
|
||||
formattedLocale = strings.Replace(formattedLocale, "-", "_", -1)
|
||||
|
||||
// Split at the underscore
|
||||
split := strings.Split(formattedLocale, "_")
|
||||
language := split[0]
|
||||
territory := ""
|
||||
if len(split) > 1 {
|
||||
territory = split[1]
|
||||
}
|
||||
|
||||
return language, territory
|
||||
}
|
Loading…
Reference in New Issue