feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+38 -52
View File
@@ -2,6 +2,7 @@ package i18n
import (
"embed"
"slices"
"strings"
"sync"
@@ -86,14 +87,17 @@ func GetLocalizer(lang string) *i18n.Localizer {
return loc
}
// T translates a message key using the language from gin context
// T translates a message key in the language the reader of the request stated
func T(c *gin.Context, key string, args ...map[string]any) string {
lang := GetLangFromContext(c)
return Translate(lang, key, args...)
return Translate(StatedLang(c), key, args...)
}
// Translate translates a message key for the specified language
// Translate translates a message key for the specified language. An empty
// language means the reader stated none and gets common.DefaultLanguage.
func Translate(lang, key string, args ...map[string]any) string {
if lang == "" {
lang = common.DefaultLanguage
}
loc := GetLocalizer(lang)
config := &i18n.LocalizeConfig{
@@ -121,29 +125,30 @@ func SetUserLangLoader(loader func(userId int) string) {
userLangLoaderFunc = loader
}
// GetLangFromContext extracts the language setting from gin context
// It checks multiple sources in priority order:
// StatedLang returns the language the reader of a request stated, or "" when
// there is none. It checks multiple sources in priority order:
// 1. User settings (ContextKeyUserSetting) - if already loaded (e.g., by TokenAuth)
// 2. Lazy load user language from cache/DB using user ID
// 2. Lazy load user language from cache/DB using user ID, when step 1 found no settings
// 3. Language set by middleware (ContextKeyLanguage) - from Accept-Language header
// 4. Default language (English)
func GetLangFromContext(c *gin.Context) string {
// 4. The Accept-Language header
func StatedLang(c *gin.Context) string {
if c == nil {
return DefaultLang
return ""
}
// 1. Try to get language from user settings (if already loaded by TokenAuth or other middleware)
if userSetting, ok := common.GetContextKeyType[dto.UserSetting](c, constant.ContextKeyUserSetting); ok {
if userSetting.Language != "" {
normalized := normalizeLang(userSetting.Language)
if IsSupported(normalized) {
return normalized
}
userSetting, settingLoaded := common.GetContextKeyType[dto.UserSetting](c, constant.ContextKeyUserSetting)
if settingLoaded && userSetting.Language != "" {
normalized := normalizeLang(userSetting.Language)
if IsSupported(normalized) {
return normalized
}
}
// 2. Lazy load user language using user ID (for session-based auth where full settings aren't loaded)
if userLangLoaderFunc != nil {
// 2. Lazy load user language using user ID (for session-based auth where full settings aren't loaded).
// Loaded settings without a language mean the user saved none; loading the user again would cost
// a cache or database read on every relay request.
if !settingLoaded && userLangLoaderFunc != nil {
if userId, exists := c.Get("id"); exists {
if uid, ok := userId.(int); ok && uid > 0 {
lang := userLangLoaderFunc(uid)
@@ -166,44 +171,30 @@ func GetLangFromContext(c *gin.Context) string {
}
// 4. Try Accept-Language header directly (fallback if middleware didn't run)
if acceptLang := c.GetHeader("Accept-Language"); acceptLang != "" {
lang := ParseAcceptLanguage(acceptLang)
if IsSupported(lang) {
return lang
}
}
return DefaultLang
return ParseAcceptLanguage(c.GetHeader("Accept-Language"))
}
// ParseAcceptLanguage parses the Accept-Language header and returns the preferred language
// ParseAcceptLanguage returns the first language of an Accept-Language header,
// or "" when the header is absent or states no preference ("*").
func ParseAcceptLanguage(header string) string {
if header == "" {
return DefaultLang
first, _, _ := strings.Cut(header, ",")
first, _, _ = strings.Cut(first, ";")
first = strings.TrimSpace(first)
if first == "" || first == "*" {
return ""
}
// Simple parsing: take the first language tag
parts := strings.Split(header, ",")
if len(parts) == 0 {
return DefaultLang
}
// Get the first language and remove quality value
firstLang := strings.TrimSpace(parts[0])
if idx := strings.Index(firstLang, ";"); idx > 0 {
firstLang = firstLang[:idx]
}
return normalizeLang(firstLang)
return normalizeLang(first)
}
// normalizeLang normalizes language code to supported format
func normalizeLang(lang string) string {
lang = strings.ToLower(strings.TrimSpace(lang))
lang = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(lang), "_", "-"))
// Handle common variations
// Handle common variations. The web console saves its own codes zhCN and
// zhTW, and maps zh-HK, zh-MO and zh-Hant to Traditional Chinese.
switch {
case strings.HasPrefix(lang, "zh-tw"):
case lang == "zhtw" || strings.HasPrefix(lang, "zh-tw") || strings.HasPrefix(lang, "zh-hk") ||
strings.HasPrefix(lang, "zh-mo") || strings.HasPrefix(lang, "zh-hant"):
return LangZhTW
case strings.HasPrefix(lang, "zh"):
return LangZhCN
@@ -222,10 +213,5 @@ func SupportedLanguages() []string {
// IsSupported checks if a language code is supported
func IsSupported(lang string) bool {
lang = normalizeLang(lang)
for _, supported := range SupportedLanguages() {
if lang == supported {
return true
}
}
return false
return slices.Contains(SupportedLanguages(), lang)
}
+207
View File
@@ -0,0 +1,207 @@
package i18n
import (
"errors"
"go/ast"
"go/parser"
"go/token"
"net/http/httptest"
"slices"
"strconv"
"strings"
"testing"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
// The web console saves its own language codes (zhCN, zhTW, fr, ...) in the
// user setting; backend messages must follow them, and languages without a
// backend locale fall back to English.
func TestLanguageFromUserSetting(t *testing.T) {
require.NoError(t, Init())
for _, tc := range []struct {
saved string
want string
}{
{"zhCN", LangZhCN},
{"zhTW", LangZhTW},
{"zh-HK", LangZhTW},
{"zh_TW", LangZhTW},
{"zh_HK", LangZhTW},
{"zh-Hant-TW", LangZhTW},
{"zh", LangZhCN},
{"en", LangEn},
{"fr", LangEn},
{"ja", LangEn},
} {
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest("GET", "/v1/chat/completions", nil)
c.Set(string(constant.ContextKeyUserSetting), dto.UserSetting{Language: tc.saved})
assert.Equal(t, tc.want, StatedLang(c), tc.saved)
}
}
// TokenAuth puts the user's settings into the request. A user who saved no
// language must not be loaded again, which would cost a cache or database read
// on every relay request; a request without loaded settings still loads them.
func TestStatedLangLoadsTheUserOnlyWithoutLoadedSettings(t *testing.T) {
previous := userLangLoaderFunc
t.Cleanup(func() { userLangLoaderFunc = previous })
loads := 0
SetUserLangLoader(func(int) string {
loads++
return "zhTW"
})
relay, _ := gin.CreateTestContext(httptest.NewRecorder())
relay.Request = httptest.NewRequest("POST", "/v1/chat/completions", nil)
relay.Set("id", 7)
relay.Set(string(constant.ContextKeyUserSetting), dto.UserSetting{})
assert.Equal(t, "", StatedLang(relay))
assert.Zero(t, loads)
dashboard, _ := gin.CreateTestContext(httptest.NewRecorder())
dashboard.Request = httptest.NewRequest("GET", "/api/user/self", nil)
dashboard.Set("id", 7)
assert.Equal(t, LangZhTW, StatedLang(dashboard))
assert.Equal(t, 1, loads)
}
// A saved language or an Accept-Language header selects the language of a
// backend message. A reader who states neither gets DEFAULT_LANGUAGE, which is
// English when unset.
func TestBackendMessageLanguage(t *testing.T) {
require.NoError(t, Init())
t.Cleanup(func() { common.DefaultLanguage = "" })
quota := map[string]any{"Remaining": "$0.10"}
const english = "Insufficient user quota, remaining quota: $0.10"
const chinese = "用户额度不足, 剩余额度: $0.10"
for _, tc := range []struct {
name string
defaultLanguage string
saved string
acceptLanguage string
want string
}{
{"nothing stated", "", "", "", english},
{"no preference header", "", "", "*", english},
{"nothing stated with a Chinese default", "zh-CN", "", "", chinese},
{"nothing stated with an English default", "en", "", "", english},
{"header wins over the default", "zh-CN", "", "en-US,en;q=0.9", english},
{"header in a language without a locale", "zh-CN", "", "fr-FR", english},
{"saved language wins over the header", "", "zhTW", "en-US", "使用者額度不足,剩餘額度:$0.10"},
{"saved language", "zh-CN", "en", "", english},
} {
t.Run(tc.name, func(t *testing.T) {
common.DefaultLanguage = tc.defaultLanguage
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest("POST", "/v1/chat/completions", nil)
if tc.acceptLanguage != "" {
c.Request.Header.Set("Accept-Language", tc.acceptLanguage)
}
if tc.saved != "" {
c.Set(string(constant.ContextKeyUserSetting), dto.UserSetting{Language: tc.saved})
}
assert.Equal(t, tc.want, T(c, MsgQuotaUserInsufficient, quota))
})
}
}
// A key missing from a locale, or a template that does not parse, reaches
// clients as the raw key.
func TestLocalesRenderEveryKey(t *testing.T) {
require.NoError(t, Init())
keysFile, err := parser.ParseFile(token.NewFileSet(), "keys.go", nil, parser.SkipObjectResolution)
require.NoError(t, err)
var declared []string
ast.Inspect(keysFile, func(node ast.Node) bool {
if spec, ok := node.(*ast.ValueSpec); ok {
for _, value := range spec.Values {
if literal, ok := value.(*ast.BasicLit); ok {
key, err := strconv.Unquote(literal.Value)
require.NoError(t, err)
declared = append(declared, key)
}
}
}
return true
})
slices.Sort(declared)
keysByLang := make(map[string][]string)
for _, lang := range SupportedLanguages() {
data, err := localeFS.ReadFile("locales/" + lang + ".yaml")
require.NoError(t, err)
var messages map[string]string
require.NoError(t, yaml.Unmarshal(data, &messages))
for key := range messages {
keysByLang[lang] = append(keysByLang[lang], key)
}
slices.Sort(keysByLang[lang])
}
for _, lang := range SupportedLanguages() {
assert.Equal(t, declared, keysByLang[lang], lang)
for _, key := range keysByLang[lang] {
text := Translate(lang, key, map[string]any{})
assert.NotEqual(t, key, text, "%s %s", lang, key)
assert.False(t, strings.Contains(text, "{{"), "%s %s: %s", lang, key, text)
}
}
}
// Web console responses carry the English source text as message_key plus its
// params, and the web console translates them. The backend sends the same body
// whatever language the request asks for.
func TestWebConsoleMessageResponse(t *testing.T) {
require.NoError(t, Init())
for _, tc := range []struct {
name string
err error
want string
}{
{
name: "params",
err: common.NewMessage("Channel {{name}} (#{{id}}) does not exist", map[string]any{"name": "{{id}}", "id": 7}),
want: `{"success":false,"message":"Channel {{id}} (#7) does not exist","message_key":"Channel {{name}} (#{{id}}) does not exist","message_params":{"id":7,"name":"{{id}}"}}`,
},
{
name: "no params",
err: common.NewMessage("Invalid parameters"),
want: `{"success":false,"message":"Invalid parameters","message_key":"Invalid parameters"}`,
},
{
name: "plain error",
err: errors.New("record not found"),
want: `{"success":false,"message":"record not found"}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request = httptest.NewRequest("GET", "/api/channel/7", nil)
c.Request.Header.Set("Accept-Language", "zh-CN")
common.ApiError(c, tc.err)
assert.JSONEq(t, tc.want, recorder.Body.String())
})
}
}
// Server log lines are English. DEFAULT_LANGUAGE=zh-CN prints the Chinese text of
// the lines that have one in common/log_text.zh-CN.json.
func TestServerLogLanguage(t *testing.T) {
t.Cleanup(func() { common.SetLogLanguage("") })
const line = "channel #%d has %d unfinished tasks"
assert.Equal(t, "channel #7 has 2 unfinished tasks", common.LogText(line, 7, 2))
common.SetLogLanguage("zh-CN")
assert.Equal(t, "渠道 #7 未完成的任务有: 2", common.LogText(line, 7, 2))
assert.Equal(t, "a line without a Chinese text: x", common.LogText("a line without a Chinese text: %s", "x"))
assert.Equal(t, "任务进度轮询开始", common.LogText("task progress polling started"))
common.SetLogLanguage("en")
assert.Equal(t, "channel #7 has 2 unfinished tasks", common.LogText(line, 7, 2))
}
+90 -320
View File
@@ -1,326 +1,30 @@
package i18n
const MsgTaskPluginUnknownMetaField = "task_plugin.unknown_meta_field"
// Message keys of i18n/locales/*.yaml, for text the backend translates itself:
// errors returned to AI clients, emails and notifications. Web console messages
// are not listed here; the web console translates them (see common.Message).
// Message keys for i18n translations
// Use these constants instead of hardcoded strings
// Common error messages
// Errors returned to AI clients
const (
MsgInvalidParams = "common.invalid_params"
MsgDatabaseError = "common.database_error"
MsgRetryLater = "common.retry_later"
MsgGenerateFailed = "common.generate_failed"
MsgNotFound = "common.not_found"
MsgUnauthorized = "common.unauthorized"
MsgForbidden = "common.forbidden"
MsgInvalidId = "common.invalid_id"
MsgIdEmpty = "common.id_empty"
MsgFeatureDisabled = "common.feature_disabled"
MsgOperationSuccess = "common.operation_success"
MsgOperationFailed = "common.operation_failed"
MsgUpdateSuccess = "common.update_success"
MsgUpdateFailed = "common.update_failed"
MsgCreateSuccess = "common.create_success"
MsgCreateFailed = "common.create_failed"
MsgDeleteSuccess = "common.delete_success"
MsgDeleteFailed = "common.delete_failed"
MsgAlreadyExists = "common.already_exists"
MsgNameCannotBeEmpty = "common.name_cannot_be_empty"
MsgBatchTooMany = "common.batch_too_many"
)
MsgDatabaseError = "common.database_error"
MsgAuthUserBanned = "auth.user_banned"
MsgTokenGetInfoFailed = "token.get_info_failed"
MsgTokenInvalid = "token.invalid"
MsgTokenNotProvided = "token.not_provided"
MsgTokenStatusUnavailable = "token.status_unavailable"
// Auth middleware messages
const (
MsgAuthNotLoggedIn = "auth.not_logged_in"
MsgAuthAccessTokenInvalid = "auth.access_token_invalid"
MsgAuthUserInfoInvalid = "auth.user_info_invalid"
MsgAuthUserIdNotProvided = "auth.user_id_not_provided"
MsgAuthUserIdFormatError = "auth.user_id_format_error"
MsgAuthUserIdMismatch = "auth.user_id_mismatch"
MsgAuthUserBanned = "auth.user_banned"
MsgAuthInsufficientPrivilege = "auth.insufficient_privilege"
)
MsgAuthClientIPUnresolved = "auth.client_ip_unresolved"
MsgAuthTokenIPNotAllowed = "auth.token_ip_not_allowed"
MsgAuthTokenGroupForbidden = "auth.token_group_forbidden"
MsgAuthTokenGroupDeprecated = "auth.token_group_deprecated"
MsgAuthChannelSelectForbidden = "auth.channel_select_forbidden"
// Token related messages
const (
MsgTokenNameTooLong = "token.name_too_long"
MsgTokenQuotaNegative = "token.quota_negative"
MsgTokenQuotaExceedMax = "token.quota_exceed_max"
MsgTokenGenerateFailed = "token.generate_failed"
MsgTokenGetInfoFailed = "token.get_info_failed"
MsgTokenExpiredCannotEnable = "token.expired_cannot_enable"
MsgTokenExhaustedCannotEable = "token.exhausted_cannot_enable"
MsgTokenInvalid = "token.invalid"
MsgTokenNotProvided = "token.not_provided"
MsgTokenExpired = "token.expired"
MsgTokenExhausted = "token.exhausted"
MsgTokenStatusUnavailable = "token.status_unavailable"
MsgTokenDbError = "token.db_error"
MsgTokenAutoGroupsTooMany = "token.auto_groups_too_many"
MsgTokenAutoGroupsDuplicate = "token.auto_groups_duplicate"
MsgTokenAutoGroupsInvalid = "token.auto_groups_invalid"
)
// Redemption related messages
const (
MsgRedemptionNameLength = "redemption.name_length"
MsgRedemptionCountPositive = "redemption.count_positive"
MsgRedemptionCountMax = "redemption.count_max"
MsgRedemptionCreateFailed = "redemption.create_failed"
MsgRedemptionInvalid = "redemption.invalid"
MsgRedemptionUsed = "redemption.used"
MsgRedemptionExpired = "redemption.expired"
MsgRedemptionFailed = "redemption.failed"
MsgRedemptionNotProvided = "redemption.not_provided"
MsgRedemptionExpireTimeInvalid = "redemption.expire_time_invalid"
)
// User related messages
const (
MsgUserPasswordLoginDisabled = "user.password_login_disabled"
MsgUserRegisterDisabled = "user.register_disabled"
MsgUserPasswordRegisterDisabled = "user.password_register_disabled"
MsgUserUsernameOrPasswordEmpty = "user.username_or_password_empty"
MsgUserUsernameOrPasswordError = "user.username_or_password_error"
MsgUserEmailOrPasswordEmpty = "user.email_or_password_empty"
MsgUserExists = "user.exists"
MsgUserNotExists = "user.not_exists"
MsgUserDisabled = "user.disabled"
MsgUserSessionSaveFailed = "user.session_save_failed"
MsgUserRequire2FA = "user.require_2fa"
MsgUserEmailVerificationRequired = "user.email_verification_required"
MsgUserVerificationCodeError = "user.verification_code_error"
MsgUserEmailAlreadyTaken = "user.email_already_taken"
MsgUserPasswordUnset = "user.password_unset"
MsgUserPasswordResetLinkInvalid = "user.password_reset_link_invalid"
MsgUserInputInvalid = "user.input_invalid"
MsgUserNoPermissionSameLevel = "user.no_permission_same_level"
MsgUserNoPermissionHigherLevel = "user.no_permission_higher_level"
MsgUserCannotCreateHigherLevel = "user.cannot_create_higher_level"
MsgUserCannotDeleteRootUser = "user.cannot_delete_root_user"
MsgUserCannotDisableRootUser = "user.cannot_disable_root_user"
MsgUserCannotDemoteRootUser = "user.cannot_demote_root_user"
MsgUserAlreadyAdmin = "user.already_admin"
MsgUserAlreadyCommon = "user.already_common"
MsgUserAdminCannotPromote = "user.admin_cannot_promote"
MsgUserOriginalPasswordError = "user.original_password_error"
MsgUserInviteQuotaInsufficient = "user.invite_quota_insufficient"
MsgUserTransferQuotaMinimum = "user.transfer_quota_minimum"
MsgUserTransferSuccess = "user.transfer_success"
MsgUserTransferFailed = "user.transfer_failed"
MsgUserTopUpProcessing = "user.topup_processing"
MsgUserRegisterFailed = "user.register_failed"
MsgUserDefaultTokenFailed = "user.default_token_failed"
MsgUserAffCodeEmpty = "user.aff_code_empty"
MsgUserEmailEmpty = "user.email_empty"
MsgUserGitHubIdEmpty = "user.github_id_empty"
MsgUserDiscordIdEmpty = "user.discord_id_empty"
MsgUserOidcIdEmpty = "user.oidc_id_empty"
MsgUserWeChatIdEmpty = "user.wechat_id_empty"
MsgUserTelegramIdEmpty = "user.telegram_id_empty"
MsgUserTelegramNotBound = "user.telegram_not_bound"
MsgUserLinuxDOIdEmpty = "user.linux_do_id_empty"
MsgUserQuotaChangeZero = "user.quota_change_zero"
)
// Quota related messages
const (
MsgQuotaNegative = "quota.negative"
MsgQuotaExceedMax = "quota.exceed_max"
MsgQuotaInsufficient = "quota.insufficient"
MsgQuotaWarningInvalid = "quota.warning_invalid"
MsgQuotaThresholdGtZero = "quota.threshold_gt_zero"
)
// Subscription related messages
const (
MsgSubscriptionNotEnabled = "subscription.not_enabled"
MsgSubscriptionTitleEmpty = "subscription.title_empty"
MsgSubscriptionPriceNegative = "subscription.price_negative"
MsgSubscriptionPriceMax = "subscription.price_max"
MsgSubscriptionPurchaseLimitNeg = "subscription.purchase_limit_negative"
MsgSubscriptionQuotaNegative = "subscription.quota_negative"
MsgSubscriptionGroupNotExists = "subscription.group_not_exists"
MsgSubscriptionResetCycleGtZero = "subscription.reset_cycle_gt_zero"
MsgSubscriptionPurchaseMax = "subscription.purchase_max"
MsgSubscriptionInvalidId = "subscription.invalid_id"
MsgSubscriptionInvalidUserId = "subscription.invalid_user_id"
)
// Payment related messages
const (
MsgPaymentNotConfigured = "payment.not_configured"
MsgPaymentMethodNotExists = "payment.method_not_exists"
MsgPaymentCallbackError = "payment.callback_error"
MsgPaymentCreateFailed = "payment.create_failed"
MsgPaymentStartFailed = "payment.start_failed"
MsgPaymentAmountTooLow = "payment.amount_too_low"
MsgPaymentStripeNotConfig = "payment.stripe_not_configured"
MsgPaymentWebhookNotConfig = "payment.webhook_not_configured"
MsgPaymentPriceIdNotConfig = "payment.price_id_not_configured"
MsgPaymentCreemNotConfig = "payment.creem_not_configured"
MsgPaymentComplianceRequired = "payment.compliance_required"
)
// Topup related messages
const (
MsgTopupNotProvided = "topup.not_provided"
MsgTopupOrderNotExists = "topup.order_not_exists"
MsgTopupOrderStatus = "topup.order_status"
MsgTopupFailed = "topup.failed"
MsgTopupInvalidQuota = "topup.invalid_quota"
)
// Channel related messages
const (
MsgChannelNotExists = "channel.not_exists"
MsgChannelIdFormatError = "channel.id_format_error"
MsgChannelNoAvailableKey = "channel.no_available_key"
MsgChannelGetListFailed = "channel.get_list_failed"
MsgChannelGetTagsFailed = "channel.get_tags_failed"
MsgChannelGetKeyFailed = "channel.get_key_failed"
MsgChannelGetOllamaFailed = "channel.get_ollama_failed"
MsgChannelQueryFailed = "channel.query_failed"
MsgChannelNoValidUpstream = "channel.no_valid_upstream"
MsgChannelUpstreamSaturated = "channel.upstream_saturated"
MsgChannelGetAvailableFailed = "channel.get_available_failed"
)
// Model related messages
const (
MsgModelNameEmpty = "model.name_empty"
MsgModelNameExists = "model.name_exists"
MsgModelIdMissing = "model.id_missing"
MsgModelGetListFailed = "model.get_list_failed"
MsgModelGetFailed = "model.get_failed"
MsgModelResetSuccess = "model.reset_success"
)
// Vendor related messages
const (
MsgVendorNameEmpty = "vendor.name_empty"
MsgVendorNameExists = "vendor.name_exists"
MsgVendorIdMissing = "vendor.id_missing"
)
// Group related messages
const (
MsgGroupNameTypeEmpty = "group.name_type_empty"
MsgGroupNameExists = "group.name_exists"
MsgGroupIdMissing = "group.id_missing"
)
// Checkin related messages
const (
MsgCheckinDisabled = "checkin.disabled"
MsgCheckinAlreadyToday = "checkin.already_today"
MsgCheckinFailed = "checkin.failed"
MsgCheckinQuotaFailed = "checkin.quota_failed"
)
// Passkey related messages
const (
MsgPasskeyCreateFailed = "passkey.create_failed"
MsgPasskeyLoginAbnormal = "passkey.login_abnormal"
MsgPasskeyUpdateFailed = "passkey.update_failed"
MsgPasskeyInvalidUserId = "passkey.invalid_user_id"
MsgPasskeyVerifyFailed = "passkey.verify_failed"
MsgPasskeyRPIDInvalid = "passkey.rp_id_invalid"
MsgPasskeyRPIDUnavailable = "passkey.rp_id_unavailable"
MsgPasskeyRPIDRemovalConfirmation = "passkey.rp_id_removal_confirmation"
)
// 2FA related messages
const (
MsgTwoFANotEnabled = "twofa.not_enabled"
MsgTwoFAUserIdEmpty = "twofa.user_id_empty"
MsgTwoFAAlreadyExists = "twofa.already_exists"
MsgTwoFARecordIdEmpty = "twofa.record_id_empty"
MsgTwoFACodeInvalid = "twofa.code_invalid"
)
// Rate limit related messages
const (
MsgRateLimitReached = "rate_limit.reached"
MsgRateLimitTotalReached = "rate_limit.total_reached"
)
// Setting related messages
const (
MsgSettingInvalidType = "setting.invalid_type"
MsgSettingWebhookEmpty = "setting.webhook_empty"
MsgSettingWebhookInvalid = "setting.webhook_invalid"
MsgSettingEmailInvalid = "setting.email_invalid"
MsgSettingBarkUrlEmpty = "setting.bark_url_empty"
MsgSettingBarkUrlInvalid = "setting.bark_url_invalid"
MsgSettingGotifyUrlEmpty = "setting.gotify_url_empty"
MsgSettingGotifyTokenEmpty = "setting.gotify_token_empty"
MsgSettingGotifyUrlInvalid = "setting.gotify_url_invalid"
MsgSettingUrlMustHttp = "setting.url_must_http"
MsgSettingSaved = "setting.saved"
)
// Deployment related messages (io.net)
const (
MsgDeploymentNotEnabled = "deployment.not_enabled"
MsgDeploymentIdRequired = "deployment.id_required"
MsgDeploymentContainerIdReq = "deployment.container_id_required"
MsgDeploymentNameEmpty = "deployment.name_empty"
MsgDeploymentNameTaken = "deployment.name_taken"
MsgDeploymentHardwareIdReq = "deployment.hardware_id_required"
MsgDeploymentHardwareInvId = "deployment.hardware_invalid_id"
MsgDeploymentApiKeyRequired = "deployment.api_key_required"
MsgDeploymentInvalidPayload = "deployment.invalid_payload"
MsgDeploymentNotFound = "deployment.not_found"
)
// Performance related messages
const (
MsgPerfDiskCacheCleared = "performance.disk_cache_cleared"
MsgPerfStatsReset = "performance.stats_reset"
MsgPerfGcExecuted = "performance.gc_executed"
)
// Ability related messages
const (
MsgAbilityDbCorrupted = "ability.db_corrupted"
MsgAbilityRepairRunning = "ability.repair_running"
)
// OAuth related messages
const (
MsgOAuthInvalidCode = "oauth.invalid_code"
MsgOAuthGetUserErr = "oauth.get_user_error"
MsgOAuthAccountUsed = "oauth.account_used"
MsgOAuthUnknownProvider = "oauth.unknown_provider"
MsgOAuthStateInvalid = "oauth.state_invalid"
MsgOAuthNotEnabled = "oauth.not_enabled"
MsgOAuthUserDeleted = "oauth.user_deleted"
MsgOAuthUserBanned = "oauth.user_banned"
MsgOAuthBindSuccess = "oauth.bind_success"
MsgOAuthAlreadyBound = "oauth.already_bound"
MsgOAuthNotAutoLinked = "oauth.not_auto_linked"
MsgOAuthConnectFailed = "oauth.connect_failed"
MsgOAuthTokenFailed = "oauth.token_failed"
MsgOAuthUserInfoEmpty = "oauth.user_info_empty"
MsgOAuthTrustLevelLow = "oauth.trust_level_low"
)
// Model layer error messages (for translation in controller)
const (
MsgRedeemFailed = "redeem.failed"
MsgCreateDefaultTokenErr = "user.create_default_token_error"
MsgUuidDuplicate = "common.uuid_duplicate"
MsgInvalidInput = "common.invalid_input"
)
// Distributor related messages
const (
MsgDistributorInvalidRequest = "distributor.invalid_request"
MsgDistributorInvalidChannelId = "distributor.invalid_channel_id"
MsgDistributorChannelDisabled = "distributor.channel_disabled"
MsgDistributorAffinityChannelDisabled = "distributor.affinity_channel_disabled"
MsgDistributorTokenNoModelAccess = "distributor.token_no_model_access"
MsgDistributorTokenModelForbidden = "distributor.token_model_forbidden"
MsgDistributorModelNameRequired = "distributor.model_name_required"
@@ -329,17 +33,83 @@ const (
MsgDistributorGetChannelFailed = "distributor.get_channel_failed"
MsgDistributorNoAvailableChannel = "distributor.no_available_channel"
MsgDistributorNoAvailableChannelTaskPlugin = "distributor.no_available_channel_task_plugin"
MsgDistributorInvalidMidjourney = "distributor.invalid_midjourney_request"
MsgDistributorInvalidParseModel = "distributor.invalid_request_parse_model"
MsgQuotaUserInsufficient = "quota.user_insufficient"
MsgQuotaPreConsumeInsufficient = "quota.pre_consume_insufficient"
MsgQuotaSubscriptionInsufficient = "quota.subscription_insufficient"
MsgRelayModelPriceNotConfigured = "relay.model_price_not_configured"
MsgRelayModelPriceNotConfiguredAdmin = "relay.model_price_not_configured_admin"
MsgRelayTaskChannelDisabled = "relay.task_channel_disabled"
MsgRelayGroupSaturated = "relay.group_saturated"
MsgRelayGroupUpstreamSaturated = "relay.group_upstream_saturated"
MsgRelayRetryGetChannelFailed = "relay.retry_get_channel_failed"
MsgRelayRetryNoAvailableChannel = "relay.retry_no_available_channel"
MsgRelayAccessTokenUnsupported = "relay.access_token_unsupported"
)
// Custom OAuth provider related messages
// Emails and the name of the token created at registration, in the
// recipient's language
const (
MsgCustomOAuthNotFound = "custom_oauth.not_found"
MsgCustomOAuthSlugEmpty = "custom_oauth.slug_empty"
MsgCustomOAuthSlugExists = "custom_oauth.slug_exists"
MsgCustomOAuthNameEmpty = "custom_oauth.name_empty"
MsgCustomOAuthHasBindings = "custom_oauth.has_bindings"
MsgCustomOAuthBindingNotFound = "custom_oauth.binding_not_found"
MsgCustomOAuthProviderIdInvalid = "custom_oauth.provider_id_field_invalid"
MsgUserDefaultTokenName = "user.default_token_name"
MsgEmailVerificationSubject = "email.verification.subject"
MsgEmailVerificationGreeting = "email.verification.greeting"
MsgEmailVerificationCode = "email.verification.code"
MsgEmailVerificationValidity = "email.verification.validity"
MsgEmailPasswordResetSubject = "email.password_reset.subject"
MsgEmailPasswordResetGreeting = "email.password_reset.greeting"
MsgEmailPasswordResetLink = "email.password_reset.link"
MsgEmailPasswordResetLinkFallback = "email.password_reset.link_fallback"
MsgEmailPasswordResetValidity = "email.password_reset.validity"
MsgEmailBindingSubject = "email.binding.subject"
MsgEmailBindingNewIntro = "email.binding.new_intro"
MsgEmailBindingOldIntro = "email.binding.old_intro"
MsgEmailBindingCode = "email.binding.code"
MsgEmailBindingNewValidity = "email.binding.new_validity"
MsgEmailBindingOldValidity = "email.binding.old_validity"
MsgEmailSecurityNoticeSubject = "email.security_notice.subject"
MsgEmailSecurityNoticeChanged = "email.security_notice.changed"
MsgEmailSecurityNoticeAdvice = "email.security_notice.advice"
MsgSecurityEventPasswordUpdated = "email.security_event.password_updated"
MsgSecurityEventEmailChangeRequested = "email.security_event.email_change_requested"
MsgSecurityEventEmailChanged = "email.security_event.email_changed"
MsgSecurityEventEmailConfirmed = "email.security_event.email_confirmed"
MsgSecurityEventLoginLinked = "email.security_event.login_linked"
MsgSecurityEventLoginUnlinked = "email.security_event.login_unlinked"
MsgSecurityEventWeChatLinked = "email.security_event.wechat_linked"
)
// Low-quota notices, in the recipient's saved language
const (
MsgNotifyQuotaLowTitle = "notify.quota_low_title"
MsgNotifySubscriptionQuotaLowTitle = "notify.subscription_quota_low_title"
MsgNotifyQuotaLowBark = "notify.quota_low_bark"
MsgNotifyQuotaLowGotify = "notify.quota_low_gotify"
MsgNotifyQuotaLowEmail = "notify.quota_low_email"
)
// Channel notices sent to administrators who receive them and to model update
// watchers
const (
MsgChannelNotifyDisabledSubject = "channel.notify_disabled_subject"
MsgChannelNotifyDisabledContent = "channel.notify_disabled_content"
MsgChannelNotifyEnabledSubject = "channel.notify_enabled_subject"
MsgChannelNotifyEnabledContent = "channel.notify_enabled_content"
MsgChannelTestCompletedSubject = "channel.test_completed_subject"
MsgChannelTestCompletedContent = "channel.test_completed_content"
MsgChannelResponseTimeExceeded = "channel.response_time_exceeded"
MsgChannelUpstreamUpdateNotifySubject = "channel.upstream_update_notify_subject"
MsgChannelUpstreamUpdateSummary = "channel.upstream_update_summary"
MsgChannelUpstreamUpdateChangedChannels = "channel.upstream_update_changed_channels"
MsgChannelUpstreamUpdateMoreChannels = "channel.upstream_update_more_channels"
MsgChannelUpstreamUpdateAddedModels = "channel.upstream_update_added_models"
MsgChannelUpstreamUpdateRemovedModels = "channel.upstream_update_removed_models"
MsgChannelUpstreamUpdateFailedChannels = "channel.upstream_update_failed_channels"
MsgChannelUpstreamUpdateMoreOmitted = "channel.upstream_update_more_omitted"
)
+70 -272
View File
@@ -1,277 +1,22 @@
# English translations
# Task plugin messages
task_plugin.unknown_meta_field: 'Plugin metadata contains an unknown field "{{.Field}}". If this plugin was downloaded from the official marketplace, it may require a newer version of new-api. Try updating new-api and installing the plugin again.'
# Common messages
common.invalid_params: "Invalid parameters"
# Errors returned to AI clients
common.database_error: "Database error, please contact the administrator"
common.retry_later: "Please try again later"
common.generate_failed: "Generation failed"
common.not_found: "Not found"
common.unauthorized: "Unauthorized"
common.forbidden: "Forbidden"
common.invalid_id: "Invalid ID"
common.id_empty: "ID is empty!"
common.feature_disabled: "This feature is not enabled"
common.operation_success: "Operation successful"
common.operation_failed: "Operation failed"
common.update_success: "Update successful"
common.update_failed: "Update failed"
common.create_success: "Creation successful"
common.create_failed: "Creation failed"
common.delete_success: "Deletion successful"
common.delete_failed: "Deletion failed"
common.already_exists: "Already exists"
common.name_cannot_be_empty: "Name cannot be empty"
common.batch_too_many: "Too many items in batch request, maximum is {{.Max}}"
# Auth middleware messages
auth.not_logged_in: "Unauthorized, not logged in and no access token provided"
auth.access_token_invalid: "Unauthorized, invalid access token"
auth.user_info_invalid: "Unauthorized, invalid user info"
auth.user_id_not_provided: "Unauthorized, New-Api-User header not provided"
auth.user_id_format_error: "Unauthorized, New-Api-User header format error"
auth.user_id_mismatch: "Unauthorized, New-Api-User does not match logged in user"
auth.user_banned: "User has been banned"
auth.insufficient_privilege: "Unauthorized, insufficient privileges"
# Token messages
token.name_too_long: "Token name is too long"
token.quota_negative: "Quota value cannot be negative"
token.quota_exceed_max: "Quota value exceeds valid range, maximum is {{.Max}}"
token.generate_failed: "Failed to generate token"
token.get_info_failed: "Failed to get token info, please try again later"
token.expired_cannot_enable: "Token has expired and cannot be enabled. Please modify the expiration time or set it to never expire"
token.exhausted_cannot_enable: "Token quota is exhausted and cannot be enabled. Please modify the remaining quota or set it to unlimited"
token.invalid: "Invalid token"
token.not_provided: "Token not provided"
token.expired: "This token has expired"
token.exhausted: "This token quota is exhausted TokenStatusExhausted[sk-{{.Prefix}}***{{.Suffix}}]"
token.status_unavailable: "This token status is unavailable"
token.db_error: "Invalid token, database query error, please contact administrator"
token.auto_groups_too_many: "A token can select at most {{.Max}} Auto groups"
token.auto_groups_duplicate: "Auto group {{.Group}} is duplicated"
token.auto_groups_invalid: "Auto group {{.Group}} is unavailable or unauthorized"
# Redemption messages
redemption.name_length: "Redemption code name length must be between 1-20"
redemption.count_positive: "Redemption code count must be greater than 0"
redemption.count_max: "Maximum 100 redemption codes can be generated at once"
redemption.create_failed: "Failed to create redemption code, please try again later"
redemption.invalid: "Invalid redemption code"
redemption.used: "This redemption code has been used"
redemption.expired: "This redemption code has expired"
redemption.failed: "Redemption failed, please try again later"
redemption.not_provided: "Redemption code not provided"
redemption.expire_time_invalid: "Expiration time cannot be earlier than current time"
# User messages
user.password_login_disabled: "Password login has been disabled by administrator"
user.register_disabled: "New user registration has been disabled by administrator"
user.password_register_disabled: "Password registration has been disabled by administrator, please use third-party account verification"
user.username_or_password_empty: "Username or password is empty"
user.username_or_password_error: "Username or password is incorrect, or user has been banned"
user.email_or_password_empty: "Email or password is empty!"
user.exists: "Username already exists or has been deleted"
user.not_exists: "User does not exist"
user.disabled: "This user has been disabled"
user.session_save_failed: "Failed to save session, please try again"
user.require_2fa: "Please enter two-factor authentication code"
user.email_verification_required: "Email verification is enabled, please enter email address and verification code"
user.verification_code_error: "Verification code is incorrect or has expired"
user.email_already_taken: "Email address is already in use"
user.password_unset: "This account has no password set. Please use password reset or contact an administrator to reset it."
user.password_reset_link_invalid: "Password reset link is invalid or has expired"
user.input_invalid: "Invalid input {{.Error}}"
user.no_permission_same_level: "No permission to access users of same or higher level"
user.no_permission_higher_level: "No permission to update users of same or higher permission level"
user.cannot_create_higher_level: "Cannot create users with permission level equal to or higher than yourself"
user.cannot_delete_root_user: "Cannot delete super administrator account"
user.cannot_disable_root_user: "Cannot disable super administrator user"
user.cannot_demote_root_user: "Cannot demote super administrator user"
user.already_admin: "This user is already an administrator"
user.already_common: "This user is already a common user"
user.admin_cannot_promote: "Regular administrators cannot promote other users to administrator"
user.original_password_error: "Original password is incorrect"
user.invite_quota_insufficient: "Invitation quota is insufficient!"
user.transfer_quota_minimum: "Minimum transfer quota is {{.Min}}!"
user.transfer_success: "Transfer successful"
user.transfer_failed: "Transfer failed {{.Error}}"
user.topup_processing: "Top-up is processing, please try again later"
user.register_failed: "User registration failed or user ID retrieval failed"
user.default_token_failed: "Failed to generate default token"
user.aff_code_empty: "Affiliate code is empty!"
user.email_empty: "Email is empty!"
user.github_id_empty: "GitHub ID is empty!"
user.discord_id_empty: "Discord ID is empty!"
user.oidc_id_empty: "OIDC ID is empty!"
user.wechat_id_empty: "WeChat ID is empty!"
user.telegram_id_empty: "Telegram ID is empty!"
user.telegram_not_bound: "This Telegram account is not bound"
user.linux_do_id_empty: "Linux DO ID is empty!"
user.quota_change_zero: "Quota change amount cannot be zero"
# Quota messages
quota.negative: "Quota cannot be negative!"
quota.exceed_max: "Quota value exceeds valid range"
quota.insufficient: "Insufficient quota"
quota.warning_invalid: "Invalid warning type"
quota.threshold_gt_zero: "Warning threshold must be greater than 0"
# Subscription messages
subscription.not_enabled: "Subscription plan is not enabled"
subscription.title_empty: "Subscription plan title cannot be empty"
subscription.price_negative: "Price cannot be negative"
subscription.price_max: "Price cannot exceed 9999"
subscription.purchase_limit_negative: "Purchase limit cannot be negative"
subscription.quota_negative: "Total quota cannot be negative"
subscription.group_not_exists: "Upgrade group does not exist"
subscription.reset_cycle_gt_zero: "Custom reset cycle must be greater than 0 seconds"
subscription.purchase_max: "Purchase limit for this plan has been reached"
subscription.invalid_id: "Invalid subscription ID"
subscription.invalid_user_id: "Invalid user ID"
# Payment messages
payment.not_configured: "Payment information has not been configured by administrator"
payment.method_not_exists: "Payment method does not exist"
payment.callback_error: "Callback URL configuration error"
payment.create_failed: "Failed to create order"
payment.start_failed: "Failed to start payment"
payment.amount_too_low: "Plan amount is too low"
payment.stripe_not_configured: "Stripe is not configured or key is invalid"
payment.webhook_not_configured: "Webhook is not configured"
payment.price_id_not_configured: "StripePriceId is not configured for this plan"
payment.creem_not_configured: "CreemProductId is not configured for this plan"
payment.compliance_required: "Payment, redemption, subscription, and invitation reward features are disabled. The administrator must confirm compliance terms before enabling them."
# Topup messages
topup.not_provided: "Payment order number not provided"
topup.order_not_exists: "Top-up order does not exist"
topup.order_status: "Top-up order status error"
topup.failed: "Top-up failed, please try again later"
topup.invalid_quota: "Invalid top-up quota"
# Channel messages
channel.not_exists: "Channel does not exist"
channel.id_format_error: "Channel ID format error"
channel.no_available_key: "No available channel keys"
channel.get_list_failed: "Failed to get channel list, please try again later"
channel.get_tags_failed: "Failed to get tags, please try again later"
channel.get_key_failed: "Failed to get channel key"
channel.get_ollama_failed: "Failed to get Ollama models"
channel.query_failed: "Failed to query channel"
channel.no_valid_upstream: "No valid upstream channel"
channel.upstream_saturated: "Current group upstream load is saturated, please try again later"
channel.get_available_failed: "Failed to get available channels for model {{.Model}} under group {{.Group}}"
# Model messages
model.name_empty: "Model name cannot be empty"
model.name_exists: "Model name already exists"
model.id_missing: "Model ID is missing"
model.get_list_failed: "Failed to get model list, please try again later"
model.get_failed: "Failed to get upstream models"
model.reset_success: "Model ratio reset successful"
# Vendor messages
vendor.name_empty: "Vendor name cannot be empty"
vendor.name_exists: "Vendor name already exists"
vendor.id_missing: "Vendor ID is missing"
# Group messages
group.name_type_empty: "Group name and type cannot be empty"
group.name_exists: "Group name already exists"
group.id_missing: "Group ID is missing"
# Checkin messages
checkin.disabled: "Check-in feature is not enabled"
checkin.already_today: "Already checked in today"
checkin.failed: "Check-in failed, please try again later"
checkin.quota_failed: "Check-in failed: quota update error"
# Passkey messages
passkey.create_failed: "Unable to create Passkey credential"
passkey.login_abnormal: "Passkey login status is abnormal"
passkey.update_failed: "Passkey credential update failed"
passkey.invalid_user_id: "Invalid user ID"
passkey.verify_failed: "Passkey verification failed, please try again or contact administrator"
passkey.rp_id_invalid: "Invalid Passkey domain. Enter a domain without a scheme, port, path or wildcard."
passkey.rp_id_unavailable: "This Passkey domain is not available on this website. Use its original website or another verification method."
passkey.rp_id_removal_confirmation: "Review the affected Passkeys and confirm the domain removal. If the settings or impact have changed, confirmation is required again."
# 2FA messages
twofa.not_enabled: "User has not enabled 2FA"
twofa.user_id_empty: "User ID cannot be empty"
twofa.already_exists: "User already has 2FA configured"
twofa.record_id_empty: "2FA record ID cannot be empty"
twofa.code_invalid: "Verification code or backup code is incorrect"
# Rate limit messages
auth.client_ip_unresolved: "Unable to resolve the client IP address"
auth.token_ip_not_allowed: "Your IP address is not on this token's allowlist"
auth.token_group_forbidden: "No permission to access group {{.Group}}"
auth.token_group_deprecated: "Group {{.Group}} has been deprecated"
auth.channel_select_forbidden: "Regular users cannot specify a channel"
rate_limit.reached: "You have reached the request limit: maximum {{.Max}} requests in {{.Minutes}} minutes"
rate_limit.total_reached: "You have reached the total request limit: maximum {{.Max}} requests in {{.Minutes}} minutes, including failed attempts"
# Setting messages
setting.invalid_type: "Invalid warning type"
setting.webhook_empty: "Webhook URL cannot be empty"
setting.webhook_invalid: "Invalid Webhook URL"
setting.email_invalid: "Invalid email address"
setting.bark_url_empty: "Bark push URL cannot be empty"
setting.bark_url_invalid: "Invalid Bark push URL"
setting.gotify_url_empty: "Gotify server URL cannot be empty"
setting.gotify_token_empty: "Gotify token cannot be empty"
setting.gotify_url_invalid: "Invalid Gotify server URL"
setting.url_must_http: "URL must start with http:// or https://"
setting.saved: "Settings updated"
# Deployment messages (io.net)
deployment.not_enabled: "io.net model deployment is not enabled or API key is missing"
deployment.id_required: "Deployment ID is required"
deployment.container_id_required: "Container ID is required"
deployment.name_empty: "Deployment name cannot be empty"
deployment.name_taken: "Deployment name is not available, please choose a different name"
deployment.hardware_id_required: "hardware_id parameter is required"
deployment.hardware_invalid_id: "Invalid hardware_id parameter"
deployment.api_key_required: "api_key is required"
deployment.invalid_payload: "Invalid request payload"
deployment.not_found: "Container details not found"
# Performance messages
performance.disk_cache_cleared: "Inactive disk cache has been cleared"
performance.stats_reset: "Statistics have been reset"
performance.gc_executed: "GC has been executed"
# Ability messages
ability.db_corrupted: "Database consistency has been compromised"
ability.repair_running: "A repair task is already running, please try again later"
# OAuth messages
oauth.invalid_code: "Invalid authorization code"
oauth.get_user_error: "Failed to get user information"
oauth.account_used: "This account has been bound to another user"
oauth.unknown_provider: "Unknown OAuth provider"
oauth.state_invalid: "State parameter is empty or mismatched"
oauth.not_enabled: "{{.Provider}} login and registration has not been enabled by administrator"
oauth.user_deleted: "User has been deleted"
oauth.user_banned: "User has been banned"
oauth.bind_success: "Binding successful"
oauth.already_bound: "This {{.Provider}} account has already been bound"
oauth.not_auto_linked: "This {{.Provider}} account cannot be linked to an existing account automatically, please sign in or register another way and then link {{.Provider}} in account settings"
oauth.connect_failed: "Unable to connect to {{.Provider}} server, please try again later"
oauth.token_failed: "Failed to get token from {{.Provider}}, please check settings"
oauth.user_info_empty: "{{.Provider}} returned empty user info, please check settings"
oauth.trust_level_low: "Linux DO trust level does not meet the minimum required by administrator"
# Model layer error messages
redeem.failed: "Redemption failed, please try again later"
user.create_default_token_error: "Failed to create default token"
common.uuid_duplicate: "Please retry, the system generated a duplicate UUID!"
common.invalid_input: "Invalid input"
# Distributor messages
rate_limit.total_reached: "You have reached the total request limit: maximum {{.Max}} requests in {{.Minutes}} minutes, including failed attempts. Please check that your requests are correct"
distributor.invalid_request: "Invalid request: {{.Error}}"
distributor.invalid_channel_id: "Invalid channel ID"
distributor.channel_disabled: "This channel has been disabled"
distributor.affinity_channel_disabled: "The channel selected by channel affinity has been disabled, and retry was stopped by rule. Please contact the administrator"
distributor.token_no_model_access: "This token has no access to any models"
distributor.token_model_forbidden: "This token has no access to model {{.Model}}"
distributor.model_name_required: "Model name not specified, model name cannot be empty"
@@ -280,14 +25,67 @@ distributor.group_access_denied: "No permission to access this group"
distributor.get_channel_failed: "Failed to get available channel for model {{.Model}} under group {{.Group}} (distributor): {{.Error}}"
distributor.no_available_channel: "No available channel for model {{.Model}} under group {{.Group}} (distributor)"
distributor.no_available_channel_task_plugin: "No available channel for model {{.Model}} under group {{.Group}}: the model is claimed by a task plugin, which has no enabled channel serving it (distributor)"
distributor.invalid_midjourney_request: "Invalid Midjourney request: {{.Error}}"
distributor.invalid_request_parse_model: "Invalid request, unable to parse model"
distributor.invalid_request_parse_model: "unable to parse model"
quota.user_insufficient: "Insufficient user quota, remaining quota: {{.Remaining}}"
quota.pre_consume_insufficient: "Not enough quota to pre-consume for this request: {{.Remaining}} remaining, {{.Required}} required"
quota.subscription_insufficient: "Subscription quota is insufficient or no subscription is configured: {{.Error}}"
relay.model_price_not_configured: "Model {{.Model}} has not been priced by the administrator yet. Please contact the site administrator to enable this model."
relay.model_price_not_configured_admin: "Model {{.Model}} price not configured. Go to System Settings → Operation Settings to enable self-use mode, or configure the model price in System Settings → Group & Model Pricing."
relay.task_channel_disabled: "The channel of this task has been disabled"
relay.group_saturated: "The current group is at capacity. Please try again later, or upgrade your account for better service."
relay.group_upstream_saturated: "The upstream of the current group is at capacity. Please try again later"
relay.retry_get_channel_failed: "Failed to get available channel for model {{.Model}} under group {{.Group}} (retry): {{.Error}}"
relay.retry_no_available_channel: "No available channel for model {{.Model}} under group {{.Group}} (retry)"
relay.access_token_unsupported: "Access tokens are not supported here"
# Custom OAuth provider messages
custom_oauth.not_found: "Custom OAuth provider not found"
custom_oauth.slug_empty: "Slug cannot be empty"
custom_oauth.slug_exists: "Slug already exists"
custom_oauth.name_empty: "Provider name cannot be empty"
custom_oauth.has_bindings: "Cannot delete provider with existing user bindings"
custom_oauth.binding_not_found: "OAuth binding not found"
custom_oauth.provider_id_field_invalid: "Could not extract user ID from provider response"
# Emails and the name of the token created at registration
user.default_token_name: "{{.Username}}'s initial token"
email.verification.subject: "{{.SystemName}} email verification"
email.verification.greeting: "Hello, you are verifying your email address for {{.SystemName}}."
email.verification.code: "Your verification code is: <strong>{{.Code}}</strong>"
email.verification.validity: "The verification code is valid for {{.Minutes}} minutes. If you did not request it, please ignore this email."
email.password_reset.subject: "{{.SystemName}} password reset"
email.password_reset.greeting: "Hello, you are resetting your password for {{.SystemName}}."
email.password_reset.link: "Click <a href='{{.Link}}'>here</a> to reset your password."
email.password_reset.link_fallback: "If the link does not open, copy the link below into your browser:<br> {{.Link}}"
email.password_reset.validity: "The reset link is valid for {{.Minutes}} minutes. If you did not request it, please ignore this email."
email.binding.subject: "{{.SystemName}} — Confirm your email address"
email.binding.new_intro: "Confirm linking this email address to your account."
email.binding.old_intro: "A change to your account email address was requested. Confirm replacing your current address."
email.binding.code: "Verification code: <strong>{{.Code}}</strong>"
email.binding.new_validity: "This code expires in {{.Minutes}} minutes. If you did not request this change, do not share this code."
email.binding.old_validity: "This code expires in {{.Minutes}} minutes. If you did not request this change, do not share this code and contact your administrator."
email.security_notice.subject: "{{.SystemName}} — Account security notification"
email.security_notice.changed: "Your account security settings have changed: {{.Event}}."
email.security_notice.advice: "If you did not make this change, open your account security settings, revoke other login sessions, and contact your administrator."
email.security_event.password_updated: "Password updated"
email.security_event.email_change_requested: "A change of your email address was requested"
email.security_event.email_changed: "Email address changed"
email.security_event.email_confirmed: "Email address confirmed"
email.security_event.login_linked: "Login account linked: {{.Provider}}"
email.security_event.login_unlinked: "Login account unlinked"
email.security_event.wechat_linked: "WeChat account linked"
# Low-quota notices
notify.quota_low_title: "Your quota is running low"
notify.subscription_quota_low_title: "Your subscription quota is running low"
notify.quota_low_bark: "{{.Prompt}}, remaining quota: {{.Quota}}. Please top up soon"
notify.quota_low_gotify: "{{.Prompt}}. Your remaining quota is {{.Quota}}. Please top up soon."
notify.quota_low_email: "{{.Prompt}}. Your remaining quota is {{.Quota}}. Top up soon to keep using the service.<br/>Top-up link: <a href='{{.Link}}'>{{.Link}}</a>"
# Channel notices sent to administrators who receive them and to model update watchers
channel.notify_disabled_subject: "Channel \"{{.Name}}\" (#{{.Id}}) has been disabled"
channel.notify_disabled_content: "Channel \"{{.Name}}\" (#{{.Id}}) has been disabled. Reason: {{.Reason}}"
channel.notify_enabled_subject: "Channel \"{{.Name}}\" (#{{.Id}}) has been enabled"
channel.notify_enabled_content: "Channel \"{{.Name}}\" (#{{.Id}}) has been enabled"
channel.test_completed_subject: "Channel test completed"
channel.test_completed_content: "All channel tests have completed"
channel.response_time_exceeded: "response time {{.Seconds}}s exceeds the threshold of {{.Threshold}}s"
channel.upstream_update_notify_subject: "Upstream model check notice"
channel.upstream_update_summary: "Upstream model check summary: {{.Checked}} channels checked, {{.Changed}} changed, {{.Added}} models added, {{.Removed}} models removed, {{.AutoAdded}} added automatically, {{.Failed}} failed."
channel.upstream_update_changed_channels: "Changed channels (showing {{.Shown}}/{{.Total}}):"
channel.upstream_update_more_channels: "{{.Count}} more channels omitted"
channel.upstream_update_added_models: "Added model examples (showing {{.Shown}}/{{.Total}}): {{.Models}}"
channel.upstream_update_removed_models: "Removed model examples (showing {{.Shown}}/{{.Total}}): {{.Models}}"
channel.upstream_update_failed_channels: "Failed channel IDs (showing {{.Shown}}/{{.Total}}): {{.Ids}}"
channel.upstream_update_more_omitted: " ({{.Count}} more omitted)"
+70 -272
View File
@@ -1,278 +1,23 @@
# Chinese (Simplified) translations
# 中文(简体)翻译文件
# Task plugin messages
task_plugin.unknown_meta_field: '插件元数据包含未知字段“{{.Field}}”。如果插件来自官方市场,可能需要更高版本的 new-api。请尝试更新 new-api 后重新安装插件。'
# Common messages
common.invalid_params: "无效的参数"
# Errors returned to AI clients
common.database_error: "数据库出错,请联系管理员"
common.retry_later: "请稍后重试"
common.generate_failed: "生成失败"
common.not_found: "未找到"
common.unauthorized: "未授权"
common.forbidden: "无权限"
common.invalid_id: "无效的ID"
common.id_empty: "ID 为空!"
common.feature_disabled: "该功能未启用"
common.operation_success: "操作成功"
common.operation_failed: "操作失败"
common.update_success: "更新成功"
common.update_failed: "更新失败"
common.create_success: "创建成功"
common.create_failed: "创建失败"
common.delete_success: "删除成功"
common.delete_failed: "删除失败"
common.already_exists: "已存在"
common.name_cannot_be_empty: "名称不能为空"
common.batch_too_many: "批量请求数量过多,最多 {{.Max}} 条"
# Auth middleware messages
auth.not_logged_in: "无权进行此操作,未登录且未提供 access token"
auth.access_token_invalid: "无权进行此操作,access token 无效"
auth.user_info_invalid: "无权进行此操作,用户信息无效"
auth.user_id_not_provided: "无权进行此操作,未提供 New-Api-User"
auth.user_id_format_error: "无权进行此操作,New-Api-User 格式错误"
auth.user_id_mismatch: "无权进行此操作,New-Api-User 与登录用户不匹配"
auth.user_banned: "用户已被封禁"
auth.insufficient_privilege: "无权进行此操作,权限不足"
# Token messages
token.name_too_long: "令牌名称过长"
token.quota_negative: "额度值不能为负数"
token.quota_exceed_max: "额度值超出有效范围,最大值为 {{.Max}}"
token.generate_failed: "生成令牌失败"
token.get_info_failed: "获取令牌信息失败,请稍后重试"
token.expired_cannot_enable: "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期"
token.exhausted_cannot_enable: "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度"
token.invalid: "无效的令牌"
token.not_provided: "未提供令牌"
token.expired: "该令牌已过期"
token.exhausted: "该令牌额度已用尽 TokenStatusExhausted[sk-{{.Prefix}}***{{.Suffix}}]"
token.status_unavailable: "该令牌状态不可用"
token.db_error: "无效的令牌,数据库查询出错,请联系管理员"
token.auto_groups_too_many: "每个令牌最多可选择 {{.Max}} 个 Auto 分组"
token.auto_groups_duplicate: "Auto 分组 {{.Group}} 重复"
token.auto_groups_invalid: "Auto 分组 {{.Group}} 不可用或无权访问"
# Redemption messages
redemption.name_length: "兑换码名称长度必须在1-20之间"
redemption.count_positive: "兑换码个数必须大于0"
redemption.count_max: "一次兑换码批量生成的个数不能大于 100"
redemption.create_failed: "创建兑换码失败,请稍后重试"
redemption.invalid: "无效的兑换码"
redemption.used: "该兑换码已被使用"
redemption.expired: "该兑换码已过期"
redemption.failed: "兑换失败,请稍后重试"
redemption.not_provided: "未提供兑换码"
redemption.expire_time_invalid: "过期时间不能早于当前时间"
# User messages
user.password_login_disabled: "管理员关闭了密码登录"
user.register_disabled: "管理员关闭了新用户注册"
user.password_register_disabled: "管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册"
user.username_or_password_empty: "用户名或密码为空"
user.username_or_password_error: "用户名或密码错误,或用户已被封禁"
user.email_or_password_empty: "邮箱地址或密码为空!"
user.exists: "用户名已存在,或已注销"
user.not_exists: "用户不存在"
user.disabled: "该用户已被禁用"
user.session_save_failed: "无法保存会话信息,请重试"
user.require_2fa: "请输入两步验证码"
user.email_verification_required: "管理员开启了邮箱验证,请输入邮箱地址和验证码"
user.verification_code_error: "验证码错误或已过期"
user.email_already_taken: "邮箱地址已被占用"
user.password_unset: "当前账号未设置密码,请使用密码重置或联系管理员重置密码"
user.password_reset_link_invalid: "重置链接非法或已过期"
user.input_invalid: "输入不合法 {{.Error}}"
user.no_permission_same_level: "无权获取同级或更高等级用户的信息"
user.no_permission_higher_level: "无权更新同权限等级或更高权限等级的用户信息"
user.cannot_create_higher_level: "无法创建权限大于等于自己的用户"
user.cannot_delete_root_user: "不能删除超级管理员账户"
user.cannot_disable_root_user: "无法禁用超级管理员用户"
user.cannot_demote_root_user: "无法降级超级管理员用户"
user.already_admin: "该用户已经是管理员"
user.already_common: "该用户已经是普通用户"
user.admin_cannot_promote: "普通管理员用户无法提升其他用户为管理员"
user.original_password_error: "原密码错误"
user.invite_quota_insufficient: "邀请额度不足!"
user.transfer_quota_minimum: "转移额度最小为{{.Min}}!"
user.transfer_success: "划转成功"
user.transfer_failed: "划转失败 {{.Error}}"
user.topup_processing: "充值处理中,请稍后重试"
user.register_failed: "用户注册失败或用户ID获取失败"
user.default_token_failed: "生成默认令牌失败"
user.aff_code_empty: "affCode 为空!"
user.email_empty: "email 为空!"
user.github_id_empty: "GitHub id 为空!"
user.discord_id_empty: "discord id 为空!"
user.oidc_id_empty: "oidc id 为空!"
user.wechat_id_empty: "WeChat id 为空!"
user.telegram_id_empty: "Telegram id 为空!"
user.telegram_not_bound: "该 Telegram 账户未绑定"
user.linux_do_id_empty: "Linux DO id 为空!"
user.quota_change_zero: "额度变更量不能为0"
# Quota messages
quota.negative: "额度不能为负数!"
quota.exceed_max: "额度值超出有效范围"
quota.insufficient: "额度不足"
quota.warning_invalid: "无效的预警类型"
quota.threshold_gt_zero: "预警阈值必须大于0"
# Subscription messages
subscription.not_enabled: "套餐未启用"
subscription.title_empty: "套餐标题不能为空"
subscription.price_negative: "价格不能为负数"
subscription.price_max: "价格不能超过9999"
subscription.purchase_limit_negative: "购买上限不能为负数"
subscription.quota_negative: "总额度不能为负数"
subscription.group_not_exists: "升级分组不存在"
subscription.reset_cycle_gt_zero: "自定义重置周期需大于0秒"
subscription.purchase_max: "已达到该套餐购买上限"
subscription.invalid_id: "无效的订阅ID"
subscription.invalid_user_id: "无效的用户ID"
# Payment messages
payment.not_configured: "当前管理员未配置支付信息"
payment.method_not_exists: "支付方式不存在"
payment.callback_error: "回调地址配置错误"
payment.create_failed: "创建订单失败"
payment.start_failed: "拉起支付失败"
payment.amount_too_low: "套餐金额过低"
payment.stripe_not_configured: "Stripe 未配置或密钥无效"
payment.webhook_not_configured: "Webhook 未配置"
payment.price_id_not_configured: "该套餐未配置 StripePriceId"
payment.creem_not_configured: "该套餐未配置 CreemProductId"
payment.compliance_required: "支付、兑换码、订阅计划和邀请返利功能已禁用。管理员需先确认合规声明后方可启用。"
# Topup messages
topup.not_provided: "未提供支付单号"
topup.order_not_exists: "充值订单不存在"
topup.order_status: "充值订单状态错误"
topup.failed: "充值失败,请稍后重试"
topup.invalid_quota: "无效的充值额度"
# Channel messages
channel.not_exists: "渠道不存在"
channel.id_format_error: "渠道ID格式错误"
channel.no_available_key: "没有可用的渠道密钥"
channel.get_list_failed: "获取渠道列表失败,请稍后重试"
channel.get_tags_failed: "获取标签失败,请稍后重试"
channel.get_key_failed: "获取渠道密钥失败"
channel.get_ollama_failed: "获取Ollama模型失败"
channel.query_failed: "查询渠道失败"
channel.no_valid_upstream: "无有效上游渠道"
channel.upstream_saturated: "当前分组上游负载已饱和,请稍后再试"
channel.get_available_failed: "获取分组 {{.Group}} 下模型 {{.Model}} 的可用渠道失败"
# Model messages
model.name_empty: "模型名称不能为空"
model.name_exists: "模型名称已存在"
model.id_missing: "缺少模型 ID"
model.get_list_failed: "获取模型列表失败,请稍后重试"
model.get_failed: "获取上游模型失败"
model.reset_success: "重置模型倍率成功"
# Vendor messages
vendor.name_empty: "供应商名称不能为空"
vendor.name_exists: "供应商名称已存在"
vendor.id_missing: "缺少供应商 ID"
# Group messages
group.name_type_empty: "组名称和类型不能为空"
group.name_exists: "组名称已存在"
group.id_missing: "缺少组 ID"
# Checkin messages
checkin.disabled: "签到功能未启用"
checkin.already_today: "今日已签到"
checkin.failed: "签到失败,请稍后重试"
checkin.quota_failed: "签到失败:更新额度出错"
# Passkey messages
passkey.create_failed: "无法创建 Passkey 凭证"
passkey.login_abnormal: "Passkey 登录状态异常"
passkey.update_failed: "Passkey 凭证更新失败"
passkey.invalid_user_id: "无效的用户 ID"
passkey.verify_failed: "Passkey 验证失败,请重试或联系管理员"
passkey.rp_id_invalid: "通行密钥域名无效。请填写域名,不包含协议、端口、路径或通配符。"
passkey.rp_id_unavailable: "此通行密钥域名无法在当前网站使用。请前往原网站或选择其他验证方式。"
passkey.rp_id_removal_confirmation: "请核对受影响的通行密钥并确认删除域名。配置或影响范围发生变化后,需要重新确认。"
# 2FA messages
twofa.not_enabled: "用户未启用2FA"
twofa.user_id_empty: "用户ID不能为空"
twofa.already_exists: "用户已存在2FA设置"
twofa.record_id_empty: "2FA记录ID不能为空"
twofa.code_invalid: "验证码或备用码不正确"
# Rate limit messages
auth.client_ip_unresolved: "无法解析客户端 IP 地址"
auth.token_ip_not_allowed: "您的 IP 不在令牌允许访问的列表中"
auth.token_group_forbidden: "无权访问 {{.Group}} 分组"
auth.token_group_deprecated: "分组 {{.Group}} 已被弃用"
auth.channel_select_forbidden: "普通用户不支持指定渠道"
rate_limit.reached: "您已达到请求数限制:{{.Minutes}}分钟内最多请求{{.Max}}次"
rate_limit.total_reached: "您已达到总请求数限制:{{.Minutes}}分钟内最多请求{{.Max}}次,包括失败次数"
# Setting messages
setting.invalid_type: "无效的预警类型"
setting.webhook_empty: "Webhook地址不能为空"
setting.webhook_invalid: "无效的Webhook地址"
setting.email_invalid: "无效的邮箱地址"
setting.bark_url_empty: "Bark推送URL不能为空"
setting.bark_url_invalid: "无效的Bark推送URL"
setting.gotify_url_empty: "Gotify服务器地址不能为空"
setting.gotify_token_empty: "Gotify令牌不能为空"
setting.gotify_url_invalid: "无效的Gotify服务器地址"
setting.url_must_http: "URL必须以http://或https://开头"
setting.saved: "设置已更新"
# Deployment messages (io.net)
deployment.not_enabled: "io.net 模型部署功能未启用或 API 密钥缺失"
deployment.id_required: "deployment ID 为必填项"
deployment.container_id_required: "container ID 为必填项"
deployment.name_empty: "deployment 名称不能为空"
deployment.name_taken: "deployment 名称已被使用,请选择其他名称"
deployment.hardware_id_required: "hardware_id 参数为必填项"
deployment.hardware_invalid_id: "无效的 hardware_id 参数"
deployment.api_key_required: "api_key 为必填项"
deployment.invalid_payload: "无效的请求内容"
deployment.not_found: "未找到容器详情"
# Performance messages
performance.disk_cache_cleared: "不活跃的磁盘缓存已清理"
performance.stats_reset: "统计信息已重置"
performance.gc_executed: "GC 已执行"
# Ability messages
ability.db_corrupted: "数据库一致性被破坏"
ability.repair_running: "已经有一个修复任务在运行中,请稍后再试"
# OAuth messages
oauth.invalid_code: "无效的授权码"
oauth.get_user_error: "获取用户信息失败"
oauth.account_used: "该账户已被其他用户绑定"
oauth.unknown_provider: "未知的 OAuth 提供商"
oauth.state_invalid: "state 参数为空或不匹配"
oauth.not_enabled: "管理员未开启通过 {{.Provider}} 登录以及注册"
oauth.user_deleted: "用户已注销"
oauth.user_banned: "用户已被封禁"
oauth.bind_success: "绑定成功"
oauth.already_bound: "该 {{.Provider}} 账户已被绑定"
oauth.not_auto_linked: "无法自动将该 {{.Provider}} 账户关联到现有账户,请使用其他方式登录或注册后,在账户设置中绑定 {{.Provider}}"
oauth.connect_failed: "无法连接至 {{.Provider}} 服务器,请稍后重试"
oauth.token_failed: "{{.Provider}} 获取 Token 失败,请检查设置"
oauth.user_info_empty: "{{.Provider}} 获取用户信息为空,请检查设置"
oauth.trust_level_low: "Linux DO 信任等级未达到管理员设置的最低信任等级"
# Model layer error messages
redeem.failed: "兑换失败,请稍后重试"
user.create_default_token_error: "创建默认令牌失败"
common.uuid_duplicate: "请重试,系统生成的 UUID 竟然重复了!"
common.invalid_input: "输入不合法"
# Distributor messages
rate_limit.total_reached: "您已达到总请求数限制:{{.Minutes}}分钟内最多请求{{.Max}}次,包括失败次数,请检查您的请求是否正确"
distributor.invalid_request: "无效的请求,{{.Error}}"
distributor.invalid_channel_id: "无效的渠道 Id"
distributor.channel_disabled: "该渠道已被禁用"
distributor.affinity_channel_disabled: "渠道亲和性命中的渠道已被禁用,已按规则停止重试,请联系管理员处理"
distributor.token_no_model_access: "该令牌无权访问任何模型"
distributor.token_model_forbidden: "该令牌无权访问模型 {{.Model}}"
distributor.model_name_required: "未指定模型名称,模型名称不能为空"
@@ -281,14 +26,67 @@ distributor.group_access_denied: "无权访问该分组"
distributor.get_channel_failed: "获取分组 {{.Group}} 下模型 {{.Model}} 的可用渠道失败(distributor):{{.Error}}"
distributor.no_available_channel: "分组 {{.Group}} 下模型 {{.Model}} 无可用渠道(distributor)"
distributor.no_available_channel_task_plugin: "分组 {{.Group}} 下模型 {{.Model}} 无可用渠道:该模型由任务插件认领,但当前没有启用的渠道可服务此模型(distributor)"
distributor.invalid_midjourney_request: "无效的midjourney请求,{{.Error}}"
distributor.invalid_request_parse_model: "无效的请求,无法解析模型"
distributor.invalid_request_parse_model: "无法解析模型"
quota.user_insufficient: "用户额度不足, 剩余额度: {{.Remaining}}"
quota.pre_consume_insufficient: "预扣费额度失败, 用户剩余额度: {{.Remaining}}, 需要预扣费额度: {{.Required}}"
quota.subscription_insufficient: "订阅额度不足或未配置订阅: {{.Error}}"
relay.model_price_not_configured: "模型 {{.Model}} 的价格尚未由管理员配置,暂时无法使用,请联系站点管理员开启该模型;Model {{.Model}} has not been priced by the administrator yet. Please contact the site administrator to enable this model."
relay.model_price_not_configured_admin: "模型 {{.Model}} 的价格未配置。请前往「系统设置 → 运营设置」开启自用模式,或在「系统设置 → 分组与模型定价设置」中为该模型配置价格;Model {{.Model}} price not configured. Go to System Settings → Operation Settings to enable self-use mode, or configure the model price in System Settings → Group & Model Pricing."
relay.task_channel_disabled: "该任务所属渠道已被禁用"
relay.group_saturated: "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。"
relay.group_upstream_saturated: "当前分组上游负载已饱和,请稍后再试"
relay.retry_get_channel_failed: "获取分组 {{.Group}} 下模型 {{.Model}} 的可用渠道失败(retry): {{.Error}}"
relay.retry_no_available_channel: "分组 {{.Group}} 下模型 {{.Model}} 的可用渠道不存在(retry)"
relay.access_token_unsupported: "暂不支持使用 access token"
# Custom OAuth provider messages
custom_oauth.not_found: "自定义 OAuth 提供商不存在"
custom_oauth.slug_empty: "标识符不能为空"
custom_oauth.slug_exists: "标识符已存在"
custom_oauth.name_empty: "提供商名称不能为空"
custom_oauth.has_bindings: "无法删除已有用户绑定的提供商"
custom_oauth.binding_not_found: "OAuth 绑定不存在"
custom_oauth.provider_id_field_invalid: "无法从提供商响应中提取用户 ID"
# Emails and the name of the token created at registration
user.default_token_name: "{{.Username}}的初始令牌"
email.verification.subject: "{{.SystemName}}邮箱验证邮件"
email.verification.greeting: "您好,你正在进行{{.SystemName}}邮箱验证。"
email.verification.code: "您的验证码为: <strong>{{.Code}}</strong>"
email.verification.validity: "验证码 {{.Minutes}} 分钟内有效,如果不是本人操作,请忽略。"
email.password_reset.subject: "{{.SystemName}}密码重置"
email.password_reset.greeting: "您好,你正在进行{{.SystemName}}密码重置。"
email.password_reset.link: "点击 <a href='{{.Link}}'>此处</a> 进行密码重置。"
email.password_reset.link_fallback: "如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:<br> {{.Link}}"
email.password_reset.validity: "重置链接 {{.Minutes}} 分钟内有效,如果不是本人操作,请忽略。"
email.binding.subject: "{{.SystemName}}邮箱绑定确认"
email.binding.new_intro: "请确认将此邮箱地址绑定到您的账户。"
email.binding.old_intro: "您的账户正在申请更换邮箱地址,请确认替换当前邮箱。"
email.binding.code: "验证码:<strong>{{.Code}}</strong>"
email.binding.new_validity: "验证码 {{.Minutes}} 分钟内有效。如果不是您本人操作,请勿把验证码告诉任何人。"
email.binding.old_validity: "验证码 {{.Minutes}} 分钟内有效。如果不是您本人操作,请勿把验证码告诉任何人,并联系管理员。"
email.security_notice.subject: "{{.SystemName}}账户安全提醒"
email.security_notice.changed: "您的账户安全设置有变动:{{.Event}}。"
email.security_notice.advice: "如果不是您本人操作,请打开账户安全设置,退出其他会话,并联系管理员。"
email.security_event.password_updated: "密码已修改"
email.security_event.email_change_requested: "已申请更换邮箱地址"
email.security_event.email_changed: "邮箱地址已更换"
email.security_event.email_confirmed: "邮箱地址已确认"
email.security_event.login_linked: "已绑定登录账号 {{.Provider}}"
email.security_event.login_unlinked: "已解绑登录账号"
email.security_event.wechat_linked: "已绑定微信账号"
# Low-quota notices
notify.quota_low_title: "您的额度即将用尽"
notify.subscription_quota_low_title: "您的订阅额度即将用尽"
notify.quota_low_bark: "{{.Prompt}},剩余额度:{{.Quota}},请及时充值"
notify.quota_low_gotify: "{{.Prompt}},当前剩余额度为 {{.Quota}},请及时充值。"
notify.quota_low_email: "{{.Prompt}},当前剩余额度为 {{.Quota}},为了不影响您的使用,请及时充值。<br/>充值链接:<a href='{{.Link}}'>{{.Link}}</a>"
# Channel notices sent to administrators who receive them and to model update watchers
channel.notify_disabled_subject: "通道「{{.Name}}」(#{{.Id}})已被禁用"
channel.notify_disabled_content: "通道「{{.Name}}」(#{{.Id}})已被禁用,原因:{{.Reason}}"
channel.notify_enabled_subject: "通道「{{.Name}}」(#{{.Id}})已被启用"
channel.notify_enabled_content: "通道「{{.Name}}」(#{{.Id}})已被启用"
channel.test_completed_subject: "通道测试完成"
channel.test_completed_content: "所有通道测试已完成"
channel.response_time_exceeded: "响应时间 {{.Seconds}}s 超过阈值 {{.Threshold}}s"
channel.upstream_update_notify_subject: "上游模型巡检通知"
channel.upstream_update_summary: "上游模型巡检摘要:检测渠道 {{.Checked}} 个,发现变更 {{.Changed}} 个,新增 {{.Added}} 个,删除 {{.Removed}} 个,自动同步新增 {{.AutoAdded}} 个,失败 {{.Failed}} 个。"
channel.upstream_update_changed_channels: "变更渠道明细(展示 {{.Shown}}/{{.Total}}):"
channel.upstream_update_more_channels: "其余 {{.Count}} 个渠道已省略"
channel.upstream_update_added_models: "新增模型示例(展示 {{.Shown}}/{{.Total}}):{{.Models}}"
channel.upstream_update_removed_models: "删除模型示例(展示 {{.Shown}}/{{.Total}}):{{.Models}}"
channel.upstream_update_failed_channels: "失败渠道 ID(展示 {{.Shown}}/{{.Total}}):{{.Ids}}"
channel.upstream_update_more_omitted: "(其余 {{.Count}} 个已省略)"
+70 -272
View File
@@ -1,278 +1,23 @@
# Chinese (Traditional) translations
# 中文(繁體)翻譯檔案
# Task plugin messages
task_plugin.unknown_meta_field: '外掛中繼資料包含未知欄位「{{.Field}}」。如果外掛來自官方市集,可能需要較新版本的 new-api。請嘗試更新 new-api 後重新安裝外掛。'
# Common messages
common.invalid_params: "無效的參數"
# Errors returned to AI clients
common.database_error: "資料庫出錯,請聯繫管理員"
common.retry_later: "請稍後重試"
common.generate_failed: "生成失敗"
common.not_found: "未找到"
common.unauthorized: "未授權"
common.forbidden: "無權限"
common.invalid_id: "無效的ID"
common.id_empty: "ID 為空!"
common.feature_disabled: "該功能未啟用"
common.operation_success: "操作成功"
common.operation_failed: "操作失敗"
common.update_success: "更新成功"
common.update_failed: "更新失敗"
common.create_success: "建立成功"
common.create_failed: "建立失敗"
common.delete_success: "刪除成功"
common.delete_failed: "刪除失敗"
common.already_exists: "已存在"
common.name_cannot_be_empty: "名稱不能為空"
common.batch_too_many: "批次請求數量過多,最多 {{.Max}} 條"
# Auth middleware messages
auth.not_logged_in: "無權進行此操作,未登入且未提供 access token"
auth.access_token_invalid: "無權進行此操作,access token 無效"
auth.user_info_invalid: "無權進行此操作,使用者資訊無效"
auth.user_id_not_provided: "無權進行此操作,未提供 New-Api-User"
auth.user_id_format_error: "無權進行此操作,New-Api-User 格式錯誤"
auth.user_id_mismatch: "無權進行此操作,New-Api-User 與登入使用者不匹配"
auth.user_banned: "使用者已被封禁"
auth.insufficient_privilege: "無權進行此操作,權限不足"
# Token messages
token.name_too_long: "令牌名稱過長"
token.quota_negative: "額度值不能為負數"
token.quota_exceed_max: "額度值超出有效範圍,最大值為 {{.Max}}"
token.generate_failed: "生成令牌失敗"
token.get_info_failed: "獲取令牌資訊失敗,請稍後重試"
token.expired_cannot_enable: "令牌已過期,無法啟用,請先修改令牌過期時間,或者設定為永不過期"
token.exhausted_cannot_enable: "令牌可用額度已用盡,無法啟用,請先修改令牌剩餘額度,或者設定為無限額度"
token.invalid: "無效的令牌"
token.not_provided: "未提供令牌"
token.expired: "該令牌已過期"
token.exhausted: "該令牌額度已用盡 TokenStatusExhausted[sk-{{.Prefix}}***{{.Suffix}}]"
token.status_unavailable: "該令牌狀態不可用"
token.db_error: "無效的令牌,資料庫查詢出錯,請聯繫管理員"
token.auto_groups_too_many: "每個令牌最多可選擇 {{.Max}} 個 Auto 分組"
token.auto_groups_duplicate: "Auto 分組 {{.Group}} 重複"
token.auto_groups_invalid: "Auto 分組 {{.Group}} 不可用或無權存取"
# Redemption messages
redemption.name_length: "兌換碼名稱長度必須在1-20之間"
redemption.count_positive: "兌換碼個數必須大於0"
redemption.count_max: "一次兌換碼批量生成的個數不能大於 100"
redemption.create_failed: "建立兌換碼失敗,請稍後重試"
redemption.invalid: "無效的兌換碼"
redemption.used: "該兌換碼已被使用"
redemption.expired: "該兌換碼已過期"
redemption.failed: "兌換失敗,請稍後重試"
redemption.not_provided: "未提供兌換碼"
redemption.expire_time_invalid: "過期時間不能早於當前時間"
# User messages
user.password_login_disabled: "管理員關閉了密碼登錄"
user.register_disabled: "管理員關閉了新使用者註冊"
user.password_register_disabled: "管理員關閉了通過密碼進行註冊,請使用第三方帳號驗證的形式進行註冊"
user.username_or_password_empty: "使用者名或密碼為空"
user.username_or_password_error: "使用者名或密碼錯誤,或使用者已被封禁"
user.email_or_password_empty: "信箱位址或密碼為空!"
user.exists: "使用者名已存在,或已註銷"
user.not_exists: "使用者不存在"
user.disabled: "該使用者已被禁用"
user.session_save_failed: "無法保存對話,請重試"
user.require_2fa: "請輸入雙重驗證碼"
user.email_verification_required: "管理員開啟了信箱驗證,請輸入信箱位址和驗證碼"
user.verification_code_error: "驗證碼錯誤或已過期"
user.email_already_taken: "信箱位址已被占用"
user.password_unset: "目前帳號未設定密碼,請使用密碼重置或聯繫管理員重置密碼"
user.password_reset_link_invalid: "重置連結非法或已過期"
user.input_invalid: "輸入不合法 {{.Error}}"
user.no_permission_same_level: "無權獲取同級或更高等級使用者的資訊"
user.no_permission_higher_level: "無權更新同權限等級或更高權限等級的使用者資訊"
user.cannot_create_higher_level: "無法建立權限大於等於自己的使用者"
user.cannot_delete_root_user: "不能刪除超級管理員帳號"
user.cannot_disable_root_user: "無法禁用超級管理員使用者"
user.cannot_demote_root_user: "無法降級超級管理員使用者"
user.already_admin: "該使用者已經是管理員"
user.already_common: "該使用者已經是普通使用者"
user.admin_cannot_promote: "普通管理員使用者無法提升其他使用者為管理員"
user.original_password_error: "原密碼錯誤"
user.invite_quota_insufficient: "邀請額度不足!"
user.transfer_quota_minimum: "轉移額度最小為{{.Min}}!"
user.transfer_success: "劃轉成功"
user.transfer_failed: "劃轉失敗 {{.Error}}"
user.topup_processing: "充值處理中,請稍後重試"
user.register_failed: "使用者註冊失敗或使用者ID獲取失敗"
user.default_token_failed: "生成預設令牌失敗"
user.aff_code_empty: "affCode 為空!"
user.email_empty: "email 為空!"
user.github_id_empty: "GitHub id 為空!"
user.discord_id_empty: "discord id 為空!"
user.oidc_id_empty: "oidc id 為空!"
user.wechat_id_empty: "WeChat id 為空!"
user.telegram_id_empty: "Telegram id 為空!"
user.telegram_not_bound: "該 Telegram 帳號未綁定"
user.linux_do_id_empty: "Linux DO id 為空!"
user.quota_change_zero: "額度變更量不能為0"
# Quota messages
quota.negative: "額度不能為負數!"
quota.exceed_max: "額度值超出有效範圍"
quota.insufficient: "額度不足"
quota.warning_invalid: "無效的預警類型"
quota.threshold_gt_zero: "預警閾值必須大於0"
# Subscription messages
subscription.not_enabled: "訂閱方案未啟用"
subscription.title_empty: "訂閱方案標題不能為空"
subscription.price_negative: "價格不能為負數"
subscription.price_max: "價格不能超過9999"
subscription.purchase_limit_negative: "購買上限不能為負數"
subscription.quota_negative: "總額度不能為負數"
subscription.group_not_exists: "升級分組不存在"
subscription.reset_cycle_gt_zero: "自訂重置週期需大於0秒"
subscription.purchase_max: "已達到該訂閱方案購買上限"
subscription.invalid_id: "無效的訂閱ID"
subscription.invalid_user_id: "無效的使用者ID"
# Payment messages
payment.not_configured: "當前管理員未設定支付資訊"
payment.method_not_exists: "不存在此支付方式"
payment.callback_error: "回調位址設定錯誤"
payment.create_failed: "建立訂單失敗"
payment.start_failed: "啟用支付失敗"
payment.amount_too_low: "訂閱方案金額過低"
payment.stripe_not_configured: "Stripe 未設定或密鑰無效"
payment.webhook_not_configured: "Webhook 未設定"
payment.price_id_not_configured: "該訂閱方案未設定 StripePriceId"
payment.creem_not_configured: "該訂閱方案未設定 CreemProductId"
payment.compliance_required: "支付、兌換碼、訂閱方案和邀請返利功能已停用。管理員需先確認合規聲明後方可啟用。"
# Topup messages
topup.not_provided: "未提供支付單號"
topup.order_not_exists: "充值訂單不存在"
topup.order_status: "充值訂單狀態錯誤"
topup.failed: "充值失敗,請稍後重試"
topup.invalid_quota: "無效的充值額度"
# Channel messages
channel.not_exists: "管道不存在"
channel.id_format_error: "管道ID格式錯誤"
channel.no_available_key: "沒有可用的管道密鑰"
channel.get_list_failed: "獲取管道列表失敗,請稍後重試"
channel.get_tags_failed: "獲取標籤失敗,請稍後重試"
channel.get_key_failed: "獲取管道密鑰失敗"
channel.get_ollama_failed: "獲取Ollama模型失敗"
channel.query_failed: "查詢管道失敗"
channel.no_valid_upstream: "無有效上游管道"
channel.upstream_saturated: "當前分組上游負載已飽和,請稍後再試"
channel.get_available_failed: "獲取分組 {{.Group}} 下模型 {{.Model}} 的可用管道失敗"
# Model messages
model.name_empty: "模型名稱不能為空"
model.name_exists: "模型名稱已存在"
model.id_missing: "缺少模型 ID"
model.get_list_failed: "獲取模型列表失敗,請稍後重試"
model.get_failed: "獲取上游模型失敗"
model.reset_success: "重置模型倍率成功"
# Vendor messages
vendor.name_empty: "供應商名稱不能為空"
vendor.name_exists: "供應商名稱已存在"
vendor.id_missing: "缺少供應商 ID"
# Group messages
group.name_type_empty: "組名稱和類型不能為空"
group.name_exists: "組名稱已存在"
group.id_missing: "缺少組 ID"
# Checkin messages
checkin.disabled: "簽到功能未啟用"
checkin.already_today: "今日已簽到"
checkin.failed: "簽到失敗,請稍後重試"
checkin.quota_failed: "簽到失敗:更新額度出錯"
# Passkey messages
passkey.create_failed: "無法建立 Passkey 憑證"
passkey.login_abnormal: "Passkey 登錄狀態異常"
passkey.update_failed: "Passkey 憑證更新失敗"
passkey.invalid_user_id: "無效的使用者 ID"
passkey.verify_failed: "Passkey 驗證失敗,請重試或聯繫管理員"
passkey.rp_id_invalid: "通行金鑰網域無效。請填寫網域,不包含通訊協定、連接埠、路徑或萬用字元。"
passkey.rp_id_unavailable: "此通行金鑰網域無法在目前網站使用。請前往原網站或選擇其他驗證方式。"
passkey.rp_id_removal_confirmation: "請核對受影響的通行金鑰並確認刪除網域。設定或影響範圍變更後,需要重新確認。"
# 2FA messages
twofa.not_enabled: "使用者未啟用2FA"
twofa.user_id_empty: "使用者ID不能為空"
twofa.already_exists: "使用者已存在2FA設定"
twofa.record_id_empty: "2FA記錄ID不能為空"
twofa.code_invalid: "驗證碼或備用碼不正確"
# Rate limit messages
auth.client_ip_unresolved: "無法解析用戶端 IP 位址"
auth.token_ip_not_allowed: "您的 IP 不在令牌允許存取的清單中"
auth.token_group_forbidden: "無權存取 {{.Group}} 分組"
auth.token_group_deprecated: "分組 {{.Group}} 已被棄用"
auth.channel_select_forbidden: "一般使用者不支援指定管道"
rate_limit.reached: "您已達到請求數限制:{{.Minutes}}分鐘內最多請求{{.Max}}次"
rate_limit.total_reached: "您已達到總請求數限制:{{.Minutes}}分鐘內最多請求{{.Max}}次,包括失敗次數"
# Setting messages
setting.invalid_type: "無效的預警類型"
setting.webhook_empty: "Webhook位址不能為空"
setting.webhook_invalid: "無效的Webhook位址"
setting.email_invalid: "無效的信箱位址"
setting.bark_url_empty: "Bark推送URL不能為空"
setting.bark_url_invalid: "無效的Bark推送URL"
setting.gotify_url_empty: "Gotify伺服器位址不能為空"
setting.gotify_token_empty: "Gotify令牌不能為空"
setting.gotify_url_invalid: "無效的Gotify伺服器位址"
setting.url_must_http: "URL必須以http://或https://開頭"
setting.saved: "設定已更新"
# Deployment messages (io.net)
deployment.not_enabled: "io.net 模型部署功能未啟用或 API 密鑰缺失"
deployment.id_required: "deployment ID 為必填項"
deployment.container_id_required: "container ID 為必填項"
deployment.name_empty: "deployment 名稱不能為空"
deployment.name_taken: "deployment 名稱已被使用,請選擇其他名稱"
deployment.hardware_id_required: "hardware_id 參數為必填項"
deployment.hardware_invalid_id: "無效的 hardware_id 參數"
deployment.api_key_required: "api_key 為必填項"
deployment.invalid_payload: "無效的請求內容"
deployment.not_found: "未找到容器詳情"
# Performance messages
performance.disk_cache_cleared: "不活躍的磁碟快取已清理"
performance.stats_reset: "統計資訊已重置"
performance.gc_executed: "GC 已執行"
# Ability messages
ability.db_corrupted: "資料庫一致性被破壞"
ability.repair_running: "已經有一個修復任務在運行中,請稍後再試"
# OAuth messages
oauth.invalid_code: "無效的授權碼"
oauth.get_user_error: "獲取使用者資訊失敗"
oauth.account_used: "該帳號已被其他使用者綁定"
oauth.unknown_provider: "未知的 OAuth 供應者"
oauth.state_invalid: "state 參數為空或不匹配"
oauth.not_enabled: "管理員未開啟通過 {{.Provider}} 登錄以及註冊"
oauth.user_deleted: "使用者已註銷"
oauth.user_banned: "使用者已被封禁"
oauth.bind_success: "綁定成功"
oauth.already_bound: "該 {{.Provider}} 帳號已被綁定"
oauth.not_auto_linked: "無法自動將該 {{.Provider}} 帳號關聯到現有帳號,請使用其他方式登入或註冊後,在帳號設定中綁定 {{.Provider}}"
oauth.connect_failed: "無法連接至 {{.Provider}} 伺服器,請稍後重試"
oauth.token_failed: "{{.Provider}} 獲取 Token 失敗,請檢查設定"
oauth.user_info_empty: "{{.Provider}} 獲取使用者資訊為空,請檢查設定"
oauth.trust_level_low: "Linux DO 信任等級未達到管理員設定的最低信任等級"
# Model layer error messages
redeem.failed: "兌換失敗,請稍後重試"
user.create_default_token_error: "建立預設令牌失敗"
common.uuid_duplicate: "請重試,系統生成的 UUID 竟然重複了!"
common.invalid_input: "輸入不合法"
# Distributor messages
rate_limit.total_reached: "您已達到總請求數限制:{{.Minutes}}分鐘內最多請求{{.Max}}次,包括失敗次數,請檢查您的請求是否正確"
distributor.invalid_request: "無效的請求,{{.Error}}"
distributor.invalid_channel_id: "無效的管道 Id"
distributor.channel_disabled: "該管道已被禁用"
distributor.affinity_channel_disabled: "管道親和性命中的管道已被禁用,已按規則停止重試,請聯絡管理員處理"
distributor.token_no_model_access: "該令牌無權存取任何模型"
distributor.token_model_forbidden: "該令牌無權存取模型 {{.Model}}"
distributor.model_name_required: "未指定模型名稱,模型名稱不能為空"
@@ -281,14 +26,67 @@ distributor.group_access_denied: "無權存取該分組"
distributor.get_channel_failed: "獲取分組 {{.Group}} 下模型 {{.Model}} 的可用管道失敗(distributor):{{.Error}}"
distributor.no_available_channel: "分組 {{.Group}} 下模型 {{.Model}} 無可用管道(distributor)"
distributor.no_available_channel_task_plugin: "分組 {{.Group}} 下模型 {{.Model}} 無可用管道:該模型由任務插件認領,但目前沒有啟用的管道可服務此模型(distributor)"
distributor.invalid_midjourney_request: "無效的midjourney請求,{{.Error}}"
distributor.invalid_request_parse_model: "無效的請求,無法解析模型"
distributor.invalid_request_parse_model: "無法解析模型"
quota.user_insufficient: "使用者額度不足,剩餘額度:{{.Remaining}}"
quota.pre_consume_insufficient: "預扣費額度失敗,使用者剩餘額度:{{.Remaining}},需要預扣費額度:{{.Required}}"
quota.subscription_insufficient: "訂閱額度不足或未設定訂閱:{{.Error}}"
relay.model_price_not_configured: "模型 {{.Model}} 的價格尚未由管理員設定,暫時無法使用,請聯絡站點管理員開啟該模型"
relay.model_price_not_configured_admin: "模型 {{.Model}} 的價格未設定。請前往「系統設定 → 營運設定」開啟自用模式,或在「系統設定 → 分組與模型定價設定」中為該模型設定價格"
relay.task_channel_disabled: "該任務所屬管道已被停用"
relay.group_saturated: "目前分組負載已飽和,請稍後再試,或升級帳戶以提升服務品質。"
relay.group_upstream_saturated: "目前分組上游負載已飽和,請稍後再試"
relay.retry_get_channel_failed: "獲取分組 {{.Group}} 下模型 {{.Model}} 的可用管道失敗(retry):{{.Error}}"
relay.retry_no_available_channel: "分組 {{.Group}} 下模型 {{.Model}} 的可用管道不存在(retry)"
relay.access_token_unsupported: "暫不支援使用 access token"
# Custom OAuth provider messages
custom_oauth.not_found: "自訂 OAuth 供應者不存在"
custom_oauth.slug_empty: "標識符不能為空"
custom_oauth.slug_exists: "標識符已存在"
custom_oauth.name_empty: "供應者名稱不能為空"
custom_oauth.has_bindings: "無法刪除已有使用者綁定的供應者"
custom_oauth.binding_not_found: "OAuth 綁定不存在"
custom_oauth.provider_id_field_invalid: "無法從供應者響應中提取使用者 ID"
# Emails and the name of the token created at registration
user.default_token_name: "{{.Username}}的初始令牌"
email.verification.subject: "{{.SystemName}}電子郵件驗證"
email.verification.greeting: "您好,您正在進行{{.SystemName}}電子郵件驗證。"
email.verification.code: "您的驗證碼為: <strong>{{.Code}}</strong>"
email.verification.validity: "驗證碼 {{.Minutes}} 分鐘內有效,如果不是本人操作,請忽略。"
email.password_reset.subject: "{{.SystemName}}密碼重設"
email.password_reset.greeting: "您好,您正在進行{{.SystemName}}密碼重設。"
email.password_reset.link: "點擊 <a href='{{.Link}}'>此處</a> 進行密碼重設。"
email.password_reset.link_fallback: "如果連結無法點擊,請嘗試點擊下面的連結或將其複製到瀏覽器中開啟:<br> {{.Link}}"
email.password_reset.validity: "重設連結 {{.Minutes}} 分鐘內有效,如果不是本人操作,請忽略。"
email.binding.subject: "{{.SystemName}}電子郵件綁定確認"
email.binding.new_intro: "請確認將此電子郵件地址綁定到您的帳戶。"
email.binding.old_intro: "您的帳戶正在申請更換電子郵件地址,請確認替換目前的電子郵件。"
email.binding.code: "驗證碼:<strong>{{.Code}}</strong>"
email.binding.new_validity: "驗證碼 {{.Minutes}} 分鐘內有效。如果不是您本人操作,請勿將驗證碼告訴任何人。"
email.binding.old_validity: "驗證碼 {{.Minutes}} 分鐘內有效。如果不是您本人操作,請勿將驗證碼告訴任何人,並聯絡管理員。"
email.security_notice.subject: "{{.SystemName}}帳戶安全提醒"
email.security_notice.changed: "您的帳戶安全設定有變動:{{.Event}}。"
email.security_notice.advice: "如果不是您本人操作,請開啟帳戶安全設定,登出其他工作階段,並聯絡管理員。"
email.security_event.password_updated: "密碼已修改"
email.security_event.email_change_requested: "已申請更換電子郵件地址"
email.security_event.email_changed: "電子郵件地址已更換"
email.security_event.email_confirmed: "電子郵件地址已確認"
email.security_event.login_linked: "已綁定登入帳號 {{.Provider}}"
email.security_event.login_unlinked: "已解綁登入帳號"
email.security_event.wechat_linked: "已綁定微信帳號"
# Low-quota notices
notify.quota_low_title: "您的額度即將用盡"
notify.subscription_quota_low_title: "您的訂閱額度即將用盡"
notify.quota_low_bark: "{{.Prompt}},剩餘額度:{{.Quota}},請及時儲值"
notify.quota_low_gotify: "{{.Prompt}},目前剩餘額度為 {{.Quota}},請及時儲值。"
notify.quota_low_email: "{{.Prompt}},目前剩餘額度為 {{.Quota}},為了不影響您的使用,請及時儲值。<br/>儲值連結:<a href='{{.Link}}'>{{.Link}}</a>"
# Channel notices sent to administrators who receive them and to model update watchers
channel.notify_disabled_subject: "管道「{{.Name}}」(#{{.Id}})已被停用"
channel.notify_disabled_content: "管道「{{.Name}}」(#{{.Id}})已被停用,原因:{{.Reason}}"
channel.notify_enabled_subject: "管道「{{.Name}}」(#{{.Id}})已被啟用"
channel.notify_enabled_content: "管道「{{.Name}}」(#{{.Id}})已被啟用"
channel.test_completed_subject: "管道測試完成"
channel.test_completed_content: "所有管道測試已完成"
channel.response_time_exceeded: "回應時間 {{.Seconds}}s 超過閾值 {{.Threshold}}s"
channel.upstream_update_notify_subject: "上游模型巡檢通知"
channel.upstream_update_summary: "上游模型巡檢摘要:檢測管道 {{.Checked}} 個,發現變更 {{.Changed}} 個,新增 {{.Added}} 個,刪除 {{.Removed}} 個,自動同步新增 {{.AutoAdded}} 個,失敗 {{.Failed}} 個。"
channel.upstream_update_changed_channels: "變更管道明細(顯示 {{.Shown}}/{{.Total}}):"
channel.upstream_update_more_channels: "其餘 {{.Count}} 個管道已省略"
channel.upstream_update_added_models: "新增模型範例(顯示 {{.Shown}}/{{.Total}}):{{.Models}}"
channel.upstream_update_removed_models: "刪除模型範例(顯示 {{.Shown}}/{{.Total}}):{{.Models}}"
channel.upstream_update_failed_channels: "失敗管道 ID(顯示 {{.Shown}}/{{.Total}}):{{.Ids}}"
channel.upstream_update_more_omitted: "(其餘 {{.Count}} 個已省略)"