mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
- 模型广场以追加方式写入 Vary: Accept-Language,保留 CORS 中间件为具体来源 写入的 Vary: Origin - 管理端写入分组译文时语言代码统一为 BCP 47 惯用大小写(en、zh-TW), 统一后重复的语言代码(如 en 与 EN)返回 400 INVALID_GROUP_I18N - 用户侧按语言取译文时规范写法的键优先,仅大小写不同的多个键按字节序取最小者, 同一请求语言的结果固定
65 lines
2.3 KiB
Go
65 lines
2.3 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"regexp"
|
|
"slices"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
|
)
|
|
|
|
type GroupI18n = domain.GroupI18n
|
|
type GroupLocaleText = domain.GroupLocaleText
|
|
|
|
// groupI18nNameMaxRunes 与 groups.name 的长度上限一致。
|
|
const groupI18nNameMaxRunes = 100
|
|
|
|
// 语言代码只校验 BCP 47 形态(en、zh、zh-TW);具体提供哪些语言由前端语言包决定。
|
|
var groupI18nLocalePattern = regexp.MustCompile(`^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$`)
|
|
|
|
// normalizeGroupI18n 把语言代码统一为 domain.CanonicalLocale 的写法,去除译文首尾空白,
|
|
// 并丢弃名称与描述都为空的语言。统一写法后重复的语言代码(如 en 与 EN)直接拒绝,
|
|
// 不在其中任选一份。返回值始终非 nil,落库为 JSON 对象。
|
|
func normalizeGroupI18n(in GroupI18n) (GroupI18n, error) {
|
|
out := make(GroupI18n, len(in))
|
|
seen := make(map[string]struct{}, len(in))
|
|
for _, raw := range slices.Sorted(maps.Keys(in)) {
|
|
if !groupI18nLocalePattern.MatchString(raw) {
|
|
return nil, infraerrors.BadRequest("INVALID_GROUP_I18N", fmt.Sprintf("invalid locale %q in i18n", raw))
|
|
}
|
|
locale := domain.CanonicalLocale(raw)
|
|
if _, dup := seen[locale]; dup {
|
|
return nil, infraerrors.BadRequest("INVALID_GROUP_I18N", fmt.Sprintf("duplicate locale %q in i18n", locale))
|
|
}
|
|
seen[locale] = struct{}{}
|
|
text := in[raw]
|
|
name := strings.TrimSpace(text.Name)
|
|
description := strings.TrimSpace(text.Description)
|
|
if utf8.RuneCountInString(name) > groupI18nNameMaxRunes {
|
|
return nil, infraerrors.BadRequest("INVALID_GROUP_I18N", fmt.Sprintf("i18n name for locale %q must be at most %d characters", locale, groupI18nNameMaxRunes))
|
|
}
|
|
if name == "" && description == "" {
|
|
continue
|
|
}
|
|
out[locale] = GroupLocaleText{Name: name, Description: description}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// cloneGroupI18nForDuplicate 复制译文中的描述。名称译文不随复制带走:
|
|
// 副本的 name 已改写,沿用源分组的名称译文会让两个分组在该语言下同名。
|
|
func cloneGroupI18nForDuplicate(in GroupI18n) GroupI18n {
|
|
out := make(GroupI18n, len(in))
|
|
for locale, text := range in {
|
|
if text.Description == "" {
|
|
continue
|
|
}
|
|
out[locale] = GroupLocaleText{Description: text.Description}
|
|
}
|
|
return out
|
|
}
|