/
translation_service.go
174 lines (152 loc) · 5.01 KB
/
translation_service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package infrastructure
import (
"bytes"
"errors"
"fmt"
"os"
"sync"
"text/template"
"time"
"flamingo.me/flamingo/v3/core/locale/domain"
"flamingo.me/flamingo/v3/framework/config"
"flamingo.me/flamingo/v3/framework/flamingo"
"github.com/nicksnyder/go-i18n/i18n/bundle"
)
// TranslationService is the default TranslationService implementation
type TranslationService struct {
mutex sync.Mutex
lastReload time.Time
translationFiles []string
logger flamingo.Logger
devmode bool
i18bundle *bundle.Bundle
}
// check if translationService implements its interface
var _ domain.TranslationService = (*TranslationService)(nil)
// Inject dependencies
func (ts *TranslationService) Inject(
logger flamingo.Logger,
config *struct {
DevMode bool `inject:"config:flamingo.debug.mode"`
TranslationFile string `inject:"config:core.locale.translationFile,optional"`
TranslationFiles config.Slice `inject:"config:core.locale.translationFiles,optional"`
},
) {
ts.logger = logger.WithField(flamingo.LogKeyModule, "locale").WithField(flamingo.LogKeyCategory, "locale.translationService")
if config != nil {
err := config.TranslationFiles.MapInto(&ts.translationFiles)
if err != nil {
ts.logger.Warn(fmt.Sprintf("could not map core.locale.translationFiles: %v", err))
}
if config.TranslationFile != "" {
ts.translationFiles = append(ts.translationFiles, config.TranslationFile)
}
ts.devmode = config.DevMode
}
ts.i18bundle = bundle.New()
ts.mutex.Lock()
ts.loadFiles()
ts.mutex.Unlock()
}
// TranslateLabel returns the result for translating a Label
func (ts *TranslationService) TranslateLabel(label domain.Label) string {
ts.reloadFilesIfNecessary()
translatedString, err := ts.translateWithLib(label.GetLocaleCode(), label.GetKey(), label.GetCount(), label.GetTranslationArguments())
//while there is an error check fallBacks
for _, fallbackLocale := range label.GetFallbackLocaleCodes() {
if err != nil {
translatedString, err = ts.translateWithLib(fallbackLocale, label.GetKey(), label.GetCount(), label.GetTranslationArguments())
}
}
if err != nil {
//default to key (=untranslated) if still an error
translatedString = label.GetKey()
}
//Fallback if label was not translated
if translatedString == label.GetKey() && label.GetDefaultLabel() != "" {
return ts.parseDefaultLabel(label.GetDefaultLabel(), label.GetKey(), label.GetTranslationArguments())
}
return translatedString
}
// Translate returns the result for translating a key, with a default label for a given locale code
func (ts *TranslationService) Translate(key string, defaultLabel string, localeCode string, count int, translationArguments map[string]interface{}) string {
ts.reloadFilesIfNecessary()
label, err := ts.translateWithLib(localeCode, key, count, translationArguments)
if err != nil {
//default to key (=untranslated) on error
label = key
}
//Fallback if label was not translated
if label == key && defaultLabel != "" {
return ts.parseDefaultLabel(defaultLabel, key, translationArguments)
}
return label
}
// AllTranslationKeys returns all keys for a given locale code
func (ts *TranslationService) AllTranslationKeys(localeCode string) []string {
ts.reloadFilesIfNecessary()
return ts.i18bundle.LanguageTranslationIDs(localeCode)
}
func (ts *TranslationService) parseDefaultLabel(defaultLabel string, key string, translationArguments map[string]interface{}) string {
if translationArguments == nil {
translationArguments = make(map[string]interface{})
}
tmpl, err := template.New(key).Parse(defaultLabel)
if err != nil {
return defaultLabel
}
var doc bytes.Buffer
err = tmpl.Execute(&doc, translationArguments)
if err != nil {
return defaultLabel
}
return doc.String()
}
func (ts *TranslationService) translateWithLib(localeCode string, key string, count int, translationArguments map[string]interface{}) (string, error) {
if translationArguments == nil {
translationArguments = make(map[string]interface{})
}
if count < 1 {
count = 1
}
T, err := ts.i18bundle.Tfunc(localeCode)
if err != nil {
ts.logger.Info("Error - locale.translationservice", err)
return "", err
}
label := T(key, count, translationArguments)
if key == label {
return label, errors.New("label not found")
}
return label, nil
}
func (ts *TranslationService) reloadFilesIfNecessary() {
if !ts.devmode {
return
}
var lastFileChange time.Time
for _, fileName := range ts.translationFiles {
stat, err := os.Stat(fileName)
if err != nil {
continue
}
if stat.ModTime().After(lastFileChange) {
lastFileChange = stat.ModTime()
}
}
ts.mutex.Lock()
if lastFileChange.After(ts.lastReload) {
ts.loadFiles()
}
ts.mutex.Unlock()
}
// loadFiles must only be called when mutex is locked
func (ts *TranslationService) loadFiles() {
for _, fileName := range ts.translationFiles {
err := ts.i18bundle.LoadTranslationFile(fileName)
if err != nil {
ts.logger.Warn(fmt.Sprintf("loading of translationfile %s failed: %s", fileName, err))
}
}
ts.lastReload = time.Now()
}