Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions internal/base/handler/lang.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
165 changes: 165 additions & 0 deletions internal/base/translator/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"

"github.com/google/wire"
myTran "github.com/segmentfault/pacman/contrib/i18n"
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (ur *userExternalLoginRepo) GetByUserID(ctx context.Context, provider, user
func (ur *userExternalLoginRepo) GetUserExternalLoginList(ctx context.Context, userID string) (
resp []*entity.UserExternalLogin, err error) {
resp = make([]*entity.UserExternalLogin, 0)
err = ur.data.DB.Context(ctx).Where("user_id = ?", userID).Find(&resp)
err = ur.data.DB.Context(ctx).Where("user_id = ?", userID).OrderBy("updated_at DESC").Find(&resp)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
Expand Down
15 changes: 9 additions & 6 deletions internal/service/notification/new_question_notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,17 @@ func (ns *ExternalNotificationService) syncNewQuestionNotificationToPlugin(ctx c
}
}

userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, subscriberUserID)
externalLogins, err := ns.userExternalLoginRepo.GetUserExternalLoginList(ctx, subscriberUserID)
if err != nil {
log.Errorf("get user external login info failed: %v", err)
return nil
}
if exist {
newMsg.ReceiverExternalID = userInfo.ExternalID
log.Errorf("get user external login list failed for user %s: %v", subscriberUserID, err)
} else if len(externalLogins) > 0 {
newMsg.ReceiverExternalID = externalLogins[0].ExternalID
if len(externalLogins) > 1 {
log.Debugf("user %s has %d SSO logins, using most recent: provider=%s",
subscriberUserID, len(externalLogins), externalLogins[0].Provider)
}
}

fn.Notify(newMsg)
}
return nil
Expand Down
15 changes: 7 additions & 8 deletions internal/service/notification_common/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,15 +423,14 @@ func (ns *NotificationCommon) syncNotificationToPlugin(ctx context.Context, objI
}
}

externalLogins, err := ns.userExternalLoginRepo.GetUserExternalLoginList(ctx, msg.ReceiverUserID)
if err != nil {
log.Errorf("get user external login list failed for user %s: %v", msg.ReceiverUserID, err)
} else if len(externalLogins) > 0 {
pluginNotificationMsg.ReceiverExternalID = externalLogins[0].ExternalID
}

_ = plugin.CallNotification(func(fn plugin.Notification) error {
userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, msg.ReceiverUserID)
if err != nil {
log.Errorf("get user external login info failed: %v", err)
return nil
}
if exist {
pluginNotificationMsg.ReceiverExternalID = userInfo.ExternalID
}
fn.Notify(pluginNotificationMsg)
return nil
})
Expand Down
2 changes: 1 addition & 1 deletion ui/src/pages/SideNavLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down