Skip to content

Commit

Permalink
Vars type added
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaysoro committed Sep 6, 2022
1 parent f9d870d commit 1187ce1
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 49 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: GoReleaser

on:
- push
- pull_request
push:
tags:
- '*'

jobs:
goreleaser:
Expand Down
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# discordgo-i18n ![Build status](https://github.com/kaysoro/discordgo-i18n/workflows/Build/badge.svg) [![Report card](https://goreportcard.com/badge/github.com/kaysoro/discordgo-i18n)](https://goreportcard.com/report/github.com/kaysoro/discordgo-i18n) [![codecov](https://codecov.io/gh/kaysoro/discordgo-i18n/branch/main/graph/badge.svg)](https://codecov.io/gh/kaysoro/discordgo-i18n) [![Sourcegraph](https://sourcegraph.com/github.com/kaysoro/discordgo-i18n/-/badge.svg)](https://sourcegraph.com/github.com/kaysoro/discordgo-i18n?badge)
# discordgo-i18n
[![GoDoc](https://godoc.org/github.com/kaysoro/discordgo-i18n?status.svg)](https://godoc.org/github.com/kaysoro/discordgo-i18n)
![Build status](https://github.com/kaysoro/discordgo-i18n/workflows/Build/badge.svg)
[![Report card](https://goreportcard.com/badge/github.com/kaysoro/discordgo-i18n)](https://goreportcard.com/report/github.com/kaysoro/discordgo-i18n)
[![codecov](https://codecov.io/gh/kaysoro/discordgo-i18n/branch/main/graph/badge.svg)](https://codecov.io/gh/kaysoro/discordgo-i18n)
[![Sourcegraph](https://sourcegraph.com/github.com/kaysoro/discordgo-i18n/-/badge.svg)](https://sourcegraph.com/github.com/kaysoro/discordgo-i18n?badge)

discordgo-i18n is a simple and lightweight Go package that helps you translate Go programs into [languages supported by Discord](https://discord.com/developers/docs/reference#locales).

- Built to ease usage of [bwmarrin/discordgo](https://github.com/bwmarrin/discordgo)
- Less verbose than [go-i18n](https://raw.githubusercontent.com/nicksnyder/go-i18n)
- Less verbose than [go-i18n](https://github.com/nicksnyder/go-i18n)
- Supports multiple strings per key to make your bot "more alive"
- Supports strings with named variables using [text/template](http://golang.org/pkg/text/template/) syntax
- Supports message files of JSON format
Expand Down Expand Up @@ -44,6 +49,7 @@ The bundle format must respect the schema below; note [text/template](http://gol
"hello_world": ["Hello world!"],
"hello_anyone": ["Hello {{ .anyone }}!"],
"bye": ["See you", "Bye!"],
"image": ["https://media2.giphy.com/media/Ju7l5y9osyymQ/giphy.gif"]
}
```

Expand All @@ -64,7 +70,7 @@ helloAnyone := i18n.Get(discordgo.EnglishUS, "hello_anyone")
fmt.Println(helloAnyone)
// Prints "Hello {{ .anyone }}!"

helloAnyone = i18n.Get(discordgo.EnglishUS, "hello_anyone", map[string]interface{}{"anyone": "Nick"})
helloAnyone = i18n.Get(discordgo.EnglishUS, "hello_anyone", i18n.Vars{"anyone": "Nick"})
fmt.Println(helloAnyone)
// Prints "Hello Nick!"

Expand All @@ -77,6 +83,28 @@ fmt.Println(keyDoesNotExist)
// Prints "key_does_not_exist"
```

Here an example of how it can work with interactions.

```go
func HelloWorld(s *discordgo.Session, i *discordgo.InteractionCreate) {

err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: i18n.Get(i.Locale, "hello_world"),
Description: i18n.Get(i.Locale, "hello_anyone", i18n.Vars{"anyone": i.Member.Nick}),
Image: &discordgo.MessageEmbedImage{URL: i18n.Get(i.Locale, "image")},
},
},
},
})

// ...
}
```

## Contributing

Contributions are very welcomed, however please follow the below guidelines.
Expand Down
24 changes: 17 additions & 7 deletions discordgoi18n.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,38 @@ import (
"github.com/bwmarrin/discordgo"
)

var translator Translator
var instance translator

func init() {
translator = New()
instance = new()
}

// SetDefaults sets the locale used as a fallback.
// Not thread-safe; designed to be called during initialization.
func SetDefault(language discordgo.Locale) {
translator.SetDefault(language)
instance.SetDefault(language)
}

// LoadBundle loads a translation file corresponding to a specified locale.
// Not thread-safe; designed to be called during initialization.
func LoadBundle(language discordgo.Locale, file string) error {
return translator.LoadBundle(language, file)
return instance.LoadBundle(language, file)
}

func Get(language discordgo.Locale, key string, values ...map[string]interface{}) string {
args := make(map[string]interface{})
// Get gets a translation corresponding to a locale and a key.
// Optional Vars parameter is used to inject variables in the translation.
// When a key does not match any translations in the desired locale,
// the default locale is used instead. If the situation persists with the fallback,
// key is returned. If more than one translation is available for dedicated key,
// it is picked randomly. Thread-safe.
func Get(language discordgo.Locale, key string, values ...Vars) string {
args := make(Vars)

for _, variables := range values {
for variable, value := range variables {
args[variable] = value
}
}

return translator.Get(language, key, args)
return instance.Get(language, key, args)
}
16 changes: 8 additions & 8 deletions discordgoi18n_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (

func TestFacade(t *testing.T) {
var expectedFile, expectedKey = "File", "Key"
var expectedValues map[string]interface{}
var expectedValues Vars
var called bool

mock := NewMock()
mock := newMock()
mock.SetDefaultFunc = func(locale discordgo.Locale) {
assert.Equal(t, discordgo.Italian, locale)
called = true
Expand All @@ -23,15 +23,15 @@ func TestFacade(t *testing.T) {
called = true
return nil
}
mock.GetFunc = func(locale discordgo.Locale, key string, values map[string]interface{}) string {
mock.GetFunc = func(locale discordgo.Locale, key string, values Vars) string {
assert.Equal(t, discordgo.ChineseCN, locale)
assert.Equal(t, expectedValues, values)
assert.Equal(t, expectedKey, key)
called = true
return ""
}

translator = mock
instance = mock

called = false
SetDefault(discordgo.Italian)
Expand All @@ -42,22 +42,22 @@ func TestFacade(t *testing.T) {
assert.True(t, called)

called = false
expectedValues = make(map[string]interface{})
expectedValues = make(Vars)
Get(discordgo.ChineseCN, expectedKey)
assert.True(t, called)

called = false
expectedValues = map[string]interface{}{
expectedValues = Vars{
"Hi": "There",
}
Get(discordgo.ChineseCN, expectedKey, expectedValues)
assert.True(t, called)

called = false
expectedValues = map[string]interface{}{
expectedValues = Vars{
"Hi": "There",
"Bye": "See u",
}
Get(discordgo.ChineseCN, expectedKey, map[string]interface{}{"Hi": "There"}, map[string]interface{}{"Bye": "See u"})
Get(discordgo.ChineseCN, expectedKey, Vars{"Hi": "There"}, Vars{"Bye": "See u"})
assert.True(t, called)
}
9 changes: 3 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@ go 1.17
require (
github.com/bwmarrin/discordgo v0.26.1
github.com/rs/zerolog v1.28.0
github.com/stretchr/testify v1.8.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/gorilla/websocket v1.4.2 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/stretchr/testify v1.8.0
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
10 changes: 5 additions & 5 deletions mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"github.com/rs/zerolog/log"
)

func NewMock() *TranslatorMock {
return &TranslatorMock{}
func newMock() *translatorMock {
return &translatorMock{}
}

func (mock *TranslatorMock) SetDefault(locale discordgo.Locale) {
func (mock *translatorMock) SetDefault(locale discordgo.Locale) {
if mock.SetDefaultFunc != nil {
mock.SetDefaultFunc(locale)
return
Expand All @@ -18,7 +18,7 @@ func (mock *TranslatorMock) SetDefault(locale discordgo.Locale) {
log.Warn().Msgf("SetDefault not mocked")
}

func (mock *TranslatorMock) LoadBundle(locale discordgo.Locale, file string) error {
func (mock *translatorMock) LoadBundle(locale discordgo.Locale, file string) error {
if mock.LoadBundleFunc != nil {
return mock.LoadBundleFunc(locale, file)
}
Expand All @@ -27,7 +27,7 @@ func (mock *TranslatorMock) LoadBundle(locale discordgo.Locale, file string) err
return nil
}

func (mock *TranslatorMock) Get(locale discordgo.Locale, key string, values map[string]interface{}) string {
func (mock *translatorMock) Get(locale discordgo.Locale, key string, values Vars) string {
if mock.GetFunc != nil {
return mock.GetFunc(locale, key, values)
}
Expand Down
4 changes: 2 additions & 2 deletions mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func TestMock(t *testing.T) {

// Must not panic
mock := NewMock()
mock := newMock()
mock.SetDefault(discordgo.ChineseCN)
assert.NoError(t, mock.LoadBundle(discordgo.SpanishES, ""))
assert.Empty(t, mock.Get(discordgo.Croatian, "", nil))
Expand All @@ -23,7 +23,7 @@ func TestMock(t *testing.T) {
called = true
return nil
}
mock.GetFunc = func(locale discordgo.Locale, key string, values map[string]interface{}) string {
mock.GetFunc = func(locale discordgo.Locale, key string, values Vars) string {
called = true
return ""
}
Expand Down
12 changes: 6 additions & 6 deletions translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ const (
executionPolicy = "missingkey=error"
)

func New() *TranslatorImpl {
return &TranslatorImpl{
func new() *translatorImpl {
return &translatorImpl{
defaultLocale: defaultLocale,
translations: make(map[discordgo.Locale]bundle),
loadedBundles: make(map[string]bundle),
}
}

func (translator *TranslatorImpl) SetDefault(language discordgo.Locale) {
func (translator *translatorImpl) SetDefault(language discordgo.Locale) {
translator.defaultLocale = language
}

func (translator *TranslatorImpl) LoadBundle(locale discordgo.Locale, path string) error {
func (translator *translatorImpl) LoadBundle(locale discordgo.Locale, path string) error {
loadedBundle, found := translator.loadedBundles[path]
if !found {

Expand All @@ -51,14 +51,14 @@ func (translator *TranslatorImpl) LoadBundle(locale discordgo.Locale, path strin
translator.translations[locale] = newBundle

} else {
log.Debug().Msgf("Bundle '%s' already loaded, content now linked to locale %s too", path, locale)
log.Debug().Msgf("Bundle '%s' loaded with '%s' content (already loaded for other locales)", locale, path)
translator.translations[locale] = loadedBundle
}

return nil
}

func (translator *TranslatorImpl) Get(locale discordgo.Locale, key string, variables map[string]interface{}) string {
func (translator *translatorImpl) Get(locale discordgo.Locale, key string, variables Vars) string {
bundles, found := translator.translations[locale]
if !found {
if locale != translator.defaultLocale {
Expand Down
10 changes: 5 additions & 5 deletions translator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ const (
)

var (
translatorTest *TranslatorImpl
translatorTest *translatorImpl
)

func setUp() {
translatorTest = New()
translatorTest = new()
if err := os.WriteFile(translatornominalCase1, []byte(content1), os.ModePerm); err != nil {
log.Fatal().Err(err).Msgf("'%s' could not be created, test stopped", translatornominalCase1)
}
Expand Down Expand Up @@ -148,14 +148,14 @@ func TestGet(t *testing.T) {

// Nominal case, Get existing key from loaded bundle
assert.Equal(t, "this is a {{ .Test }}", translatorTest.Get(discordgo.Dutch, "hi", nil))
assert.Equal(t, "this is a test :)", translatorTest.Get(discordgo.Dutch, "hi", map[string]interface{}{"Test": "test :)"}))
assert.Equal(t, "this is a test :)", translatorTest.Get(discordgo.Dutch, "hi", Vars{"Test": "test :)"}))

// Nominal case, Get key not present in loaded bundle but available in default
assert.Equal(t, "see you", translatorTest.Get(discordgo.Dutch, "bye", nil))

// Bad case, value is not well structured to be parsed
assert.Equal(t, "{{if $foo}}{{end}}", translatorTest.Get(discordgo.Dutch, "parse", map[string]interface{}{}))
assert.Equal(t, "{{if $foo}}{{end}}", translatorTest.Get(discordgo.Dutch, "parse", Vars{}))

// Bad case, value is well structured but cannot inject value
assert.Equal(t, "this is a {{ .Test }}", translatorTest.Get(discordgo.Dutch, "hi", map[string]interface{}{}))
assert.Equal(t, "this is a {{ .Test }}", translatorTest.Get(discordgo.Dutch, "hi", Vars{}))
}
14 changes: 9 additions & 5 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@ import (
"github.com/bwmarrin/discordgo"
)

type Translator interface {
// Vars is the collection used to inject variables during translation.
// This type only exists to be less verbose.
type Vars map[string]interface{}

type translator interface {
SetDefault(locale discordgo.Locale)
LoadBundle(locale discordgo.Locale, file string) error
Get(locale discordgo.Locale, key string, values map[string]interface{}) string
Get(locale discordgo.Locale, key string, values Vars) string
}

type TranslatorImpl struct {
type translatorImpl struct {
defaultLocale discordgo.Locale
translations map[discordgo.Locale]bundle
loadedBundles map[string]bundle
}

type TranslatorMock struct {
type translatorMock struct {
SetDefaultFunc func(locale discordgo.Locale)
LoadBundleFunc func(locale discordgo.Locale, file string) error
GetFunc func(locale discordgo.Locale, key string, values map[string]interface{}) string
GetFunc func(locale discordgo.Locale, key string, values Vars) string
}

type bundle map[string][]string

0 comments on commit 1187ce1

Please sign in to comment.