+ )
+}
diff --git a/web/default/src/features/system-settings/models/model-ratio-form.tsx b/web/default/src/features/system-settings/models/model-ratio-form.tsx
index 6cfa93fa..9a9c345d 100644
--- a/web/default/src/features/system-settings/models/model-ratio-form.tsx
+++ b/web/default/src/features/system-settings/models/model-ratio-form.tsx
@@ -31,7 +31,7 @@ import {
FormMessage,
} from '@/components/ui/form'
import { Switch } from '@/components/ui/switch'
-import { Textarea } from '@/components/ui/textarea'
+import { JsonCodeEditor } from '@/components/json-code-editor'
import {
SettingsForm,
SettingsSwitchContent,
@@ -140,10 +140,9 @@ function ModelJsonTextareaField(props: {
{props.label}
-
diff --git a/web/default/src/features/system-settings/models/ratio-settings-card.tsx b/web/default/src/features/system-settings/models/ratio-settings-card.tsx
index 6f9d9f58..7b12a61f 100644
--- a/web/default/src/features/system-settings/models/ratio-settings-card.tsx
+++ b/web/default/src/features/system-settings/models/ratio-settings-card.tsx
@@ -16,7 +16,7 @@ along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
-import { useCallback, useEffect, useRef, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import * as z from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
@@ -34,169 +34,99 @@ import { ToolPriceSettings } from './tool-price-settings'
import { UpstreamRatioSync } from './upstream-ratio-sync'
import {
formatJsonForTextarea,
+ type JsonValidationError,
normalizeJsonString,
validateJsonString,
} from './utils'
-const modelSchema = z.object({
- ModelPrice: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- ModelRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- CacheRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- CreateCacheRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- CompletionRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- ImageRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- AudioRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- AudioCompletionRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- ExposeRatioEnabled: z.boolean(),
- BillingMode: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- BillingExpr: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
-})
+type Translate = (key: string, options?: Record) => string
-const groupSchema = z.object({
- GroupRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
+function formatJsonValidationError(
+ t: Translate,
+ error?: JsonValidationError,
+ fallback = 'Invalid JSON'
+) {
+ if (!error) return t(fallback)
+
+ if (error.type === 'required') return t('Value is required')
+ if (error.type === 'structure') {
+ return t(
+ fallback === 'Invalid JSON' ? 'JSON structure is invalid' : fallback
+ )
+ }
+
+ const parts = [
+ error.line && error.column
+ ? t('JSON is invalid at line {{line}}, column {{column}}.', {
+ line: error.line,
+ column: error.column,
+ })
+ : error.position !== undefined
+ ? t('JSON is invalid at position {{position}}.', {
+ position: error.position,
+ })
+ : t('JSON is invalid. Please check the syntax.'),
+ ]
+
+ if (error.missingCommaLine) {
+ parts.push(
+ t('Check line {{line}} for a missing comma.', {
+ line: error.missingCommaLine,
+ })
+ )
+ }
+
+ return parts.join(' ')
+}
+
+function createJsonStringField(
+ t: Translate,
+ options?: Parameters[1]
+) {
+ return z.string().superRefine((value, ctx) => {
+ const result = validateJsonString(value, options)
if (!result.valid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
+ message: formatJsonValidationError(t, result.error, result.message),
})
}
- }),
- TopupGroupRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- UserUsableGroups: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- GroupGroupRatio: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
- AutoGroups: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value, {
+ })
+}
+
+const createModelSchema = (t: Translate) =>
+ z.object({
+ ModelPrice: createJsonStringField(t),
+ ModelRatio: createJsonStringField(t),
+ CacheRatio: createJsonStringField(t),
+ CreateCacheRatio: createJsonStringField(t),
+ CompletionRatio: createJsonStringField(t),
+ ImageRatio: createJsonStringField(t),
+ AudioRatio: createJsonStringField(t),
+ AudioCompletionRatio: createJsonStringField(t),
+ ExposeRatioEnabled: z.boolean(),
+ BillingMode: createJsonStringField(t),
+ BillingExpr: createJsonStringField(t),
+ })
+
+const createGroupSchema = (t: Translate) =>
+ z.object({
+ GroupRatio: createJsonStringField(t),
+ TopupGroupRatio: createJsonStringField(t),
+ UserUsableGroups: createJsonStringField(t),
+ GroupGroupRatio: createJsonStringField(t),
+ AutoGroups: createJsonStringField(t, {
predicate: (parsed) =>
Array.isArray(parsed) &&
parsed.every((item) => typeof item === 'string'),
predicateMessage: 'Expected a JSON array of group identifiers',
- })
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON array',
- })
- }
- }),
- DefaultUseAutoGroup: z.boolean(),
- GroupSpecialUsableGroup: z.string().superRefine((value, ctx) => {
- const result = validateJsonString(value)
- if (!result.valid) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: result.message || 'Invalid JSON',
- })
- }
- }),
-})
+ }),
+ DefaultUseAutoGroup: z.boolean(),
+ GroupSpecialUsableGroup: createJsonStringField(t),
+ })
-type ModelFormValues = z.infer
-type GroupFormValues = z.infer
+type ModelFormValues = z.infer>
+type GroupFormValues = z.infer>
type RatioTabId = 'models' | 'groups' | 'tool-prices' | 'upstream-sync'
type RatioSettingsCardProps = {
@@ -265,6 +195,8 @@ export function RatioSettingsCard({
groupDefaults.GroupSpecialUsableGroup
),
})
+ const modelSchema = useMemo(() => createModelSchema(t), [t])
+ const groupSchema = useMemo(() => createGroupSchema(t), [t])
const modelForm = useForm({
resolver: zodResolver(modelSchema),
diff --git a/web/default/src/features/system-settings/models/utils.ts b/web/default/src/features/system-settings/models/utils.ts
index 85caa0ab..952e8571 100644
--- a/web/default/src/features/system-settings/models/utils.ts
+++ b/web/default/src/features/system-settings/models/utils.ts
@@ -49,6 +49,14 @@ type JsonValidationOptions = {
predicateMessage?: string
}
+export type JsonValidationError = {
+ type: 'required' | 'structure' | 'syntax'
+ line?: number
+ column?: number
+ position?: number
+ missingCommaLine?: number
+}
+
function extractErrorPosition(
error: unknown,
jsonString: string
@@ -81,8 +89,15 @@ function extractErrorPosition(
return {}
}
-function formatErrorMessage(error: unknown, jsonString: string): string {
- if (!(error instanceof Error)) return 'Invalid JSON'
+function buildSyntaxError(
+ error: unknown,
+ jsonString: string
+): JsonValidationError {
+ if (!(error instanceof Error)) {
+ return {
+ type: 'syntax',
+ } satisfies JsonValidationError
+ }
const position = extractErrorPosition(error, jsonString)
const message = error.message
@@ -93,10 +108,29 @@ function formatErrorMessage(error: unknown, jsonString: string): string {
message.includes('Expected property name') ||
message.includes('Unexpected string')
+ const missingCommaLine =
+ isMissingCommaError && position.line && position.line > 1
+ ? position.line - 1
+ : undefined
+
+ return {
+ type: 'syntax',
+ ...position,
+ missingCommaLine,
+ } satisfies JsonValidationError
+}
+
+function formatErrorMessage(error: unknown, jsonString: string): string {
+ if (!(error instanceof Error)) return 'Invalid JSON'
+
+ const position = extractErrorPosition(error, jsonString)
+ const message = error.message
+ const syntaxError = buildSyntaxError(error, jsonString)
+
if (position.line && position.column) {
let hint = ''
- if (isMissingCommaError && position.line > 1) {
- hint = ` (check line ${position.line - 1} for missing comma)`
+ if (syntaxError.missingCommaLine) {
+ hint = ` (check line ${syntaxError.missingCommaLine} for missing comma)`
}
return `Error at line ${position.line}, column ${position.column}: ${message}${hint}`
}
@@ -119,6 +153,11 @@ export function validateJsonString(
return {
valid: allowEmpty,
message: allowEmpty ? undefined : 'Value is required',
+ error: allowEmpty
+ ? undefined
+ : ({
+ type: 'required',
+ } satisfies JsonValidationError),
}
}
@@ -128,6 +167,9 @@ export function validateJsonString(
return {
valid: false,
message: predicateMessage || 'JSON structure is invalid',
+ error: {
+ type: 'structure',
+ } satisfies JsonValidationError,
}
}
@@ -136,6 +178,7 @@ export function validateJsonString(
return {
valid: false,
message: formatErrorMessage(error, trimmed),
+ error: buildSyntaxError(error, trimmed),
}
}
}
diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json
index 7abaed07..dbe68185 100644
--- a/web/default/src/i18n/locales/en.json
+++ b/web/default/src/i18n/locales/en.json
@@ -679,6 +679,7 @@
"Check for updates": "Check for updates",
"Check in daily to receive random quota rewards": "Check in daily to receive random quota rewards",
"Check in now": "Check in now",
+ "Check line {{line}} for a missing comma.": "Check line {{line}} for a missing comma.",
"Check out the Quick Start": "Check out the Quick Start",
"Check resolved IPs against IP filters even when accessing by domain": "Check resolved IPs against IP filters even when accessing by domain",
"Check-in failed": "Check-in failed",
@@ -1527,6 +1528,7 @@
"Expand": "Expand",
"Expand All": "Expand All",
"Expected a JSON array.": "Expected a JSON array.",
+ "Expected a JSON array of group identifiers": "Expected a JSON array of group identifiers",
"Experiment with prompts and models in real time.": "Experiment with prompts and models in real time.",
"Expiration Time": "Expiration Time",
"expired": "expired",
@@ -2093,6 +2095,9 @@
"JSON Editor": "JSON Editor",
"JSON format error": "JSON format error",
"JSON format supports service account JSON files": "JSON format supports service account JSON files",
+ "JSON is invalid at line {{line}}, column {{column}}.": "JSON is invalid at line {{line}}, column {{column}}.",
+ "JSON is invalid at position {{position}}.": "JSON is invalid at position {{position}}.",
+ "JSON is invalid. Please check the syntax.": "JSON is invalid. Please check the syntax.",
"JSON map of group → description exposed when users create API keys.": "JSON map of group → description exposed when users create API keys.",
"JSON map of group → ratio applied when the user selects the group explicitly.": "JSON map of group → ratio applied when the user selects the group explicitly.",
"JSON map of model → multiplier applied to quota billing.": "JSON map of model → multiplier applied to quota billing.",
@@ -2101,6 +2106,7 @@
"JSON Mode": "JSON Mode",
"JSON must be an object": "JSON must be an object",
"JSON object:": "JSON object:",
+ "JSON structure is invalid": "JSON structure is invalid",
"JSON Text": "JSON Text",
"JSON-based access control rules. Leave empty to allow all users.": "JSON-based access control rules. Leave empty to allow all users.",
"Just now": "Just now",
@@ -4313,6 +4319,7 @@
"Validity Period": "Validity Period",
"Value": "Value",
"Value (supports JSON or plain text)": "Value (supports JSON or plain text)",
+ "Value is required": "Value is required",
"Value must be at least 0": "Value must be at least 0",
"Value Regex": "Value Regex",
"variable": "variable",
diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json
index 27e3ea49..996aaac5 100644
--- a/web/default/src/i18n/locales/fr.json
+++ b/web/default/src/i18n/locales/fr.json
@@ -679,6 +679,7 @@
"Check for updates": "Vérifier les mises à jour",
"Check in daily to receive random quota rewards": "Connectez-vous quotidiennement pour recevoir des récompenses de quota aléatoires",
"Check in now": "Se connecter maintenant",
+ "Check line {{line}} for a missing comma.": "Vérifiez la ligne {{line}} pour une virgule manquante.",
"Check out the Quick Start": "Consultez le démarrage rapide",
"Check resolved IPs against IP filters even when accessing by domain": "Vérifier les adresses IP résolues par rapport aux filtres IP même lors de l'accès par domaine",
"Check-in failed": "Échec de la connexion",
@@ -1527,6 +1528,7 @@
"Expand": "Développer",
"Expand All": "Tout développer",
"Expected a JSON array.": "Un tableau JSON est attendu.",
+ "Expected a JSON array of group identifiers": "Un tableau JSON d'identifiants de groupe est attendu",
"Experiment with prompts and models in real time.": "Expérimentez avec des prompts et des modèles en temps réel.",
"Expiration Time": "Heure d'expiration",
"expired": "expiré",
@@ -2093,6 +2095,9 @@
"JSON Editor": "Édition JSON",
"JSON format error": "Erreur de format JSON",
"JSON format supports service account JSON files": "Le format JSON prend en charge les fichiers JSON de compte de service",
+ "JSON is invalid at line {{line}}, column {{column}}.": "Le JSON est invalide à la ligne {{line}}, colonne {{column}}.",
+ "JSON is invalid at position {{position}}.": "Le JSON est invalide à la position {{position}}.",
+ "JSON is invalid. Please check the syntax.": "Le JSON est invalide. Veuillez vérifier la syntaxe.",
"JSON map of group → description exposed when users create API keys.": "Carte JSON de groupe → description exposée lorsque les utilisateurs créent des clés API.",
"JSON map of group → ratio applied when the user selects the group explicitly.": "Carte JSON de groupe → ratio appliqué lorsque l'utilisateur sélectionne explicitement le groupe.",
"JSON map of model → multiplier applied to quota billing.": "Carte JSON de modèle → multiplicateur appliqué à la facturation par quota.",
@@ -2101,6 +2106,7 @@
"JSON Mode": "Mode JSON",
"JSON must be an object": "Le JSON doit être un objet",
"JSON object:": "Objet JSON :",
+ "JSON structure is invalid": "La structure JSON est invalide",
"JSON Text": "Texte JSON",
"JSON-based access control rules. Leave empty to allow all users.": "Règles de contrôle d'accès basées sur JSON. Laisser vide pour autoriser tous les utilisateurs.",
"Just now": "À l'instant",
@@ -4313,6 +4319,7 @@
"Validity Period": "Période de validité",
"Value": "Valeur",
"Value (supports JSON or plain text)": "Valeur (JSON ou texte brut)",
+ "Value is required": "La valeur est obligatoire",
"Value must be at least 0": "La valeur doit être au moins 0",
"Value Regex": "Regex de valeur",
"variable": "variable",
diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json
index 26ed4174..384c43c4 100644
--- a/web/default/src/i18n/locales/ja.json
+++ b/web/default/src/i18n/locales/ja.json
@@ -679,6 +679,7 @@
"Check for updates": "更新を確認",
"Check in daily to receive random quota rewards": "毎日チェックインして、ランダムなノルマ報酬を受け取りましょう",
"Check in now": "今すぐチェックイン",
+ "Check line {{line}} for a missing comma.": "{{line}} 行目にカンマの抜けがないか確認してください。",
"Check out the Quick Start": "クイックスタートをご確認ください",
"Check resolved IPs against IP filters even when accessing by domain": "ドメインによるアクセスであっても、解決されたIPをIPフィルターと照合してチェックします",
"Check-in failed": "チェックインできませんでした",
@@ -1527,6 +1528,7 @@
"Expand": "展開",
"Expand All": "すべて展開",
"Expected a JSON array.": "JSON 配列が必要です。",
+ "Expected a JSON array of group identifiers": "グループ識別子の JSON 配列が必要です",
"Experiment with prompts and models in real time.": "プロンプトとモデルをリアルタイムで実験する。",
"Expiration Time": "有効期限",
"expired": "期限切れ",
@@ -2093,6 +2095,9 @@
"JSON Editor": "JSON編集",
"JSON format error": "JSONフォーマットエラー",
"JSON format supports service account JSON files": "JSON形式はサービスアカウントJSONファイルをサポートします",
+ "JSON is invalid at line {{line}}, column {{column}}.": "JSON は {{line}} 行目、{{column}} 列目で無効です。",
+ "JSON is invalid at position {{position}}.": "JSON は位置 {{position}} で無効です。",
+ "JSON is invalid. Please check the syntax.": "JSON が無効です。構文を確認してください。",
"JSON map of group → description exposed when users create API keys.": "ユーザーがAPIキーを作成する際に公開される、グループ → 説明のJSONマップ。",
"JSON map of group → ratio applied when the user selects the group explicitly.": "ユーザーがグループを明示的に選択したときに適用される、グループ → 比率のJSONマップ。",
"JSON map of model → multiplier applied to quota billing.": "モデル → クォータ請求に適用される乗数のJSONマップ。",
@@ -2101,6 +2106,7 @@
"JSON Mode": "JSONモード",
"JSON must be an object": "JSON はオブジェクトである必要があります",
"JSON object:": "JSONオブジェクト:",
+ "JSON structure is invalid": "JSON 構造が無効です",
"JSON Text": "JSONテキスト",
"JSON-based access control rules. Leave empty to allow all users.": "JSONベースのアクセス制御ルール。すべてのユーザーを許可する場合は空のままにしてください。",
"Just now": "たった今",
@@ -4313,6 +4319,7 @@
"Validity Period": "有効期間",
"Value": "値",
"Value (supports JSON or plain text)": "値(JSONまたはプレーンテキスト対応)",
+ "Value is required": "値は必須です",
"Value must be at least 0": "値は 0 以上である必要があります",
"Value Regex": "Value 正規表現",
"variable": "変数",
diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json
index 50382fd0..fd132e90 100644
--- a/web/default/src/i18n/locales/ru.json
+++ b/web/default/src/i18n/locales/ru.json
@@ -679,6 +679,7 @@
"Check for updates": "Проверить обновления",
"Check in daily to receive random quota rewards": "Регистрируйтесь ежедневно, чтобы получать случайные вознаграждения по квоте",
"Check in now": "Войдите сейчас",
+ "Check line {{line}} for a missing comma.": "Проверьте строку {{line}} на пропущенную запятую.",
"Check out the Quick Start": "Ознакомьтесь с быстрым стартом",
"Check resolved IPs against IP filters even when accessing by domain": "Проверять разрешенные IP-адреса по IP-фильтрам даже при доступе по домену",
"Check-in failed": "Регистрация не удалась.",
@@ -1527,6 +1528,7 @@
"Expand": "Развернуть",
"Expand All": "Развернуть все",
"Expected a JSON array.": "Ожидается JSON-массив.",
+ "Expected a JSON array of group identifiers": "Ожидается JSON-массив идентификаторов групп",
"Experiment with prompts and models in real time.": "Экспериментируйте с промптами и моделями в реальном времени.",
"Expiration Time": "Время истечения срока действия",
"expired": "истек",
@@ -2093,6 +2095,9 @@
"JSON Editor": "Редактирование JSON",
"JSON format error": "Ошибка формата JSON",
"JSON format supports service account JSON files": "Формат JSON поддерживает JSON-файлы сервисного аккаунта",
+ "JSON is invalid at line {{line}}, column {{column}}.": "JSON недействителен в строке {{line}}, столбце {{column}}.",
+ "JSON is invalid at position {{position}}.": "JSON недействителен в позиции {{position}}.",
+ "JSON is invalid. Please check the syntax.": "JSON недействителен. Проверьте синтаксис.",
"JSON map of group → description exposed when users create API keys.": "JSON-карта группы → описание, отображаемое при создании пользователями ключей API.",
"JSON map of group → ratio applied when the user selects the group explicitly.": "JSON-карта группы → соотношение, применяемое, когда пользователь явно выбирает группу.",
"JSON map of model → multiplier applied to quota billing.": "JSON-карта модели → множитель, применяемый к тарификации по квоте.",
@@ -2101,6 +2106,7 @@
"JSON Mode": "Режим JSON",
"JSON must be an object": "JSON должен быть объектом",
"JSON object:": "Объект JSON:",
+ "JSON structure is invalid": "Структура JSON недействительна",
"JSON Text": "JSON текст",
"JSON-based access control rules. Leave empty to allow all users.": "Правила контроля доступа на основе JSON. Оставьте пустым, чтобы разрешить всем пользователям.",
"Just now": "Только что",
@@ -4313,6 +4319,7 @@
"Validity Period": "Срок действия",
"Value": "Значение",
"Value (supports JSON or plain text)": "Значение (JSON или текст)",
+ "Value is required": "Значение обязательно",
"Value must be at least 0": "Значение должно быть не менее 0",
"Value Regex": "Регулярное выражение значения",
"variable": "переменная",
diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json
index a28b90fe..61ec9f67 100644
--- a/web/default/src/i18n/locales/vi.json
+++ b/web/default/src/i18n/locales/vi.json
@@ -679,6 +679,7 @@
"Check for updates": "Kiểm tra cập nhật",
"Check in daily to receive random quota rewards": "Nhận phòng hàng ngày để nhận phần thưởng theo hạn ngạch ngẫu nhiên",
"Check in now": "Điểm danh ngay",
+ "Check line {{line}} for a missing comma.": "Kiểm tra dòng {{line}} xem có thiếu dấu phẩy không.",
"Check out the Quick Start": "Xem hướng dẫn bắt đầu nhanh",
"Check resolved IPs against IP filters even when accessing by domain": "Kiểm tra các IP đã phân giải đối chiếu với các bộ lọc IP ngay cả khi truy cập bằng tên miền",
"Check-in failed": "Điểm danh thất bại",
@@ -1527,6 +1528,7 @@
"Expand": "Mở rộng",
"Expand All": "Mở rộng tất cả",
"Expected a JSON array.": "Cần là một mảng JSON.",
+ "Expected a JSON array of group identifiers": "Cần là một mảng JSON gồm các định danh nhóm",
"Experiment with prompts and models in real time.": "Thử nghiệm với prompt và mô hình theo thời gian thực.",
"Expiration Time": "Thời gian hết hạn",
"expired": "Đã hết hạn",
@@ -2093,6 +2095,9 @@
"JSON Editor": "Trình chỉnh sửa JSON",
"JSON format error": "Lỗi định dạng JSON",
"JSON format supports service account JSON files": "Định dạng JSON hỗ trợ các tệp JSON tài khoản dịch vụ",
+ "JSON is invalid at line {{line}}, column {{column}}.": "JSON không hợp lệ tại dòng {{line}}, cột {{column}}.",
+ "JSON is invalid at position {{position}}.": "JSON không hợp lệ tại vị trí {{position}}.",
+ "JSON is invalid. Please check the syntax.": "JSON không hợp lệ. Vui lòng kiểm tra cú pháp.",
"JSON map of group → description exposed when users create API keys.": "Ánh xạ JSON của nhóm → mô tả được hiển thị khi người dùng tạo khóa API.",
"JSON map of group → ratio applied when the user selects the group explicitly.": "Bản đồ JSON của nhóm → tỷ lệ được áp dụng khi người dùng chọn nhóm đó một cách rõ ràng.",
"JSON map of model → multiplier applied to quota billing.": "Bản đồ JSON của mô hình → hệ số nhân áp dụng cho thanh toán hạn mức.",
@@ -2101,6 +2106,7 @@
"JSON Mode": "Chế độ JSON",
"JSON must be an object": "JSON phải là object",
"JSON object:": "Đối tượng JSON:",
+ "JSON structure is invalid": "Cấu trúc JSON không hợp lệ",
"JSON Text": "Văn bản JSON",
"JSON-based access control rules. Leave empty to allow all users.": "Quy tắc kiểm soát truy cập dựa trên JSON. Để trống để cho phép tất cả người dùng.",
"Just now": "Vừa nãy",
@@ -4313,6 +4319,7 @@
"Validity Period": "Thời hạn hiệu lực",
"Value": "Giá trị",
"Value (supports JSON or plain text)": "Giá trị (hỗ trợ JSON hoặc văn bản thuần)",
+ "Value is required": "Giá trị là bắt buộc",
"Value must be at least 0": "Giá trị phải ít nhất là 0",
"Value Regex": "Regex giá trị",
"variable": "biến",
diff --git a/web/default/src/i18n/locales/zh.json b/web/default/src/i18n/locales/zh.json
index 1ce821f5..1e2624f1 100644
--- a/web/default/src/i18n/locales/zh.json
+++ b/web/default/src/i18n/locales/zh.json
@@ -679,6 +679,7 @@
"Check for updates": "检查更新",
"Check in daily to receive random quota rewards": "每日签到可获得随机额度奖励",
"Check in now": "立即签到",
+ "Check line {{line}} for a missing comma.": "请检查第 {{line}} 行是否缺少逗号。",
"Check out the Quick Start": "请查看 新手入门",
"Check resolved IPs against IP filters even when accessing by domain": "即使通过域名访问,也对照 IP 过滤器检查解析的 IP",
"Check-in failed": "签到失败",
@@ -1527,6 +1528,7 @@
"Expand": "展开",
"Expand All": "全部展开",
"Expected a JSON array.": "应为 JSON 数组。",
+ "Expected a JSON array of group identifiers": "应为分组标识符的 JSON 数组",
"Experiment with prompts and models in real time.": "实时实验提示词和模型。",
"Expiration Time": "过期时间",
"expired": "已过期",
@@ -2093,6 +2095,9 @@
"JSON Editor": "JSON 编辑",
"JSON format error": "JSON 格式错误",
"JSON format supports service account JSON files": "JSON 格式支持服务账户 JSON 文件",
+ "JSON is invalid at line {{line}}, column {{column}}.": "JSON 在第 {{line}} 行、第 {{column}} 列无效。",
+ "JSON is invalid at position {{position}}.": "JSON 在位置 {{position}} 无效。",
+ "JSON is invalid. Please check the syntax.": "JSON 无效,请检查语法。",
"JSON map of group → description exposed when users create API keys.": "分组 → 描述的 JSON 映射,在用户创建 API 密钥时公开。",
"JSON map of group → ratio applied when the user selects the group explicitly.": "分组 → 比率的 JSON 映射,当用户明确选择该分组时应用此比率。",
"JSON map of model → multiplier applied to quota billing.": "模型 → 应用于配额计费的乘数的 JSON 映射。",
@@ -2101,6 +2106,7 @@
"JSON Mode": "JSON 模式",
"JSON must be an object": "JSON 必须是对象",
"JSON object:": "JSON 对象:",
+ "JSON structure is invalid": "JSON 结构无效",
"JSON Text": "JSON 文本",
"JSON-based access control rules. Leave empty to allow all users.": "基于 JSON 的访问控制规则。留空以允许所有用户。",
"Just now": "刚刚",
@@ -4313,6 +4319,7 @@
"Validity Period": "有效期",
"Value": "值",
"Value (supports JSON or plain text)": "值(支持 JSON 或普通文本)",
+ "Value is required": "值为必填项",
"Value must be at least 0": "值必须至少为 0",
"Value Regex": "Value 正则",
"variable": "变量",
diff --git a/web/default/src/i18n/static-keys.ts b/web/default/src/i18n/static-keys.ts
index 1ee0eacd..d3482b43 100644
--- a/web/default/src/i18n/static-keys.ts
+++ b/web/default/src/i18n/static-keys.ts
@@ -88,6 +88,7 @@ export const STATIC_I18N_KEYS = [
'Failed to delete API key',
'Failed to delete API keys',
'Failed to update API key status',
+ 'Expected a JSON array of group identifiers',
'Successfully created {{count}} API Key(s)',
'Successfully deleted {{count}} API key(s)',
'Enter API key for this channel',