mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
- groups 新增 i18n JSONB 列(迁移 242):语言代码 -> {name, description},只存译文;
name/description 原列语义不变,仍用于唯一性校验、搜索与各类日志
- 用户侧接口由后端按请求语言输出:取 Accept-Language 中权重最高的一项,先按完整
语言代码、再按主语言匹配;该语言有译文的字段换成译文,否则保持原字段,且不再带出
译文表。覆盖密钥、可用分组、订阅(含进度与汇总)、兑换、使用记录、可用渠道、
模型广场、套餐列表
- 管理端接口一律返回原字段;分组管理接口额外带 i18n 供编辑
- 创建/编辑分组:名称、描述的标题行右侧新增语言下拉,同一个输入框在默认内容与各语言
版本之间切换;语种跟随前端 availableLocales
- 管理端写入:创建/更新接受 i18n(更新时缺省不修改,传入则整体替换),校验语言代码
形态与名称长度并丢弃空译文;简易模式同样放行;复制分组只带走描述译文
- 切换界面语言后整页刷新,让已加载的数据换成新语言;模型广场响应加
Vary: Accept-Language
- 网关路径,以及只带 group_name 字符串的统计、错误记录出口不做本地化
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestToLocalizedModelPlazaGroups(t *testing.T) {
|
|
visible := []service.PlazaGroup{
|
|
{
|
|
ID: 1, Name: "标准", Description: "号池", Platform: "anthropic",
|
|
I18n: service.GroupI18n{"en": {Name: "Standard", Description: "Pro + Max pool"}},
|
|
},
|
|
{ID: 2, Name: "无译文", Description: "原描述", Platform: "openai"},
|
|
}
|
|
|
|
english := toLocalizedModelPlazaGroups(visible, nil, "en")
|
|
require.Equal(t, "Standard", english[0].Name)
|
|
require.Equal(t, "Pro + Max pool", english[0].Description)
|
|
require.Equal(t, "无译文", english[1].Name)
|
|
require.Equal(t, "原描述", english[1].Description)
|
|
|
|
chinese := toLocalizedModelPlazaGroups(visible, nil, "zh")
|
|
require.Equal(t, "标准", chinese[0].Name)
|
|
require.Equal(t, "号池", chinese[0].Description)
|
|
|
|
// service 层对象不被改写,同一批分组可继续按别的语言输出
|
|
require.Equal(t, "标准", visible[0].Name)
|
|
|
|
raw, err := json.Marshal(english[0])
|
|
require.NoError(t, err)
|
|
var decoded map[string]any
|
|
require.NoError(t, json.Unmarshal(raw, &decoded))
|
|
require.NotContains(t, decoded, "i18n")
|
|
}
|
|
|
|
func TestLocalizeUserAvailableGroups(t *testing.T) {
|
|
refs := []service.AvailableGroupRef{
|
|
{ID: 1, Name: "标准", Platform: "anthropic", I18n: service.GroupI18n{"en": {Name: "Standard"}}},
|
|
{ID: 2, Name: "无译文", Platform: "anthropic"},
|
|
}
|
|
allowed := map[int64]struct{}{1: {}, 2: {}}
|
|
|
|
english := filterUserVisibleGroups(refs, allowed)
|
|
localizeUserAvailableGroups(english, "en")
|
|
require.Equal(t, "Standard", english[0].Name)
|
|
require.Equal(t, "无译文", english[1].Name)
|
|
|
|
chinese := filterUserVisibleGroups(refs, allowed)
|
|
localizeUserAvailableGroups(chinese, "zh")
|
|
require.Equal(t, "标准", chinese[0].Name)
|
|
|
|
raw, err := json.Marshal(english[0])
|
|
require.NoError(t, err)
|
|
var decoded map[string]any
|
|
require.NoError(t, json.Unmarshal(raw, &decoded))
|
|
require.NotContains(t, decoded, "i18n")
|
|
}
|
|
|
|
// 用户侧 handler 输出带分组的 DTO 时必须经过 Localized,否则分组名称与描述会
|
|
// 停留在原字段,并把译文表原样带给用户。管理端(handler/admin)不在此列。
|
|
func TestUserFacingGroupDTOsAreLocalized(t *testing.T) {
|
|
mapperCall := regexp.MustCompile(`dto\.(APIKey|UserSubscription|RedeemCode|UsageLog|Group)FromService[A-Za-z]*\(`)
|
|
|
|
files, err := filepath.Glob("*.go")
|
|
require.NoError(t, err)
|
|
|
|
checked := 0
|
|
for _, file := range files {
|
|
if strings.HasSuffix(file, "_test.go") {
|
|
continue
|
|
}
|
|
source, err := os.ReadFile(file)
|
|
require.NoError(t, err)
|
|
for i, line := range strings.Split(string(source), "\n") {
|
|
if !mapperCall.MatchString(line) {
|
|
continue
|
|
}
|
|
checked++
|
|
require.Containsf(t, line, ".Localized(",
|
|
"%s:%d outputs a group DTO without Localized(Accept-Language)", file, i+1)
|
|
}
|
|
}
|
|
require.NotZero(t, checked, "expected to find user-facing group DTO call sites")
|
|
}
|