Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
666ab70
feat(ui): add min values for inputs and context-based keyboards for i…
Dinesht04 Nov 12, 2025
157dbfd
feat(ui): add types, logic and defaults for min, max value of input c…
Dinesht04 Nov 13, 2025
54e602c
fix(ui): refactor number input props (min,max)
Dinesht04 Nov 19, 2025
9a7eb0f
fix(service): set default language to "en_US" if invalid language is …
LinkinStars Nov 20, 2025
09b6b34
fix(comment): decode CommentID using DeShortID for consistency
LinkinStars Nov 20, 2025
a15dd41
refactor(internal): compile regex once while clearing text
ferhatelmas Nov 22, 2025
ce053cc
fix: multi byte run boundary for cut long title
ferhatelmas Nov 24, 2025
0777291
fix(ui): null pointer access if get branding fails
ferhatelmas Nov 26, 2025
bc629db
chore(deps): bump mockgen to 0.6.0 for go1.25 support
ferhatelmas Nov 27, 2025
fc2a1d8
fix(answer): update QuestionID handling in answer update process
LinkinStars Nov 28, 2025
f723d12
feat: add golangci-lint into lint target
ferhatelmas Nov 28, 2025
9540ef6
refactor(goimports): add goimports to golangci-lint configuration #1432
LinkinStars Dec 1, 2025
5e705a1
refactor(lint): improve error handling and code consistency across mu…
LinkinStars Dec 1, 2025
670aa32
refactor(lint): add new linters and fix their issues
ferhatelmas Dec 1, 2025
6660cdf
fix: add missing revision data for default content (fixes #1436)
oxkrypton Dec 2, 2025
740ac61
fix: get right lang
liruohrh Dec 1, 2025
8e395d4
Polish translation
kinjelom Nov 22, 2025
48b1de8
Update pt_BR.yaml
joaoback Sep 13, 2025
57ba299
chore: turkish translation improved (#1454)
aburakt Dec 11, 2025
d468e2b
feat: add env for glob load template files by gin debug render
liruohrh Dec 6, 2025
fbb877a
fix(translator): enhance error reporting for invalid translator YAML …
LinkinStars Dec 11, 2025
3ddec99
Merge remote-tracking branch 'origin/main' into dev
LinkinStars Dec 11, 2025
f88ff2e
fix: footer width should be aligned with main content
shuashuai Dec 12, 2025
e35b955
fix(lang): enhance language retrieval from gin context
LinkinStars Dec 15, 2025
c6b4ca4
Merge remote-tracking branch 'origin/fix/lang' into test
LinkinStars Dec 15, 2025
f43f22b
fix(lang): correct translations in Polish and Turkish language files
LinkinStars Dec 15, 2025
5940877
fix(lang): correct translations in Polish and Turkish language files
LinkinStars Dec 15, 2025
7098b79
Merge remote-tracking branch 'origin/dev' into test
LinkinStars Dec 15, 2025
d4b5c06
Merge branch 'fix/language' into fix/translation
LinkinStars Dec 15, 2025
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()
}
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