This commit is contained in:
13
.drone.yml
Normal file
13
.drone.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- go test -parallel 10 ./...
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
image: golangci/golangci-lint:v1.39
|
||||||
|
commands:
|
||||||
|
- golangci-lint run
|
||||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# ---> 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, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
46
.golangci.yml
Normal file
46
.golangci.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 50
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
lll:
|
||||||
|
line-length: 140
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- exhaustivestruct
|
||||||
|
- maligned
|
||||||
|
- interfacer
|
||||||
|
- scopelint
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- exhaustivestruct
|
||||||
|
- wrapcheck
|
||||||
|
- path: test/*
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- exhaustivestruct
|
||||||
|
- wrapcheck
|
||||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
MIT License Copyright (c) 2020 4devs
|
||||||
|
|
||||||
|
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 (including the next
|
||||||
|
paragraph) 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.
|
||||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# translation
|
||||||
|
|
||||||
|
[](https://drone.gitoa.ru/go-4devs/translation)
|
||||||
|
[](https://goreportcard.com/report/gitoa.ru/go-4devs/translation)
|
||||||
|
[](http://godoc.org/gitoa.ru/go-4devs/translation)
|
||||||
19
arg/arg.go
Normal file
19
arg/arg.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package arg
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Arg defaults argument.
|
||||||
|
type Arg struct {
|
||||||
|
Key string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String arg to string.
|
||||||
|
func (a Arg) String() string {
|
||||||
|
return fmt.Sprintf("key:%s, value:%v", a.Key, a.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val gets valuet argument.
|
||||||
|
func (a Arg) Val() interface{} {
|
||||||
|
return a.Value
|
||||||
|
}
|
||||||
45
arg/currency.go
Normal file
45
arg/currency.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package arg
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// FormatCurrency types format.
|
||||||
|
type FormatCurrency int
|
||||||
|
|
||||||
|
// Currency format.
|
||||||
|
const (
|
||||||
|
CurrencyFormatSymbol FormatCurrency = iota + 1
|
||||||
|
CurrencyFormatISO
|
||||||
|
CurrencyFormatNarrowSymbol
|
||||||
|
)
|
||||||
|
|
||||||
|
// CurrencyOption configures option.
|
||||||
|
type CurrencyOption func(*Currency)
|
||||||
|
|
||||||
|
// WithCurrencyFormat sets format currency.
|
||||||
|
func WithCurrencyFormat(format FormatCurrency) CurrencyOption {
|
||||||
|
return func(c *Currency) { c.Format = format }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCurrencyISO sets ISO 4217 code currecy.
|
||||||
|
func WithCurrencyISO(iso string) CurrencyOption {
|
||||||
|
return func(c *Currency) { c.ISO = iso }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currency argument.
|
||||||
|
type Currency struct {
|
||||||
|
Key string
|
||||||
|
Value interface{}
|
||||||
|
Format FormatCurrency
|
||||||
|
// ISO 3-letter ISO 4217
|
||||||
|
ISO string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String gets string from currency.
|
||||||
|
func (a Currency) String() string {
|
||||||
|
return fmt.Sprintf("currency key:%s, value:%v", a.Key, a.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val gets value currency.
|
||||||
|
func (a Currency) Val() interface{} {
|
||||||
|
return a.Value
|
||||||
|
}
|
||||||
49
arg/number.go
Normal file
49
arg/number.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package arg
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// FormatNumber format number.
|
||||||
|
type FormatNumber int
|
||||||
|
|
||||||
|
// format argument.
|
||||||
|
const (
|
||||||
|
NumberFormatDecimal FormatNumber = iota + 1
|
||||||
|
NumberFormatPercent
|
||||||
|
NumberFormatPerMille
|
||||||
|
NumberFormatEngineering
|
||||||
|
NumberFormatScientific
|
||||||
|
)
|
||||||
|
|
||||||
|
// NumberOption configure number argument.
|
||||||
|
type NumberOption func(*Number)
|
||||||
|
|
||||||
|
// WithNumberFormat sets format number.
|
||||||
|
func WithNumberFormat(format FormatNumber) NumberOption {
|
||||||
|
return func(n *Number) { n.Format = format }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number argument.
|
||||||
|
type Number struct {
|
||||||
|
Key string
|
||||||
|
Value interface{}
|
||||||
|
Format FormatNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure number.
|
||||||
|
func (n Number) Configure(opts ...NumberOption) Number {
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val gets number value.
|
||||||
|
func (n Number) Val() interface{} {
|
||||||
|
return n.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// String number to string.
|
||||||
|
func (n Number) String() string {
|
||||||
|
return fmt.Sprintf("number key: %s, value: %v", n.Key, n.Value)
|
||||||
|
}
|
||||||
27
context.go
Normal file
27
context.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package translation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ctxkey uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
localeKey ctxkey = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithLanguage sets language to context.
|
||||||
|
func WithLanguage(ctx context.Context, lang language.Tag) context.Context {
|
||||||
|
return context.WithValue(ctx, localeKey, lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromContext get language from context or use default.
|
||||||
|
func FromContext(ctx context.Context, def language.Tag) language.Tag {
|
||||||
|
if t, ok := ctx.Value(localeKey).(language.Tag); ok {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module gitoa.ru/go-4devs/translation
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require golang.org/x/text v0.3.6
|
||||||
3
go.sum
Normal file
3
go.sum
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
22
icu/trans.go
Normal file
22
icu/trans.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package icu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/translation"
|
||||||
|
"gitoa.ru/go-4devs/translation/provider/gotext"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint: gochecknoglobals
|
||||||
|
var trans = translation.New(language.English, gotext.NewProvider())
|
||||||
|
|
||||||
|
// Trans translate message.
|
||||||
|
func Trans(ctx context.Context, key string, opts ...translation.Option) string {
|
||||||
|
return trans.Trans(ctx, key, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTranslator sets translator.
|
||||||
|
func SetTranslator(tr *translation.Translator) {
|
||||||
|
trans = tr
|
||||||
|
}
|
||||||
41
icu/trans_example_test.go
Normal file
41
icu/trans_example_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package icu_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/translation"
|
||||||
|
"gitoa.ru/go-4devs/translation/arg"
|
||||||
|
"gitoa.ru/go-4devs/translation/icu"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"golang.org/x/text/message"
|
||||||
|
"golang.org/x/text/message/catalog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleTrans_withLanguage() {
|
||||||
|
err := message.Set(language.Russian, "Hello {city}", catalog.String("Привет %s"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = message.Set(language.Russian, "It costs {cost}", catalog.String("Это стоит %.2f."))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lang, err := language.Parse("ru")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := translation.WithLanguage(context.Background(), lang)
|
||||||
|
|
||||||
|
tr := icu.Trans(ctx, "Hello {city}", translation.WithArgs("Москва"))
|
||||||
|
fmt.Println(tr)
|
||||||
|
tr = icu.Trans(ctx, "It costs {cost}", translation.WithNumber("cost", 1000.00, arg.WithNumberFormat(arg.NumberFormatDecimal)))
|
||||||
|
fmt.Println(tr)
|
||||||
|
// Output:
|
||||||
|
// Привет Москва
|
||||||
|
// Это стоит 1 000,00.
|
||||||
|
}
|
||||||
108
provider/gotext/provider.go
Normal file
108
provider/gotext/provider.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package gotext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/translation"
|
||||||
|
"gitoa.ru/go-4devs/translation/arg"
|
||||||
|
"golang.org/x/text/currency"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"golang.org/x/text/message"
|
||||||
|
"golang.org/x/text/message/catalog"
|
||||||
|
"golang.org/x/text/number"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ translation.Provider = (*Provider)(nil)
|
||||||
|
|
||||||
|
// Option confires message provider.
|
||||||
|
type Option func(*Provider)
|
||||||
|
|
||||||
|
// WithCatalog set coatalog bu domain name.
|
||||||
|
func WithCatalog(domain string, cat catalog.Catalog) Option {
|
||||||
|
return func(mp *Provider) {
|
||||||
|
mp.catalog[domain] = cat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProvider creates new messgae provider.
|
||||||
|
func NewProvider(opts ...Option) *Provider {
|
||||||
|
mp := &Provider{
|
||||||
|
catalog: map[string]catalog.Catalog{
|
||||||
|
translation.DefaultDomain: message.DefaultCatalog,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(mp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider provider messages.
|
||||||
|
type Provider struct {
|
||||||
|
catalog map[string]catalog.Catalog
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate by key and args.
|
||||||
|
func (mp *Provider) Translate(ctx context.Context, key string, opt translation.Translate) string {
|
||||||
|
return message.NewPrinter(opt.Locale, message.Catalog(mp.catalog[opt.Domain])).
|
||||||
|
Sprintf(key, messageArgs(opt.Locale, opt.Args)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func currencyMessage(lang language.Tag) func(v arg.Currency) interface{} {
|
||||||
|
return func(v arg.Currency) interface{} {
|
||||||
|
var u currency.Unit
|
||||||
|
|
||||||
|
if v.ISO != "" {
|
||||||
|
u, _ = currency.ParseISO(v.ISO)
|
||||||
|
} else {
|
||||||
|
u, _ = currency.FromTag(lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Format {
|
||||||
|
case arg.CurrencyFormatISO:
|
||||||
|
return currency.ISO(u.Amount(v.Val()))
|
||||||
|
case arg.CurrencyFormatSymbol:
|
||||||
|
return currency.Symbol(u.Amount(v.Val()))
|
||||||
|
case arg.CurrencyFormatNarrowSymbol:
|
||||||
|
return currency.NarrowSymbol(u.Amount(v.Val()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Amount(v.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberMessage(v arg.Number) interface{} {
|
||||||
|
switch v.Format {
|
||||||
|
case arg.NumberFormatPercent:
|
||||||
|
return number.Percent(v.Val())
|
||||||
|
case arg.NumberFormatEngineering:
|
||||||
|
return number.Engineering(v.Val())
|
||||||
|
case arg.NumberFormatPerMille:
|
||||||
|
return number.PerMille(v.Val())
|
||||||
|
case arg.NumberFormatDecimal:
|
||||||
|
return number.Decimal(v.Val())
|
||||||
|
case arg.NumberFormatScientific:
|
||||||
|
return number.Scientific(v.Val())
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageArgs(lang language.Tag, in []translation.Arg) []interface{} {
|
||||||
|
out := make([]interface{}, 0, len(in))
|
||||||
|
|
||||||
|
for _, a := range in {
|
||||||
|
switch v := a.(type) {
|
||||||
|
case arg.Currency:
|
||||||
|
out = append(out, currencyMessage(lang)(v))
|
||||||
|
case arg.Number:
|
||||||
|
out = append(out, numberMessage(v))
|
||||||
|
default:
|
||||||
|
out = append(out, v.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
80
translate.go
Normal file
80
translate.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package translation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/translation/arg"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option configures translate.
|
||||||
|
type Option func(*Translate)
|
||||||
|
|
||||||
|
// Translate configure translate.
|
||||||
|
type Translate struct {
|
||||||
|
Domain string
|
||||||
|
Locale language.Tag
|
||||||
|
Args []Arg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgValues gets value arguments.
|
||||||
|
func (t *Translate) ArgValues() []interface{} {
|
||||||
|
a := make([]interface{}, len(t.Args))
|
||||||
|
for i, v := range t.Args {
|
||||||
|
a[i] = v.Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg arg translate.
|
||||||
|
type Arg interface {
|
||||||
|
Val() interface{}
|
||||||
|
fmt.Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNumber sets number with options.
|
||||||
|
func WithNumber(key string, val interface{}, opts ...arg.NumberOption) Option {
|
||||||
|
return func(t *Translate) {
|
||||||
|
t.Args = append(t.Args, arg.Number{Key: key, Value: val}.Configure(opts...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCurrency sets date argument.
|
||||||
|
func WithCurrency(key string, val interface{}, opts ...arg.CurrencyOption) Option {
|
||||||
|
return func(t *Translate) {
|
||||||
|
c := arg.Currency{Value: val, Key: key}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Args = append(t.Args, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDomain sets domain translate.
|
||||||
|
func WithDomain(domain string) Option {
|
||||||
|
return func(o *Translate) {
|
||||||
|
o.Domain = domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLocale sets locale translate.
|
||||||
|
func WithLocale(locale string) Option {
|
||||||
|
return func(o *Translate) {
|
||||||
|
o.Locale = language.Make(locale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithArgs sets arguments value.
|
||||||
|
func WithArgs(vals ...interface{}) Option {
|
||||||
|
return func(t *Translate) {
|
||||||
|
args := make([]Arg, len(vals))
|
||||||
|
for i, val := range vals {
|
||||||
|
args[i] = arg.Arg{Value: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Args = append(t.Args, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
56
translator.go
Normal file
56
translator.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package translation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default values.
|
||||||
|
const (
|
||||||
|
DefaultDomain = "messages"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider for translate key.
|
||||||
|
type Provider interface {
|
||||||
|
Translate(ctx context.Context, key string, opt Translate) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslatorOption options translator.
|
||||||
|
type TranslatorOption func(*Translator)
|
||||||
|
|
||||||
|
// New creates new translator.
|
||||||
|
func New(locale language.Tag, provider Provider, opts ...TranslatorOption) *Translator {
|
||||||
|
tr := Translator{
|
||||||
|
locale: locale,
|
||||||
|
provider: provider,
|
||||||
|
domain: DefaultDomain,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translator struct.
|
||||||
|
type Translator struct {
|
||||||
|
provider Provider
|
||||||
|
domain string
|
||||||
|
locale language.Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trans translates key by options.
|
||||||
|
func (t *Translator) Trans(ctx context.Context, key string, opts ...Option) string {
|
||||||
|
opt := Translate{
|
||||||
|
Locale: FromContext(ctx, t.locale),
|
||||||
|
Domain: t.domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.provider.Translate(ctx, key, opt)
|
||||||
|
}
|
||||||
91
translator_example_test.go
Normal file
91
translator_example_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package translation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/translation"
|
||||||
|
"gitoa.ru/go-4devs/translation/arg"
|
||||||
|
"gitoa.ru/go-4devs/translation/provider/gotext"
|
||||||
|
"golang.org/x/text/feature/plural"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"golang.org/x/text/message/catalog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleTranslator_Trans() {
|
||||||
|
cat := catalog.NewBuilder()
|
||||||
|
|
||||||
|
_ = cat.Set(language.Russian, "Hello World!", catalog.String("Привет Мир!"))
|
||||||
|
_ = cat.Set(language.Slovak, "Hello World!", catalog.String("Ahoj Svet!"))
|
||||||
|
_ = cat.Set(language.Hebrew, "Hello World!", catalog.String("שלום עולם"))
|
||||||
|
_ = cat.Set(language.Russian, "Hello {name}!", catalog.String("Привет %[1]s!"))
|
||||||
|
_ = cat.Set(language.English, "Hello {name}!", catalog.String("Hello %[1]s!"))
|
||||||
|
_ = cat.Set(language.English, "There are {bikes} bikes per household.", catalog.String("There are %v bikes per household."))
|
||||||
|
_ = cat.Set(language.Russian, "There are {bikes} bikes per household.", catalog.String("В каждом доме есть %v велосипеда."))
|
||||||
|
_ = cat.Set(language.English, "You are {minute} minute(s) late.",
|
||||||
|
plural.Selectf(1, "",
|
||||||
|
"=0", "You're on time.",
|
||||||
|
plural.One, "You are %v minute late.",
|
||||||
|
plural.Other, "You are %v minutes late."))
|
||||||
|
_ = cat.Set(language.Russian, "You are {minute} minute(s) late.",
|
||||||
|
plural.Selectf(1, "",
|
||||||
|
"=1", "Вы опоздали на одну минуту.",
|
||||||
|
"=0", "Вы вовремя.",
|
||||||
|
plural.One, "Вы опоздали на %v минуту.",
|
||||||
|
plural.Few, "Вы опоздали на %v минуты.",
|
||||||
|
plural.Other, "Вы опоздали на %v минут.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
_ = cat.Set(language.Russian, "It costs {cost}.",
|
||||||
|
catalog.String("Это стоит %.2f."))
|
||||||
|
_ = cat.Set(language.English, "It costs {cost}.",
|
||||||
|
catalog.String("It costs %.2f."))
|
||||||
|
|
||||||
|
provider := gotext.NewProvider(gotext.WithCatalog(translation.DefaultDomain, cat))
|
||||||
|
|
||||||
|
trans := translation.New(language.Russian, provider)
|
||||||
|
ctx := context.Background()
|
||||||
|
// context with language
|
||||||
|
heCtx := translation.WithLanguage(ctx, language.Make("he"))
|
||||||
|
|
||||||
|
fmt.Println(trans.Trans(ctx, "Hello World!"))
|
||||||
|
fmt.Println(trans.Trans(ctx, "Hello World!", translation.WithLocale("en")))
|
||||||
|
fmt.Println(trans.Trans(ctx, "Hello World!", translation.WithLocale("sk")))
|
||||||
|
fmt.Println(trans.Trans(heCtx, "Hello World!"))
|
||||||
|
|
||||||
|
fmt.Println(trans.Trans(ctx, "Hello {name}!", translation.WithArgs("Andrey")))
|
||||||
|
|
||||||
|
fmt.Println(trans.Trans(ctx, "You are {minute} minute(s) late.", translation.WithNumber("minute", 1)))
|
||||||
|
fmt.Println(trans.Trans(ctx, "You are {minute} minute(s) late.", translation.WithNumber("minute", 0)))
|
||||||
|
fmt.Println(trans.Trans(ctx, "You are {minute} minute(s) late.", translation.WithNumber("minute", 101)))
|
||||||
|
fmt.Println(trans.Trans(ctx, "You are {minute} minute(s) late.", translation.WithArgs(123456.78)))
|
||||||
|
fmt.Println(trans.Trans(ctx, "You are {minute} minute(s) late.", translation.WithArgs(50)))
|
||||||
|
fmt.Println(trans.Trans(ctx, "You are {minute} minute(s) late.", translation.WithArgs(1), translation.WithLocale("en")))
|
||||||
|
fmt.Println(trans.Trans(ctx, "You are {minute} minute(s) late.", translation.WithArgs(33), translation.WithLocale("en")))
|
||||||
|
|
||||||
|
fmt.Println(trans.Trans(ctx, "There are {bikes} bikes per household.", translation.WithNumber("bikes", 1.2)))
|
||||||
|
fmt.Println(trans.Trans(ctx, "There are {bikes} bikes per household.", translation.WithNumber("bikes", 1.2), translation.WithLocale("en")))
|
||||||
|
|
||||||
|
fmt.Println(trans.Trans(ctx, "It costs {cost}.",
|
||||||
|
translation.WithCurrency("cost", 12.0101, arg.WithCurrencyISO("rub"), arg.WithCurrencyFormat(arg.CurrencyFormatSymbol))))
|
||||||
|
fmt.Println(trans.Trans(ctx, "It costs {cost}.",
|
||||||
|
translation.WithCurrency("cost", 15.0, arg.WithCurrencyISO("HKD"), arg.WithCurrencyFormat(arg.CurrencyFormatSymbol)),
|
||||||
|
translation.WithLocale("en")))
|
||||||
|
// Output:
|
||||||
|
// Привет Мир!
|
||||||
|
// Hello World!
|
||||||
|
// Ahoj Svet!
|
||||||
|
// שלום עולם
|
||||||
|
// Привет Andrey!
|
||||||
|
// Вы опоздали на одну минуту.
|
||||||
|
// Вы вовремя.
|
||||||
|
// Вы опоздали на 101 минуту.
|
||||||
|
// Вы опоздали на 123 456,78 минут.
|
||||||
|
// Вы опоздали на 50 минут.
|
||||||
|
// You are 1 minute late.
|
||||||
|
// You are 33 minutes late.
|
||||||
|
// В каждом доме есть 1,2 велосипеда.
|
||||||
|
// There are 1.2 bikes per household.
|
||||||
|
// Это стоит ₽ 12.0101.
|
||||||
|
// It costs HK$ 15.
|
||||||
|
}
|
||||||
32
translator_test.go
Normal file
32
translator_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package translation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/translation"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestProvider struct{}
|
||||||
|
|
||||||
|
func (tp *TestProvider) Translate(_ context.Context, key string, opt translation.Translate) string {
|
||||||
|
args := make([]interface{}, 0, len(opt.Args)+1)
|
||||||
|
args = append(args, key)
|
||||||
|
args = append(args, opt.ArgValues()...)
|
||||||
|
|
||||||
|
return fmt.Sprint(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTranslator(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
trans := translation.New(language.Russian, &TestProvider{})
|
||||||
|
tr := trans.Trans(ctx, "key", translation.WithArgs("arg1", "arg2"))
|
||||||
|
|
||||||
|
if tr != "keyarg1arg2" {
|
||||||
|
t.Fatalf("expect: keyarg1arg2, got:%s", tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user