feat: harden support, localize knowledge and add operator availability [skip ci]
Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.
Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.
Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.
Expected source tree: 0a4a7528e9.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
// Modified in the independent AgentDesk fork: localized knowledge workflows and safe display errors.
|
||||
package i18nx
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const contextLocaleKey = "i18nx.locale"
|
||||
|
||||
@@ -25,3 +30,27 @@ func SetLocale(ctx *gin.Context, locale string) {
|
||||
}
|
||||
ctx.Set(contextLocaleKey, NormalizeLocale(locale))
|
||||
}
|
||||
|
||||
// localeContextKey is deliberately distinct from Gin's string key.
|
||||
type localeContextKey struct{}
|
||||
|
||||
// WithLocale carries an explicitly selected application locale without losing
|
||||
// the parent's cancellation, deadline, or values. It does not detect a locale
|
||||
// from HTTP headers or change the configured default locale.
|
||||
func WithLocale(ctx context.Context, locale string) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, localeContextKey{}, NormalizeLocale(locale))
|
||||
}
|
||||
|
||||
// LocaleFromContext preserves the configured-default behavior of callers that
|
||||
// do not provide an explicit locale (including background jobs).
|
||||
func LocaleFromContext(ctx context.Context) string {
|
||||
if ctx != nil {
|
||||
if locale, ok := ctx.Value(localeContextKey{}).(string); ok {
|
||||
return NormalizeLocale(locale)
|
||||
}
|
||||
}
|
||||
return DefaultLocale
|
||||
}
|
||||
|
||||
45
internal/pkg/i18nx/context_test.go
Normal file
45
internal/pkg/i18nx/context_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package i18nx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWithLocalePreservesContext(t *testing.T) {
|
||||
type testKey struct{}
|
||||
deadline := time.Now().Add(time.Minute)
|
||||
parent, cancel := context.WithDeadline(context.WithValue(context.Background(), testKey{}, "request-value"), deadline)
|
||||
defer cancel()
|
||||
localized := WithLocale(parent, "ru")
|
||||
if got := LocaleFromContext(localized); got != LocaleRuRU {
|
||||
t.Fatalf("locale = %q", got)
|
||||
}
|
||||
if got := localized.Value(testKey{}); got != "request-value" {
|
||||
t.Fatalf("parent value lost: %v", got)
|
||||
}
|
||||
if got, ok := localized.Deadline(); !ok || !got.Equal(deadline) {
|
||||
t.Fatalf("deadline lost: %v, %v", got, ok)
|
||||
}
|
||||
cancel()
|
||||
if !errors.Is(localized.Err(), context.Canceled) {
|
||||
t.Fatalf("cancellation lost: %v", localized.Err())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleFromContextDefaultsAndIsolation(t *testing.T) {
|
||||
for _, ctx := range []context.Context{nil, context.Background(), context.WithValue(context.Background(), "i18nx.locale", LocaleZhCN)} {
|
||||
if got := LocaleFromContext(ctx); got != DefaultLocale {
|
||||
t.Fatalf("missing explicit locale = %q, want default %q", got, DefaultLocale)
|
||||
}
|
||||
}
|
||||
english := WithLocale(context.Background(), LocaleEnUS)
|
||||
russian := WithLocale(english, LocaleRuRU)
|
||||
if LocaleFromContext(english) != LocaleEnUS || LocaleFromContext(russian) != LocaleRuRU {
|
||||
t.Fatal("child locale changed parent locale")
|
||||
}
|
||||
if got := LocaleFromContext(WithLocale(nil, "unsupported")); got != DefaultLocale {
|
||||
t.Fatalf("invalid locale = %q", got)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified in the independent hardening fork; upstream notices retained.
|
||||
error.e0001: "AI Agent not found"
|
||||
error.e0002: "AI Agent not found."
|
||||
error.e0003: "AI Agent not found or disabled."
|
||||
@@ -567,3 +568,77 @@ tool.graph.prepareTicketDraft.param.expectedOutcome: "The user's expected outcom
|
||||
tool.graph.prepareTicketDraft.param.currentAttempt: "Current attempted troubleshooting steps. Optional."
|
||||
error.setup.invalidQuickFixAction: "Unsupported setup quick-fix action: %v"
|
||||
error.setup.authRequired: "Sign in to run setup diagnostics or quick fixes"
|
||||
|
||||
error.setup.previewRequired: "Review a setup preview and explicitly confirm its token before applying changes."
|
||||
error.setup.previewChanged: "Configuration changed since the preview. Review a new preview before applying."
|
||||
error.setup.targetRequired: "Select a specific existing record, or use its settings to choose the intended configuration."
|
||||
error.setup.targetUnavailable: "The selected record is unavailable, deleted or ineligible. It was not restored."
|
||||
error.setup.persistenceFailed: "Setup configuration could not be read or saved. No incomplete repair was committed."
|
||||
error.setup.bypassUnsupported: "Bypassing AI is not supported by this diagnostic. Run a human-only route check or test AI separately."
|
||||
error.channel.configChanged: "The channel configuration changed. Reload it and review your changes before saving again."
|
||||
error.channel.configUpdateFailed: "The channel configuration could not be saved. Please try again."
|
||||
|
||||
# Knowledge-base display labels and safe application-owned messages.
|
||||
knowledge.labels.unknown: "Unknown"
|
||||
knowledge.labels.type.document: "Document knowledge base"
|
||||
knowledge.labels.type.faq: "FAQ knowledge base"
|
||||
knowledge.labels.status.0: "Enabled"
|
||||
knowledge.labels.status.1: "Disabled"
|
||||
knowledge.labels.status.2: "Deleted"
|
||||
knowledge.labels.index.pending: "Pending"
|
||||
knowledge.labels.index.indexed: "Indexed"
|
||||
knowledge.labels.index.failed: "Index failed"
|
||||
knowledge.labels.mode.1: "Strict mode"
|
||||
knowledge.labels.mode.2: "Assist mode"
|
||||
knowledge.labels.channel.im: "Conversations"
|
||||
knowledge.labels.channel.agent_assist: "Agent assist"
|
||||
knowledge.labels.channel.api: "API"
|
||||
knowledge.labels.channel.debug: "Debug"
|
||||
knowledge.labels.scene.first_response: "First response"
|
||||
knowledge.labels.scene.assist: "Assisted reply"
|
||||
knowledge.labels.scene.qa: "Q&A"
|
||||
knowledge.labels.answer.1: "Answered"
|
||||
knowledge.labels.answer.2: "No answer"
|
||||
knowledge.labels.answer.3: "Fallback"
|
||||
knowledge.labels.answer.4: "Blocked"
|
||||
knowledge.labels.chunk.text: "Text"
|
||||
knowledge.labels.chunk.faq: "Q&A"
|
||||
knowledge.labels.chunk.table: "Table"
|
||||
knowledge.labels.chunk.code: "Code"
|
||||
error.knowledge.indexFailed: "Indexing failed. Check the indexing configuration and try again."
|
||||
error.knowledge.retrieveFailed: "Knowledge retrieval failed. Check the retrieval configuration and try again."
|
||||
error.knowledge.workflowReference: "This knowledge base is used by workflow “%s”. Remove it from the knowledge retrieval node first."
|
||||
error.knowledge.workflowReferences: "This knowledge base is used by %d workflows. Remove it from their knowledge retrieval nodes first."
|
||||
knowledge.rag.fallback.noAnswer: "The knowledge base currently has no clear information."
|
||||
knowledge.rag.fallback.suggestRetry: "The knowledge base did not contain enough clear information. Try asking a more specific question."
|
||||
knowledge.rag.system.assist: "You are a customer support knowledge base assistant. Prioritize the supplied knowledge excerpts. You may summarize them, but do not invent facts that were not provided. Respond in English."
|
||||
knowledge.rag.system.strict: "You are a strict customer support knowledge base assistant. Answer only from the supplied knowledge excerpts. If they are insufficient, explicitly state that the knowledge base has no clear information. Respond in English."
|
||||
knowledge.rag.userPrompt: "User question: %s\n\nReference material:\n%s"
|
||||
|
||||
knowledge.excel.question: "Primary question"
|
||||
knowledge.excel.answer: "Answer"
|
||||
knowledge.excel.similarQuestions: "Similar questions"
|
||||
knowledge.excel.remark: "Notes"
|
||||
knowledge.excel.exampleQuestion: "How do I reset my password?"
|
||||
knowledge.excel.exampleAnswer: "Open your profile settings and select Reset password."
|
||||
knowledge.excel.exampleSimilar: "I forgot my password\nWhere can I reset my password?"
|
||||
knowledge.excel.exampleRemark: "Account"
|
||||
knowledge.rag.context.faq: "[FAQ: %s]\n%s\n\n"
|
||||
knowledge.rag.context.faqTitle: "FAQ#%d"
|
||||
knowledge.rag.context.documentTitle: "Document#%d"
|
||||
knowledge.rag.context.documentSection: "[Document: %s | Section: %s]\n%s\n\n"
|
||||
knowledge.rag.context.documentHeading: "[Document: %s | Title: %s]\n%s\n\n"
|
||||
knowledge.rag.context.document: "[Document: %s]\n%s\n\n"
|
||||
knowledge.rag.faq.question: "Question: %s"
|
||||
knowledge.rag.faq.similarQuestions: "Similar questions: %s"
|
||||
knowledge.rag.faq.answer: "Answer: %s"
|
||||
knowledge.rag.faq.separator: "; "
|
||||
|
||||
# Support availability validation.
|
||||
error.supportAvailability.invalidCalendar: "Enter a valid timezone, weekly hours, and date exceptions."
|
||||
error.supportAvailability.invalidAbsence: "Vacation requires a start and end with an explicit timezone; the end must be after the start."
|
||||
error.supportAvailability.unavailable: "This operator is not currently eligible for new conversations. Refresh availability and try again."
|
||||
error.supportAvailability.persistence: "Support availability could not be loaded or saved. Please try again."
|
||||
error.supportAvailability.noProfile: "An enabled support operator profile is required."
|
||||
error.supportAvailability.invalidStatus: "Choose a valid working availability status."
|
||||
error.supportAvailability.invalidTime: "Enter a timestamp including its timezone offset."
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified in the independent hardening fork; upstream notices retained.
|
||||
error.e0001: "ИИ-агент не найден"
|
||||
error.e0002: "ИИ-агент не найден."
|
||||
error.e0003: "ИИ-агент не найден или отключён."
|
||||
@@ -566,4 +567,77 @@ tool.graph.prepareTicketDraft.param.impact: "Область воздействи
|
||||
tool.graph.prepareTicketDraft.param.expectedOutcome: "Ожидаемый результат или запрос пользователя."
|
||||
tool.graph.prepareTicketDraft.param.currentAttempt: "Предпринятые шаги по устранению. Необязательно."
|
||||
error.setup.invalidQuickFixAction: "Неподдерживаемое действие быстрой настройки: %v"
|
||||
error.setup.authRequired: "Войдите в систему для запуска диагностики или быстрой настройки"
|
||||
error.setup.authRequired: "Войдите в систему для запуска диагностики или быстрой настройки"
|
||||
error.setup.previewRequired: "Перед применением изменений просмотрите план и явно подтвердите его токен."
|
||||
error.setup.previewChanged: "После предварительного просмотра настройки изменились. Просмотрите новый план."
|
||||
error.setup.targetRequired: "Выберите конкретную запись или задайте нужную конфигурацию в её настройках."
|
||||
error.setup.targetUnavailable: "Выбранная запись недоступна, удалена или не подходит. Она не была восстановлена."
|
||||
error.setup.persistenceFailed: "Не удалось прочитать или сохранить настройки. Неполное исправление не было сохранено."
|
||||
error.setup.bypassUnsupported: "Эта диагностика не поддерживает обход ИИ. Проверьте маршрут только с оператором или протестируйте ИИ отдельно."
|
||||
error.channel.configChanged: "Настройки канала изменились. Перезагрузите их и проверьте изменения перед повторным сохранением."
|
||||
error.channel.configUpdateFailed: "Не удалось сохранить настройки канала. Попробуйте ещё раз."
|
||||
|
||||
# Knowledge-base display labels and safe application-owned messages.
|
||||
knowledge.labels.unknown: "Неизвестно"
|
||||
knowledge.labels.type.document: "База знаний документов"
|
||||
knowledge.labels.type.faq: "База знаний FAQ"
|
||||
knowledge.labels.status.0: "Включено"
|
||||
knowledge.labels.status.1: "Отключено"
|
||||
knowledge.labels.status.2: "Удалено"
|
||||
knowledge.labels.index.pending: "Ожидает индексации"
|
||||
knowledge.labels.index.indexed: "Проиндексировано"
|
||||
knowledge.labels.index.failed: "Ошибка индексации"
|
||||
knowledge.labels.mode.1: "Строгий режим"
|
||||
knowledge.labels.mode.2: "Вспомогательный режим"
|
||||
knowledge.labels.channel.im: "Диалоги"
|
||||
knowledge.labels.channel.agent_assist: "Помощник оператора"
|
||||
knowledge.labels.channel.api: "API"
|
||||
knowledge.labels.channel.debug: "Отладка"
|
||||
knowledge.labels.scene.first_response: "Первый ответ"
|
||||
knowledge.labels.scene.assist: "Подсказка ответа"
|
||||
knowledge.labels.scene.qa: "Вопросы и ответы"
|
||||
knowledge.labels.answer.1: "Ответ получен"
|
||||
knowledge.labels.answer.2: "Нет ответа"
|
||||
knowledge.labels.answer.3: "Резервный ответ"
|
||||
knowledge.labels.answer.4: "Заблокирован"
|
||||
knowledge.labels.chunk.text: "Текст"
|
||||
knowledge.labels.chunk.faq: "Вопросы и ответы"
|
||||
knowledge.labels.chunk.table: "Таблица"
|
||||
knowledge.labels.chunk.code: "Код"
|
||||
error.knowledge.indexFailed: "Не удалось выполнить индексацию. Проверьте настройки индексации и повторите попытку."
|
||||
error.knowledge.retrieveFailed: "Не удалось выполнить поиск по базе знаний. Проверьте настройки поиска и повторите попытку."
|
||||
error.knowledge.workflowReference: "Эта база знаний используется в процессе «%s». Сначала удалите её из узла поиска по базе знаний."
|
||||
error.knowledge.workflowReferences: "Эта база знаний используется в %d процессах. Сначала удалите её из узлов поиска по базе знаний."
|
||||
knowledge.rag.fallback.noAnswer: "В базе знаний пока нет однозначной информации."
|
||||
knowledge.rag.fallback.suggestRetry: "В базе знаний недостаточно однозначной информации. Попробуйте задать более конкретный вопрос."
|
||||
knowledge.rag.system.assist: "Вы помощник службы поддержки по базе знаний. Отвечайте прежде всего на основе предоставленных фрагментов. Можно обобщать их, но нельзя придумывать отсутствующие факты. Отвечайте на русском языке."
|
||||
knowledge.rag.system.strict: "Вы помощник службы поддержки, строго использующий базу знаний. Отвечайте только на основе предоставленных фрагментов. Если их недостаточно, прямо сообщите, что в базе знаний нет однозначной информации. Отвечайте на русском языке."
|
||||
knowledge.rag.userPrompt: "Вопрос пользователя: %s\n\nСправочные материалы:\n%s"
|
||||
|
||||
knowledge.excel.question: "Основной вопрос"
|
||||
knowledge.excel.answer: "Ответ"
|
||||
knowledge.excel.similarQuestions: "Похожие вопросы"
|
||||
knowledge.excel.remark: "Примечания"
|
||||
knowledge.excel.exampleQuestion: "Как сбросить пароль?"
|
||||
knowledge.excel.exampleAnswer: "Откройте настройки профиля и выберите «Сбросить пароль»."
|
||||
knowledge.excel.exampleSimilar: "Я забыл пароль\nГде можно сбросить пароль?"
|
||||
knowledge.excel.exampleRemark: "Учётная запись"
|
||||
knowledge.rag.context.faq: "[FAQ: %s]\n%s\n\n"
|
||||
knowledge.rag.context.faqTitle: "FAQ#%d"
|
||||
knowledge.rag.context.documentTitle: "Документ №%d"
|
||||
knowledge.rag.context.documentSection: "[Документ: %s | Раздел: %s]\n%s\n\n"
|
||||
knowledge.rag.context.documentHeading: "[Документ: %s | Заголовок: %s]\n%s\n\n"
|
||||
knowledge.rag.context.document: "[Документ: %s]\n%s\n\n"
|
||||
knowledge.rag.faq.question: "Вопрос: %s"
|
||||
knowledge.rag.faq.similarQuestions: "Похожие вопросы: %s"
|
||||
knowledge.rag.faq.answer: "Ответ: %s"
|
||||
knowledge.rag.faq.separator: "; "
|
||||
|
||||
# Support availability validation.
|
||||
error.supportAvailability.invalidCalendar: "Укажите корректный часовой пояс, часы работы по дням недели и исключения по датам."
|
||||
error.supportAvailability.invalidAbsence: "Для отпуска укажите начало и окончание с часовым поясом. Окончание должно быть позже начала."
|
||||
error.supportAvailability.unavailable: "Этот оператор сейчас не может принимать новые обращения. Обновите данные о доступности и повторите попытку."
|
||||
error.supportAvailability.persistence: "Не удалось загрузить или сохранить настройки доступности поддержки. Повторите попытку."
|
||||
error.supportAvailability.noProfile: "Требуется активный профиль оператора поддержки."
|
||||
error.supportAvailability.invalidStatus: "Выберите корректный рабочий статус доступности."
|
||||
error.supportAvailability.invalidTime: "Укажите дату и время со смещением часового пояса."
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Modified in the independent hardening fork; upstream notices retained.
|
||||
error.e0001: "AI Agent not found"
|
||||
error.e0002: "AI Agent 不存在"
|
||||
error.e0003: "AI Agent 不存在或已停用"
|
||||
@@ -567,3 +568,77 @@ tool.graph.prepareTicketDraft.param.expectedOutcome: "用户期望的处理结
|
||||
tool.graph.prepareTicketDraft.param.currentAttempt: "当前已尝试过的处理步骤,可选。"
|
||||
error.setup.invalidQuickFixAction: "不支持的一键修复操作:%v"
|
||||
error.setup.authRequired: "请先登录以执行配置诊断或一键修复"
|
||||
|
||||
error.setup.previewRequired: "应用更改前,请先预览设置并明确确认预览令牌。"
|
||||
error.setup.previewChanged: "预览后配置已更改。请重新预览后再应用。"
|
||||
error.setup.targetRequired: "请选择具体记录,或在对应设置中选择所需配置。"
|
||||
error.setup.targetUnavailable: "所选记录不可用、已删除或不符合条件,未进行恢复。"
|
||||
error.setup.persistenceFailed: "无法读取或保存设置配置,未提交不完整的修复。"
|
||||
error.setup.bypassUnsupported: "此诊断不支持绕过 AI。请检查仅人工路由,或单独测试 AI。"
|
||||
error.channel.configChanged: "渠道配置已更改。请重新加载并检查更改后再保存。"
|
||||
error.channel.configUpdateFailed: "无法保存渠道配置,请重试。"
|
||||
|
||||
# Knowledge-base display labels and safe application-owned messages.
|
||||
knowledge.labels.unknown: "未知"
|
||||
knowledge.labels.type.document: "文档知识库"
|
||||
knowledge.labels.type.faq: "FAQ知识库"
|
||||
knowledge.labels.status.0: "启用"
|
||||
knowledge.labels.status.1: "禁用"
|
||||
knowledge.labels.status.2: "已删除"
|
||||
knowledge.labels.index.pending: "待索引"
|
||||
knowledge.labels.index.indexed: "已索引"
|
||||
knowledge.labels.index.failed: "索引失败"
|
||||
knowledge.labels.mode.1: "严格模式"
|
||||
knowledge.labels.mode.2: "辅助模式"
|
||||
knowledge.labels.channel.im: "客服会话"
|
||||
knowledge.labels.channel.agent_assist: "客服助手"
|
||||
knowledge.labels.channel.api: "API接口"
|
||||
knowledge.labels.channel.debug: "调试"
|
||||
knowledge.labels.scene.first_response: "首次回复"
|
||||
knowledge.labels.scene.assist: "辅助回复"
|
||||
knowledge.labels.scene.qa: "问答"
|
||||
knowledge.labels.answer.1: "正常"
|
||||
knowledge.labels.answer.2: "无答案"
|
||||
knowledge.labels.answer.3: "兜底回复"
|
||||
knowledge.labels.answer.4: "已屏蔽"
|
||||
knowledge.labels.chunk.text: "文本"
|
||||
knowledge.labels.chunk.faq: "问答"
|
||||
knowledge.labels.chunk.table: "表格"
|
||||
knowledge.labels.chunk.code: "代码"
|
||||
error.knowledge.indexFailed: "索引失败。请检查索引配置后重试。"
|
||||
error.knowledge.retrieveFailed: "知识检索失败。请检查检索配置后重试。"
|
||||
error.knowledge.workflowReference: "知识库正在被流程「%s」使用,请先从知识检索节点中移除"
|
||||
error.knowledge.workflowReferences: "知识库正在被 %d 个流程使用,请先从知识检索节点中移除"
|
||||
knowledge.rag.fallback.noAnswer: "当前知识库暂无明确信息。"
|
||||
knowledge.rag.fallback.suggestRetry: "当前知识库里没有找到足够明确的信息,你可以换个更具体的问法再试一次。"
|
||||
knowledge.rag.system.assist: "你是客服知识库助手。请优先依据提供的知识片段回答,可以做轻度归纳,但不要编造未提供的事实。请使用中文回答。"
|
||||
knowledge.rag.system.strict: "你是严格的客服知识库助手。只能依据提供的知识片段回答;如果资料不足,请明确说明知识库暂无明确信息。请使用中文回答。"
|
||||
knowledge.rag.userPrompt: "用户问题:%s\n\n参考资料:\n%s"
|
||||
|
||||
knowledge.excel.question: "标准问题"
|
||||
knowledge.excel.answer: "答案"
|
||||
knowledge.excel.similarQuestions: "相似问"
|
||||
knowledge.excel.remark: "备注"
|
||||
knowledge.excel.exampleQuestion: "如何重置密码?"
|
||||
knowledge.excel.exampleAnswer: "进入个人设置后点击重置密码。"
|
||||
knowledge.excel.exampleSimilar: "忘记密码怎么办\n重置密码在哪里"
|
||||
knowledge.excel.exampleRemark: "账号"
|
||||
knowledge.rag.context.faq: "【FAQ:%s】\n%s\n\n"
|
||||
knowledge.rag.context.faqTitle: "FAQ#%d"
|
||||
knowledge.rag.context.documentTitle: "文档#%d"
|
||||
knowledge.rag.context.documentSection: "【文档:%s|章节:%s】\n%s\n\n"
|
||||
knowledge.rag.context.documentHeading: "【文档:%s|标题:%s】\n%s\n\n"
|
||||
knowledge.rag.context.document: "【文档:%s】\n%s\n\n"
|
||||
knowledge.rag.faq.question: "问题:%s"
|
||||
knowledge.rag.faq.similarQuestions: "相似问:%s"
|
||||
knowledge.rag.faq.answer: "回答:%s"
|
||||
knowledge.rag.faq.separator: ";"
|
||||
|
||||
# Support availability validation.
|
||||
error.supportAvailability.invalidCalendar: "请输入有效的时区、每周工作时间和特殊日期规则。"
|
||||
error.supportAvailability.invalidAbsence: "休假需要包含明确时区的开始和结束时间,结束时间必须晚于开始时间。"
|
||||
error.supportAvailability.unavailable: "该客服当前无法接收新会话。请刷新可用状态后重试。"
|
||||
error.supportAvailability.persistence: "无法加载或保存客服可用状态。请重试。"
|
||||
error.supportAvailability.noProfile: "需要已启用的客服资料。"
|
||||
error.supportAvailability.invalidStatus: "请选择有效的工作状态。"
|
||||
error.supportAvailability.invalidTime: "请输入包含时区偏移的日期和时间。"
|
||||
|
||||
Reference in New Issue
Block a user