This is an automated email from the ASF dual-hosted git repository.

linkinstar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/answer.git


The following commit(s) were added to refs/heads/main by this push:
     new 3000e3ae Fix/translation (#1460)
3000e3ae is described below

commit 3000e3aec0814b710d90bc853115ac36078cfe23
Author: LinkinStars <[email protected]>
AuthorDate: Mon Dec 15 15:48:45 2025 +0800

    Fix/translation (#1460)
---
 internal/base/handler/lang.go        |  11 +++
 internal/base/translator/provider.go | 165 +++++++++++++++++++++++++++++++++++
 ui/src/pages/SideNavLayout/index.tsx |   2 +-
 3 files changed, 177 insertions(+), 1 deletion(-)

diff --git a/internal/base/handler/lang.go b/internal/base/handler/lang.go
index 8886f063..202449e9 100644
--- a/internal/base/handler/lang.go
+++ b/internal/base/handler/lang.go
@@ -23,11 +23,22 @@ import (
        "context"
 
        "github.com/apache/answer/internal/base/constant"
+       "github.com/gin-gonic/gin"
        "github.com/segmentfault/pacman/i18n"
 )
 
 // GetLangByCtx get language from header
 func GetLangByCtx(ctx context.Context) i18n.Language {
+       if ginCtx, ok := ctx.(*gin.Context); ok {
+               acceptLanguage, ok := ginCtx.Get(constant.AcceptLanguageFlag)
+               if ok {
+                       if acceptLanguage, ok := 
acceptLanguage.(i18n.Language); ok {
+                               return acceptLanguage
+                       }
+                       return i18n.DefaultLanguage
+               }
+       }
+
        acceptLanguage, ok := 
ctx.Value(constant.AcceptLanguageContextKey).(i18n.Language)
        if ok {
                return acceptLanguage
diff --git a/internal/base/translator/provider.go 
b/internal/base/translator/provider.go
index 47212e84..1a465b1e 100644
--- a/internal/base/translator/provider.go
+++ b/internal/base/translator/provider.go
@@ -23,6 +23,8 @@ import (
        "fmt"
        "os"
        "path/filepath"
+       "sort"
+       "strings"
 
        "github.com/google/wire"
        myTran "github.com/segmentfault/pacman/contrib/i18n"
@@ -100,6 +102,7 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) 
{
                // add translator use backend translation
                if err = myTran.AddTranslator(content, file.Name()); err != nil 
{
                        log.Debugf("add translator failed: %s %s", file.Name(), 
err)
+                       reportTranslatorFormatError(file.Name(), buf)
                        continue
                }
        }
@@ -160,3 +163,165 @@ func TrWithData(lang i18n.Language, key string, 
templateData any) string {
        }
        return translation
 }
+
+// reportTranslatorFormatError re-parses the YAML file to locate the invalid 
entry
+// when go-i18n fails to add the translator.
+func reportTranslatorFormatError(fileName string, content []byte) {
+       var raw any
+       if err := yaml.Unmarshal(content, &raw); err != nil {
+               log.Errorf("parse translator file %s failed when diagnosing 
format error: %s", fileName, err)
+               return
+       }
+       if err := inspectTranslatorNode(raw, nil, true); err != nil {
+               log.Errorf("translator file %s invalid: %s", fileName, err)
+       }
+}
+
+func inspectTranslatorNode(node any, path []string, isRoot bool) error {
+       switch data := node.(type) {
+       case nil:
+               if isRoot {
+                       return fmt.Errorf("root value is empty")
+               }
+               return fmt.Errorf("%s contains an empty value", 
formatTranslationPath(path))
+       case string:
+               if isRoot {
+                       return fmt.Errorf("root value must be an object but 
found string")
+               }
+               return nil
+       case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, 
uint64, float32, float64:
+               if isRoot {
+                       return fmt.Errorf("root value must be an object but 
found %T", data)
+               }
+               return fmt.Errorf("%s expects a string translation but found 
%T", formatTranslationPath(path), data)
+       case map[string]any:
+               if isMessageMap(data) {
+                       return nil
+               }
+               keys := make([]string, 0, len(data))
+               for key := range data {
+                       keys = append(keys, key)
+               }
+               sort.Strings(keys)
+               for _, key := range keys {
+                       if err := inspectTranslatorNode(data[key], append(path, 
key), false); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       case map[string]string:
+               mapped := make(map[string]any, len(data))
+               for k, v := range data {
+                       mapped[k] = v
+               }
+               return inspectTranslatorNode(mapped, path, isRoot)
+       case map[any]any:
+               if isMessageMap(data) {
+                       return nil
+               }
+               type kv struct {
+                       key string
+                       val any
+               }
+               items := make([]kv, 0, len(data))
+               for key, val := range data {
+                       strKey, ok := key.(string)
+                       if !ok {
+                               return fmt.Errorf("%s uses a non-string key 
%#v", formatTranslationPath(path), key)
+                       }
+                       items = append(items, kv{key: strKey, val: val})
+               }
+               sort.Slice(items, func(i, j int) bool {
+                       return items[i].key < items[j].key
+               })
+               for _, item := range items {
+                       if err := inspectTranslatorNode(item.val, append(path, 
item.key), false); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       case []any:
+               for idx, child := range data {
+                       nextPath := append(path, fmt.Sprintf("[%d]", idx))
+                       if err := inspectTranslatorNode(child, nextPath, 
false); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       case []map[string]any:
+               for idx, child := range data {
+                       nextPath := append(path, fmt.Sprintf("[%d]", idx))
+                       if err := inspectTranslatorNode(child, nextPath, 
false); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       default:
+               if isRoot {
+                       return fmt.Errorf("root value must be an object but 
found %T", data)
+               }
+               return fmt.Errorf("%s contains unsupported value type %T", 
formatTranslationPath(path), data)
+       }
+}
+
+var translatorReservedKeys = []string{
+       "id", "description", "hash", "leftdelim", "rightdelim",
+       "zero", "one", "two", "few", "many", "other",
+}
+
+func isMessageMap(data any) bool {
+       switch v := data.(type) {
+       case map[string]any:
+               for _, key := range translatorReservedKeys {
+                       val, ok := v[key]
+                       if !ok {
+                               continue
+                       }
+                       if _, ok := val.(string); ok {
+                               return true
+                       }
+               }
+       case map[string]string:
+               for _, key := range translatorReservedKeys {
+                       val, ok := v[key]
+                       if !ok {
+                               continue
+                       }
+                       if val != "" {
+                               return true
+                       }
+               }
+       case map[any]any:
+               for _, key := range translatorReservedKeys {
+                       val, ok := v[key]
+                       if !ok {
+                               continue
+                       }
+                       if _, ok := val.(string); ok {
+                               return true
+                       }
+               }
+       }
+       return false
+}
+
+func formatTranslationPath(path []string) string {
+       if len(path) == 0 {
+               return "root"
+       }
+       var b strings.Builder
+       for _, part := range path {
+               if part == "" {
+                       continue
+               }
+               if part[0] == '[' {
+                       b.WriteString(part)
+                       continue
+               }
+               if b.Len() > 0 {
+                       b.WriteByte('.')
+               }
+               b.WriteString(part)
+       }
+       return b.String()
+}
diff --git a/ui/src/pages/SideNavLayout/index.tsx 
b/ui/src/pages/SideNavLayout/index.tsx
index 907b9b28..b9bc38b9 100644
--- a/ui/src/pages/SideNavLayout/index.tsx
+++ b/ui/src/pages/SideNavLayout/index.tsx
@@ -38,7 +38,7 @@ const Index: FC = () => {
             <Outlet />
           </div>
         </div>
-        <div className="d-flex justify-content-center">
+        <div className="d-flex justify-content-center px-0 px-md-4">
           <div className="main-mx-with">
             <Footer />
           </div>

Reply via email to