Skip to content

Commit 3000e3a

Browse files
authored
Fix/translation (#1460)
1 parent 216786a commit 3000e3a

File tree

3 files changed

+177
-1
lines changed

3 files changed

+177
-1
lines changed

internal/base/handler/lang.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,22 @@ import (
2323
"context"
2424

2525
"github.com/apache/answer/internal/base/constant"
26+
"github.com/gin-gonic/gin"
2627
"github.com/segmentfault/pacman/i18n"
2728
)
2829

2930
// GetLangByCtx get language from header
3031
func GetLangByCtx(ctx context.Context) i18n.Language {
32+
if ginCtx, ok := ctx.(*gin.Context); ok {
33+
acceptLanguage, ok := ginCtx.Get(constant.AcceptLanguageFlag)
34+
if ok {
35+
if acceptLanguage, ok := acceptLanguage.(i18n.Language); ok {
36+
return acceptLanguage
37+
}
38+
return i18n.DefaultLanguage
39+
}
40+
}
41+
3142
acceptLanguage, ok := ctx.Value(constant.AcceptLanguageContextKey).(i18n.Language)
3243
if ok {
3344
return acceptLanguage

internal/base/translator/provider.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"fmt"
2424
"os"
2525
"path/filepath"
26+
"sort"
27+
"strings"
2628

2729
"github.com/google/wire"
2830
myTran "github.com/segmentfault/pacman/contrib/i18n"
@@ -100,6 +102,7 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
100102
// add translator use backend translation
101103
if err = myTran.AddTranslator(content, file.Name()); err != nil {
102104
log.Debugf("add translator failed: %s %s", file.Name(), err)
105+
reportTranslatorFormatError(file.Name(), buf)
103106
continue
104107
}
105108
}
@@ -160,3 +163,165 @@ func TrWithData(lang i18n.Language, key string, templateData any) string {
160163
}
161164
return translation
162165
}
166+
167+
// reportTranslatorFormatError re-parses the YAML file to locate the invalid entry
168+
// when go-i18n fails to add the translator.
169+
func reportTranslatorFormatError(fileName string, content []byte) {
170+
var raw any
171+
if err := yaml.Unmarshal(content, &raw); err != nil {
172+
log.Errorf("parse translator file %s failed when diagnosing format error: %s", fileName, err)
173+
return
174+
}
175+
if err := inspectTranslatorNode(raw, nil, true); err != nil {
176+
log.Errorf("translator file %s invalid: %s", fileName, err)
177+
}
178+
}
179+
180+
func inspectTranslatorNode(node any, path []string, isRoot bool) error {
181+
switch data := node.(type) {
182+
case nil:
183+
if isRoot {
184+
return fmt.Errorf("root value is empty")
185+
}
186+
return fmt.Errorf("%s contains an empty value", formatTranslationPath(path))
187+
case string:
188+
if isRoot {
189+
return fmt.Errorf("root value must be an object but found string")
190+
}
191+
return nil
192+
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
193+
if isRoot {
194+
return fmt.Errorf("root value must be an object but found %T", data)
195+
}
196+
return fmt.Errorf("%s expects a string translation but found %T", formatTranslationPath(path), data)
197+
case map[string]any:
198+
if isMessageMap(data) {
199+
return nil
200+
}
201+
keys := make([]string, 0, len(data))
202+
for key := range data {
203+
keys = append(keys, key)
204+
}
205+
sort.Strings(keys)
206+
for _, key := range keys {
207+
if err := inspectTranslatorNode(data[key], append(path, key), false); err != nil {
208+
return err
209+
}
210+
}
211+
return nil
212+
case map[string]string:
213+
mapped := make(map[string]any, len(data))
214+
for k, v := range data {
215+
mapped[k] = v
216+
}
217+
return inspectTranslatorNode(mapped, path, isRoot)
218+
case map[any]any:
219+
if isMessageMap(data) {
220+
return nil
221+
}
222+
type kv struct {
223+
key string
224+
val any
225+
}
226+
items := make([]kv, 0, len(data))
227+
for key, val := range data {
228+
strKey, ok := key.(string)
229+
if !ok {
230+
return fmt.Errorf("%s uses a non-string key %#v", formatTranslationPath(path), key)
231+
}
232+
items = append(items, kv{key: strKey, val: val})
233+
}
234+
sort.Slice(items, func(i, j int) bool {
235+
return items[i].key < items[j].key
236+
})
237+
for _, item := range items {
238+
if err := inspectTranslatorNode(item.val, append(path, item.key), false); err != nil {
239+
return err
240+
}
241+
}
242+
return nil
243+
case []any:
244+
for idx, child := range data {
245+
nextPath := append(path, fmt.Sprintf("[%d]", idx))
246+
if err := inspectTranslatorNode(child, nextPath, false); err != nil {
247+
return err
248+
}
249+
}
250+
return nil
251+
case []map[string]any:
252+
for idx, child := range data {
253+
nextPath := append(path, fmt.Sprintf("[%d]", idx))
254+
if err := inspectTranslatorNode(child, nextPath, false); err != nil {
255+
return err
256+
}
257+
}
258+
return nil
259+
default:
260+
if isRoot {
261+
return fmt.Errorf("root value must be an object but found %T", data)
262+
}
263+
return fmt.Errorf("%s contains unsupported value type %T", formatTranslationPath(path), data)
264+
}
265+
}
266+
267+
var translatorReservedKeys = []string{
268+
"id", "description", "hash", "leftdelim", "rightdelim",
269+
"zero", "one", "two", "few", "many", "other",
270+
}
271+
272+
func isMessageMap(data any) bool {
273+
switch v := data.(type) {
274+
case map[string]any:
275+
for _, key := range translatorReservedKeys {
276+
val, ok := v[key]
277+
if !ok {
278+
continue
279+
}
280+
if _, ok := val.(string); ok {
281+
return true
282+
}
283+
}
284+
case map[string]string:
285+
for _, key := range translatorReservedKeys {
286+
val, ok := v[key]
287+
if !ok {
288+
continue
289+
}
290+
if val != "" {
291+
return true
292+
}
293+
}
294+
case map[any]any:
295+
for _, key := range translatorReservedKeys {
296+
val, ok := v[key]
297+
if !ok {
298+
continue
299+
}
300+
if _, ok := val.(string); ok {
301+
return true
302+
}
303+
}
304+
}
305+
return false
306+
}
307+
308+
func formatTranslationPath(path []string) string {
309+
if len(path) == 0 {
310+
return "root"
311+
}
312+
var b strings.Builder
313+
for _, part := range path {
314+
if part == "" {
315+
continue
316+
}
317+
if part[0] == '[' {
318+
b.WriteString(part)
319+
continue
320+
}
321+
if b.Len() > 0 {
322+
b.WriteByte('.')
323+
}
324+
b.WriteString(part)
325+
}
326+
return b.String()
327+
}

ui/src/pages/SideNavLayout/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const Index: FC = () => {
3838
<Outlet />
3939
</div>
4040
</div>
41-
<div className="d-flex justify-content-center">
41+
<div className="d-flex justify-content-center px-0 px-md-4">
4242
<div className="main-mx-with">
4343
<Footer />
4444
</div>

0 commit comments

Comments
 (0)