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

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


The following commit(s) were added to refs/heads/dev by this push:
     new fbb877ab fix(translator): enhance error reporting for invalid 
translator YAML files
fbb877ab is described below

commit fbb877ab11760b69ee17cb0d00822fead64e60c9
Author: LinkinStars <[email protected]>
AuthorDate: Thu Dec 11 14:23:17 2025 +0800

    fix(translator): enhance error reporting for invalid translator YAML files
---
 internal/base/translator/provider.go | 165 +++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)

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()
+}

Reply via email to