fix(langpack): sync WebK catalog alias support
This commit is contained in:
parent
9ef746d45e
commit
a785ae7491
7 changed files with 313 additions and 8 deletions
|
|
@ -2,11 +2,13 @@ package langpack
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
|
|
@ -205,8 +207,8 @@ func TestSeedDirectoryReconcilesManifest(t *testing.T) {
|
|||
t.Fatalf("reconcile removed file = %d, %v", seeded, err)
|
||||
}
|
||||
languages, err = service.ListLanguages(ctx, "tdesktop")
|
||||
if err != nil || len(languages) != 0 {
|
||||
t.Fatalf("languages after removal = %+v, err %v", languages, err)
|
||||
if !errors.Is(err, domain.ErrLangPackInvalid) || len(languages) != 0 {
|
||||
t.Fatalf("languages after removal = %+v, err %v, want ErrLangPackInvalid", languages, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ func (s *Service) GetDifference(ctx context.Context, langPack, langCode string,
|
|||
if s == nil || s.packs == nil {
|
||||
return domain.LangPack{LangPack: packName, LangCode: code, FromVersion: fromVersion}, nil
|
||||
}
|
||||
if err := s.validateLanguage(ctx, packName, code); err != nil {
|
||||
return domain.LangPack{}, err
|
||||
}
|
||||
var (
|
||||
pack domain.LangPack
|
||||
err error
|
||||
|
|
@ -96,6 +99,9 @@ func (s *Service) GetStrings(ctx context.Context, langPack, langCode string, key
|
|||
if s == nil || s.packs == nil {
|
||||
return domain.LangPack{LangPack: packName, LangCode: code}, nil
|
||||
}
|
||||
if err := s.validateLanguage(ctx, packName, code); err != nil {
|
||||
return domain.LangPack{}, err
|
||||
}
|
||||
pack, err := s.effectivePack(ctx, packName, code)
|
||||
if err != nil {
|
||||
return domain.LangPack{}, err
|
||||
|
|
@ -123,7 +129,14 @@ func (s *Service) ListLanguages(ctx context.Context, langPack string) ([]domain.
|
|||
if s == nil || s.packs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return s.cachedLanguages(ctx, packName)
|
||||
languages, err := s.cachedLanguages(ctx, packName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(languages) == 0 {
|
||||
return nil, domain.ErrLangPackInvalid
|
||||
}
|
||||
return languages, nil
|
||||
}
|
||||
|
||||
func normalizePack(langPack string) string {
|
||||
|
|
@ -131,6 +144,9 @@ func normalizePack(langPack string) string {
|
|||
if pack == "" {
|
||||
return "tdesktop"
|
||||
}
|
||||
if pack == "web" {
|
||||
return "webk"
|
||||
}
|
||||
return pack
|
||||
}
|
||||
|
||||
|
|
@ -262,6 +278,22 @@ func (s *Service) cachedLanguages(ctx context.Context, langPack string) ([]domai
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) validateLanguage(ctx context.Context, langPack, langCode string) error {
|
||||
languages, err := s.cachedLanguages(ctx, langPack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(languages) == 0 {
|
||||
return domain.ErrLangPackInvalid
|
||||
}
|
||||
for _, language := range languages {
|
||||
if normalizeCode(language.LangCode) == langCode {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return domain.ErrLangCodeNotSupported
|
||||
}
|
||||
|
||||
func (s *Service) brandPack(pack domain.LangPack) domain.LangPack {
|
||||
for i := range pack.Strings {
|
||||
item := &pack.Strings[i]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package langpack
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -11,6 +12,106 @@ import (
|
|||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
func TestServiceNormalizesWebAliasAndRejectsUnknownCatalogEntries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
packs := memory.NewLangPackStore()
|
||||
svc := NewService(packs)
|
||||
for _, pack := range []domain.LangPack{
|
||||
{
|
||||
LangPack: "webk",
|
||||
LangCode: "en",
|
||||
Version: 7,
|
||||
Strings: []domain.LangPackString{{Key: "lng_settings_language", Value: "Language"}},
|
||||
},
|
||||
{
|
||||
LangPack: "webk",
|
||||
LangCode: "zh-hans",
|
||||
Version: 9,
|
||||
Strings: []domain.LangPackString{{Key: "lng_settings_language", Value: "语言"}},
|
||||
},
|
||||
} {
|
||||
if err := packs.UpsertPack(ctx, pack); err != nil {
|
||||
t.Fatalf("seed %s/%s: %v", pack.LangPack, pack.LangCode, err)
|
||||
}
|
||||
}
|
||||
|
||||
languages, err := svc.ListLanguages(ctx, " WEB ")
|
||||
if err != nil {
|
||||
t.Fatalf("list web languages: %v", err)
|
||||
}
|
||||
if len(languages) != 2 || findLanguage(languages, "zh-hans") == nil {
|
||||
t.Fatalf("web languages = %+v, want canonical webk catalog", languages)
|
||||
}
|
||||
|
||||
full, err := svc.GetLangPack(ctx, "web", "ZH_HANS")
|
||||
if err != nil {
|
||||
t.Fatalf("get web langpack: %v", err)
|
||||
}
|
||||
if full.LangPack != "webk" || full.LangCode != "zh-hans" || full.Version != 9 || stringValue(full.Strings, "lng_settings_language") != "语言" {
|
||||
t.Fatalf("web langpack = %+v, want canonical webk/zh-hans", full)
|
||||
}
|
||||
|
||||
diff, err := svc.GetDifference(ctx, "web", "zh-hans", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("get web difference: %v", err)
|
||||
}
|
||||
if diff.LangPack != "webk" || diff.FromVersion != 1 || len(diff.Strings) != 1 {
|
||||
t.Fatalf("web difference = %+v, want canonical webk delta", diff)
|
||||
}
|
||||
|
||||
selected, err := svc.GetStrings(ctx, "web", "zh-hans", []string{"lng_settings_language"})
|
||||
if err != nil {
|
||||
t.Fatalf("get web strings: %v", err)
|
||||
}
|
||||
if selected.LangPack != "webk" || stringValue(selected.Strings, "lng_settings_language") != "语言" {
|
||||
t.Fatalf("web strings = %+v, want selected webk string", selected)
|
||||
}
|
||||
|
||||
invalidPackCalls := map[string]func() error{
|
||||
"list": func() error {
|
||||
_, err := svc.ListLanguages(ctx, "web-invalid")
|
||||
return err
|
||||
},
|
||||
"full": func() error {
|
||||
_, err := svc.GetLangPack(ctx, "web-invalid", "en")
|
||||
return err
|
||||
},
|
||||
"difference": func() error {
|
||||
_, err := svc.GetDifference(ctx, "web-invalid", "en", 1)
|
||||
return err
|
||||
},
|
||||
"strings": func() error {
|
||||
_, err := svc.GetStrings(ctx, "web-invalid", "en", []string{"key"})
|
||||
return err
|
||||
},
|
||||
}
|
||||
for name, call := range invalidPackCalls {
|
||||
if err := call(); !errors.Is(err, domain.ErrLangPackInvalid) {
|
||||
t.Fatalf("%s invalid pack error = %v, want ErrLangPackInvalid", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
unsupportedCodeCalls := map[string]func() error{
|
||||
"full": func() error {
|
||||
_, err := svc.GetLangPack(ctx, "web", "fr")
|
||||
return err
|
||||
},
|
||||
"difference": func() error {
|
||||
_, err := svc.GetDifference(ctx, "web", "fr", 1)
|
||||
return err
|
||||
},
|
||||
"strings": func() error {
|
||||
_, err := svc.GetStrings(ctx, "web", "fr", []string{"key"})
|
||||
return err
|
||||
},
|
||||
}
|
||||
for name, call := range unsupportedCodeCalls {
|
||||
if err := call(); !errors.Is(err, domain.ErrLangCodeNotSupported) {
|
||||
t.Fatalf("%s unsupported code error = %v, want ErrLangCodeNotSupported", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceNormalizesWebARawLangCode(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
packs := memory.NewLangPackStore()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
package domain
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrLangPackInvalid 表示客户端请求的语言包目录不存在。
|
||||
ErrLangPackInvalid = errors.New("lang pack invalid")
|
||||
// ErrLangCodeNotSupported 表示语言包目录存在,但其中没有请求的语言码。
|
||||
ErrLangCodeNotSupported = errors.New("lang code not supported")
|
||||
)
|
||||
|
||||
// LangPack 是一份客户端语言包的查询结果。
|
||||
type LangPack struct {
|
||||
LangPack string
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ func inputConstructorInvalidErr() error { return tgerr.New(400, "INPUT_CONSTRUCT
|
|||
// langCodeNotSupportedErr 表示请求的语言码没有已导入的语言包。
|
||||
func langCodeNotSupportedErr() error { return tgerr.New(400, "LANG_CODE_NOT_SUPPORTED") }
|
||||
|
||||
// langPackInvalidErr 表示请求的语言包目录不存在。
|
||||
func langPackInvalidErr() error { return tgerr.New(400, "LANG_PACK_INVALID") }
|
||||
|
||||
// folderIDInvalidErr 表示客户端传入多个 folder peer 或非法 folder。
|
||||
func folderIDInvalidErr() error { return tgerr.New(400, "FOLDER_ID_INVALID") }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ package rpc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
"github.com/iamxvbaba/td/tlprofile"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// registerLangpack 注册 langpack.* RPC handler。
|
||||
|
|
@ -20,7 +23,7 @@ func (r *Router) registerLangpack(d *tlprofile.Dispatcher) {
|
|||
|
||||
languages, err := r.langpackLanguages(ctx, langPack)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
return nil, langpackServiceErr(err)
|
||||
}
|
||||
return languages, nil
|
||||
})
|
||||
|
|
@ -42,7 +45,7 @@ func (r *Router) registerLangpack(d *tlprofile.Dispatcher) {
|
|||
}
|
||||
pack, err := r.deps.LangPack.GetLangPack(ctx, langPack, req.LangCode)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
return nil, langpackServiceErr(err)
|
||||
}
|
||||
return tgLangPackDifference(pack), nil
|
||||
})
|
||||
|
|
@ -52,7 +55,7 @@ func (r *Router) registerLangpack(d *tlprofile.Dispatcher) {
|
|||
}
|
||||
pack, err := r.deps.LangPack.GetDifference(ctx, langPackOrClient(ctx, req.LangPack), req.LangCode, req.FromVersion)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
return nil, langpackServiceErr(err)
|
||||
}
|
||||
return tgLangPackDifference(pack), nil
|
||||
})
|
||||
|
|
@ -62,7 +65,7 @@ func (r *Router) registerLangpack(d *tlprofile.Dispatcher) {
|
|||
}
|
||||
pack, err := r.deps.LangPack.GetStrings(ctx, langPackOrClient(ctx, req.LangPack), req.LangCode, req.Keys)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
return nil, langpackServiceErr(err)
|
||||
}
|
||||
return tgLangPackStrings(pack.Strings), nil
|
||||
})
|
||||
|
|
@ -88,7 +91,7 @@ func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string
|
|||
langCode = normalizeLangpackCode(langCode)
|
||||
languages, err := r.langpackLanguages(ctx, langPack)
|
||||
if err != nil {
|
||||
return tg.LangPackLanguage{}, internalErr()
|
||||
return tg.LangPackLanguage{}, langpackServiceErr(err)
|
||||
}
|
||||
for _, lang := range languages {
|
||||
if strings.ToLower(lang.LangCode) == langCode {
|
||||
|
|
@ -150,3 +153,14 @@ func normalizeLangpackCode(langCode string) string {
|
|||
code = strings.ReplaceAll(code, "_", "-")
|
||||
return strings.TrimSuffix(code, "-raw")
|
||||
}
|
||||
|
||||
func langpackServiceErr(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrLangPackInvalid):
|
||||
return langPackInvalidErr()
|
||||
case errors.Is(err, domain.ErrLangCodeNotSupported):
|
||||
return langCodeNotSupportedErr()
|
||||
default:
|
||||
return internalErr()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,121 @@ func TestLangpackGetLanguagesCurrentAndLegacy(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLangpackWebAliasCoversEveryRPC(t *testing.T) {
|
||||
r := newSeededLangpackRouter(t)
|
||||
ctx := context.Background()
|
||||
|
||||
var languagesIn bin.Buffer
|
||||
if err := (&tg.LangpackGetLanguagesRequest{LangPack: "web"}).Encode(&languagesIn); err != nil {
|
||||
t.Fatalf("encode getLanguages: %v", err)
|
||||
}
|
||||
languages := dispatchLangpackLanguages(t, r, ctx, &languagesIn)
|
||||
assertHasLangpackLanguage(t, languages, "zh-hans")
|
||||
|
||||
lang, err := r.langpackLanguage(ctx, "web", "zh-hans")
|
||||
if err != nil {
|
||||
t.Fatalf("getLanguage web alias: %v", err)
|
||||
}
|
||||
if lang.LangCode != "zh-hans" {
|
||||
t.Fatalf("getLanguage web alias = %+v, want zh-hans", lang)
|
||||
}
|
||||
|
||||
var fullIn bin.Buffer
|
||||
if err := (&tg.LangpackGetLangPackRequest{LangPack: "web", LangCode: "zh-hans"}).Encode(&fullIn); err != nil {
|
||||
t.Fatalf("encode getLangPack: %v", err)
|
||||
}
|
||||
full := dispatchLangpackDifference(t, r, ctx, &fullIn)
|
||||
if full.LangCode != "zh-hans" || len(full.Strings) != 1 {
|
||||
t.Fatalf("getLangPack web alias = %+v, want populated zh-hans pack", full)
|
||||
}
|
||||
|
||||
var differenceIn bin.Buffer
|
||||
if err := (&tg.LangpackGetDifferenceRequest{LangPack: "web", LangCode: "zh-hans", FromVersion: 1}).Encode(&differenceIn); err != nil {
|
||||
t.Fatalf("encode getDifference: %v", err)
|
||||
}
|
||||
difference := dispatchLangpackDifference(t, r, ctx, &differenceIn)
|
||||
if difference.LangCode != "zh-hans" || difference.FromVersion != 1 || len(difference.Strings) != 1 {
|
||||
t.Fatalf("getDifference web alias = %+v, want populated zh-hans delta", difference)
|
||||
}
|
||||
|
||||
var stringsIn bin.Buffer
|
||||
if err := (&tg.LangpackGetStringsRequest{LangPack: "web", LangCode: "zh-hans", Keys: []string{"lng_settings_language"}}).Encode(&stringsIn); err != nil {
|
||||
t.Fatalf("encode getStrings: %v", err)
|
||||
}
|
||||
enc, err := r.Dispatch(ctx, [8]byte{}, 0, &stringsIn)
|
||||
if err != nil {
|
||||
t.Fatalf("dispatch getStrings web alias: %v", err)
|
||||
}
|
||||
var stringsOut bin.Buffer
|
||||
if err := enc.Encode(&stringsOut); err != nil {
|
||||
t.Fatalf("encode getStrings response: %v", err)
|
||||
}
|
||||
var stringsVector tg.LangPackStringClassVector
|
||||
if err := stringsVector.Decode(&stringsOut); err != nil {
|
||||
t.Fatalf("decode getStrings response: %v", err)
|
||||
}
|
||||
if len(stringsVector.Elems) != 1 {
|
||||
t.Fatalf("getStrings web alias = %+v, want one selected string", stringsVector.Elems)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLangpackInvalidCatalogErrorsAreMappedForEveryRPC(t *testing.T) {
|
||||
r := newSeededLangpackRouter(t)
|
||||
ctx := context.Background()
|
||||
tests := []struct {
|
||||
name string
|
||||
encode func(*bin.Buffer) error
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "getLanguages invalid pack",
|
||||
encode: func(buf *bin.Buffer) error {
|
||||
return (&tg.LangpackGetLanguagesRequest{LangPack: "web-invalid"}).Encode(buf)
|
||||
},
|
||||
wantErr: "LANG_PACK_INVALID",
|
||||
},
|
||||
{
|
||||
name: "getLanguage invalid pack",
|
||||
encode: func(buf *bin.Buffer) error {
|
||||
return (&tg.LangpackGetLanguageRequest{LangPack: "web-invalid", LangCode: "en"}).Encode(buf)
|
||||
},
|
||||
wantErr: "LANG_PACK_INVALID",
|
||||
},
|
||||
{
|
||||
name: "getLangPack invalid pack",
|
||||
encode: func(buf *bin.Buffer) error {
|
||||
return (&tg.LangpackGetLangPackRequest{LangPack: "web-invalid", LangCode: "en"}).Encode(buf)
|
||||
},
|
||||
wantErr: "LANG_PACK_INVALID",
|
||||
},
|
||||
{
|
||||
name: "getDifference unsupported code",
|
||||
encode: func(buf *bin.Buffer) error {
|
||||
return (&tg.LangpackGetDifferenceRequest{LangPack: "web", LangCode: "fr", FromVersion: 1}).Encode(buf)
|
||||
},
|
||||
wantErr: "LANG_CODE_NOT_SUPPORTED",
|
||||
},
|
||||
{
|
||||
name: "getStrings unsupported code",
|
||||
encode: func(buf *bin.Buffer) error {
|
||||
return (&tg.LangpackGetStringsRequest{LangPack: "web", LangCode: "fr", Keys: []string{"key"}}).Encode(buf)
|
||||
},
|
||||
wantErr: "LANG_CODE_NOT_SUPPORTED",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var in bin.Buffer
|
||||
if err := test.encode(&in); err != nil {
|
||||
t.Fatalf("encode request: %v", err)
|
||||
}
|
||||
if _, err := r.Dispatch(ctx, [8]byte{}, 0, &in); err == nil || !strings.Contains(err.Error(), test.wantErr) {
|
||||
t.Fatalf("dispatch error = %v, want %s", err, test.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLangpackGetLanguage(t *testing.T) {
|
||||
r := newSeededLangpackRouter(t)
|
||||
|
||||
|
|
@ -247,6 +362,18 @@ func seededLangPackService(t testing.TB) LangPackService {
|
|||
{Key: "TranslateLanguageFA", Value: "فارسی"},
|
||||
},
|
||||
},
|
||||
{
|
||||
LangPack: "webk",
|
||||
LangCode: "en",
|
||||
Version: 2,
|
||||
Strings: []domain.LangPackString{{Key: "lng_settings_language", Value: "Language"}},
|
||||
},
|
||||
{
|
||||
LangPack: "webk",
|
||||
LangCode: "zh-hans",
|
||||
Version: 2,
|
||||
Strings: []domain.LangPackString{{Key: "lng_settings_language", Value: "语言"}},
|
||||
},
|
||||
} {
|
||||
if err := store.UpsertPack(ctx, pack); err != nil {
|
||||
t.Fatalf("seed %s/%s: %v", pack.LangPack, pack.LangCode, err)
|
||||
|
|
@ -272,6 +399,23 @@ func dispatchLangpackLanguages(t *testing.T, r *Router, ctx context.Context, in
|
|||
return langs.Elems
|
||||
}
|
||||
|
||||
func dispatchLangpackDifference(t *testing.T, r *Router, ctx context.Context, in *bin.Buffer) tg.LangPackDifference {
|
||||
t.Helper()
|
||||
enc, err := r.Dispatch(ctx, [8]byte{}, 0, in)
|
||||
if err != nil {
|
||||
t.Fatalf("dispatch langpack difference: %v", err)
|
||||
}
|
||||
var out bin.Buffer
|
||||
if err := enc.Encode(&out); err != nil {
|
||||
t.Fatalf("encode langpack difference: %v", err)
|
||||
}
|
||||
var diff tg.LangPackDifference
|
||||
if err := diff.Decode(&out); err != nil {
|
||||
t.Fatalf("decode langpack difference: %v", err)
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func assertHasLangpackLanguage(t *testing.T, langs []tg.LangPackLanguage, code string) {
|
||||
t.Helper()
|
||||
for _, lang := range langs {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue