tavern-harness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/README.zh.md +123 -0
- package/bin/cli.js +48 -0
- package/index.html +35 -0
- package/package.json +50 -0
- package/public/favicon.png +0 -0
- package/public/tavern-harness-logo.png +0 -0
- package/sandbox-server.mjs +997 -0
- package/src/App.tsx +268 -0
- package/src/assets/tavern-handbook.md +39 -0
- package/src/components/AchievementModal.tsx +158 -0
- package/src/components/CharactersView.tsx +904 -0
- package/src/components/ChatView.tsx +1639 -0
- package/src/components/ConfirmationDialog.tsx +56 -0
- package/src/components/Dashboard.tsx +180 -0
- package/src/components/DeleteConfirmDialog.tsx +48 -0
- package/src/components/FileDisplayModal.tsx +556 -0
- package/src/components/GameplayDialogs.tsx +191 -0
- package/src/components/NewSessionMenu.tsx +362 -0
- package/src/components/SettingsView.tsx +517 -0
- package/src/components/Sidebar.tsx +392 -0
- package/src/components/StatsView.tsx +189 -0
- package/src/components/Toasts.tsx +20 -0
- package/src/components/WorkspaceFileManagerModal.tsx +216 -0
- package/src/components/shared.tsx +369 -0
- package/src/core/achievements.ts +85 -0
- package/src/core/fileDownload.ts +67 -0
- package/src/core/gameplay.ts +521 -0
- package/src/core/horizontalWheel.ts +93 -0
- package/src/core/i18n.ts +1403 -0
- package/src/core/linkedScroll.ts +236 -0
- package/src/core/markdown.ts +170 -0
- package/src/core/openai.ts +451 -0
- package/src/core/openingGreeting.ts +41 -0
- package/src/core/prompts.ts +254 -0
- package/src/core/proxy.ts +58 -0
- package/src/core/sillyTavernImporter.ts +317 -0
- package/src/core/stats.ts +91 -0
- package/src/core/strings.ts +19 -0
- package/src/core/toolDefinitions.ts +400 -0
- package/src/core/tools/builtinTools.ts +38 -0
- package/src/core/tools/generatedSkillExecutor.ts +706 -0
- package/src/core/tools/generatedWorkspace.ts +13 -0
- package/src/core/tools/toolExecutor.ts +718 -0
- package/src/core/turnLoop.ts +211 -0
- package/src/db/database.ts +647 -0
- package/src/main.tsx +23 -0
- package/src/store/store.ts +2419 -0
- package/src/theme/chat.css +1227 -0
- package/src/theme/global.css +1377 -0
- package/src/theme/theme.ts +143 -0
- package/src/theme/views.css +1430 -0
- package/src/types/models.ts +364 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +21 -0
- package/vite.config.ts +273 -0
package/src/core/i18n.ts
ADDED
|
@@ -0,0 +1,1403 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from 'react';
|
|
2
|
+
import type { AppLanguage } from '../types/models';
|
|
3
|
+
|
|
4
|
+
// ============================================================
|
|
5
|
+
// 多语言(i18n)
|
|
6
|
+
// - useT() :React 响应式 hook(组件顶部调用),返回 t(key, params)
|
|
7
|
+
// - translate() :静态翻译(store / 工具执行器 / 非组件模块使用),
|
|
8
|
+
// 读取当前语言模块级快照
|
|
9
|
+
// - setLanguage(): 由 store 在初始化与切换语言时调用(设置 <html lang> + 通知订阅者)
|
|
10
|
+
// - 键前缀约定:common. nav. chat. settings. workshop. stats. dash. header. newSession.
|
|
11
|
+
// gameplay. confirm. deleteConfirm. toast. tool. ach. achModal. display. prompt. builtinNpc.
|
|
12
|
+
// ============================================================
|
|
13
|
+
|
|
14
|
+
type LangKey = Exclude<AppLanguage, null>;
|
|
15
|
+
|
|
16
|
+
const MESSAGES: Record<LangKey, Record<string, string>> = {
|
|
17
|
+
// ---------------- 简体中文(默认) ----------------
|
|
18
|
+
'zh-CN': {
|
|
19
|
+
// ---- 通用 ----
|
|
20
|
+
'common.cancel': '取消',
|
|
21
|
+
'common.delete': '删除',
|
|
22
|
+
'common.save': '保存',
|
|
23
|
+
'common.ok': '确定',
|
|
24
|
+
'common.clear': '清除',
|
|
25
|
+
'common.close': '关闭',
|
|
26
|
+
'common.user': '用户',
|
|
27
|
+
'common.you': '你',
|
|
28
|
+
'common.me': '我',
|
|
29
|
+
'common.builtin': '内置',
|
|
30
|
+
'common.edit': '编辑',
|
|
31
|
+
'common.confirmDelete': '确定删除「{name}」?该操作不可恢复。',
|
|
32
|
+
'common.removeAttachment': '移除附件',
|
|
33
|
+
'common.attachment': '附件',
|
|
34
|
+
|
|
35
|
+
// ---- 侧边栏 / 导航 ----
|
|
36
|
+
'nav.chat': '对话',
|
|
37
|
+
'nav.characters': '角色工坊',
|
|
38
|
+
'nav.achievements': '成就',
|
|
39
|
+
'nav.settings': '设置',
|
|
40
|
+
'nav.brandSub': '酒馆营业时间 0:00~23:59',
|
|
41
|
+
'nav.sessions': '会话',
|
|
42
|
+
'nav.sessionActive': '正在生成或有新回复',
|
|
43
|
+
'nav.newChat': '新建',
|
|
44
|
+
'nav.newChatTitle': '新建对话',
|
|
45
|
+
'nav.importGameplay': '导入',
|
|
46
|
+
'nav.importGameplayTitle': '导入游戏(JSON 文件,重建角色/世界书/技能/对话)',
|
|
47
|
+
'nav.exportGameplay': '导出游戏',
|
|
48
|
+
'nav.searchPh': '搜索会话(标题或内容)…',
|
|
49
|
+
'nav.empty': '还没有会话,先开一局?',
|
|
50
|
+
'nav.noMatch': '没有匹配的会话',
|
|
51
|
+
'nav.newConversation': '开始新对话…',
|
|
52
|
+
'nav.deleteSession': '删除会话',
|
|
53
|
+
'nav.editSession': '编辑会话',
|
|
54
|
+
'nav.pinSession': '置顶对话',
|
|
55
|
+
'nav.unpinSession': '取消置顶',
|
|
56
|
+
'nav.newStandardTitle': '新对话',
|
|
57
|
+
'nav.newNpcTitle': '新角色对话',
|
|
58
|
+
'nav.newGroupTitle': '新群聊',
|
|
59
|
+
'nav.loading': '正在支起酒馆…',
|
|
60
|
+
'nav.deleteSessionConfirm': '确定删除会话「{title}」?该操作不可恢复,会同时删除其中的全部消息,并清理会话的专属工作目录。',
|
|
61
|
+
'nav.resize': '拖动调整宽度',
|
|
62
|
+
'nav.home': '回到首页',
|
|
63
|
+
|
|
64
|
+
// ---- 会话头部(App) ----
|
|
65
|
+
'header.modeStandard': '标准对话',
|
|
66
|
+
'header.modeNpc': '角色对话',
|
|
67
|
+
'header.modeGroup': '群聊',
|
|
68
|
+
'header.editSessionTip': '编辑对话设置',
|
|
69
|
+
'header.resetSessionTip': '重置对话',
|
|
70
|
+
'header.resetSessionTitle': '重置对话',
|
|
71
|
+
'header.resetSessionConfirm': '确定重置对话「{title}」?将清空消息并按最新设置重新开始。',
|
|
72
|
+
'header.resetSessionAction': '重置对话',
|
|
73
|
+
'header.exportGameplayTip': '导出游戏(角色/世界书/技能/对话设置,可导入重建)',
|
|
74
|
+
'header.deleteTip': '删除对话',
|
|
75
|
+
'header.deleteTitle': '删除对话',
|
|
76
|
+
'header.deleteConfirm': '确定删除对话「{title}」?该操作不可恢复。',
|
|
77
|
+
'header.workspaceHint': '本对话的专属工作目录。文件工具限制在该目录内;本地命令以此为起始目录,但不具备 OS 隔离。点击可浏览目录文件(只读)。',
|
|
78
|
+
'header.workspaceRootHint': '酒馆老板的单人对话固定使用公共工作目录。文件工具限制在该目录内;本地命令不具备 OS 隔离。点击可浏览文件(只读)。',
|
|
79
|
+
'header.fileManagerTitle': '工作区文件',
|
|
80
|
+
'header.fileManagerReadonly': '只读浏览 · 点击文件可预览',
|
|
81
|
+
'header.fileManagerHint': '点击文件可预览,点击删除按钮可删除文件',
|
|
82
|
+
'header.fileManagerDeleteTitle': '删除工作区文件',
|
|
83
|
+
'header.fileManagerEmpty': '此工作区还没有文件',
|
|
84
|
+
'header.fileManagerError': '无法读取工作区文件',
|
|
85
|
+
'header.workspaceRoot': 'public',
|
|
86
|
+
|
|
87
|
+
'gameplay.exportTitle': '导出游戏',
|
|
88
|
+
'gameplay.importTitle': '导入游戏',
|
|
89
|
+
'gameplay.includeHistory': '包含对话历史',
|
|
90
|
+
'gameplay.includeHistoryHint': '勾选后,导出的 JSON 将会包含全部消息记录',
|
|
91
|
+
'gameplay.exportSummary': '导出内容将包含:全部参与角色(人设 / 开场白 / 头像 / 启用的技能)、世界书与所需的生成式技能。对方导入后可完全重建这一局游戏。',
|
|
92
|
+
'gameplay.exportAction': '导出游戏',
|
|
93
|
+
'gameplay.importAction': '导入并重建',
|
|
94
|
+
'gameplay.importSummary': '选择游戏 JSON 文件后,将自动创建所需角色、世界书、技能与对话;若文件包含对话历史,会一并重建。名称相同的角色 / 世界书 / 技能会创建副本(不覆盖已有数据),会话始终新建。',
|
|
95
|
+
'gameplay.chooseFile': '选择游戏 JSON 文件…',
|
|
96
|
+
'gameplay.invalidFile': '所选文件不是有效的游戏 JSON',
|
|
97
|
+
'gameplay.busy': '处理中…',
|
|
98
|
+
|
|
99
|
+
// ---- 聊天区 ----
|
|
100
|
+
'chat.emptyStandardTitle': '开始一段新的对话',
|
|
101
|
+
'chat.emptyStandardSub': '输入消息开始聊天',
|
|
102
|
+
'chat.emptyNpcTitle': '与 {name} 对话',
|
|
103
|
+
'chat.emptyGroupTitle': '群聊',
|
|
104
|
+
'chat.emptyGroupSub': '多位角色轮番发言',
|
|
105
|
+
'chat.speakerChar': '角色',
|
|
106
|
+
'chat.speakerAssistant': '助手',
|
|
107
|
+
'chat.thinking': '思考',
|
|
108
|
+
'chat.editMsg': '编辑消息',
|
|
109
|
+
'chat.addAttachment': '添加附件',
|
|
110
|
+
'chat.saveHint': '⌘/Ctrl+Enter 保存 · Esc 取消',
|
|
111
|
+
'chat.saveRegenerate': '保存并重新生成',
|
|
112
|
+
'chat.saveOnly': '仅保存',
|
|
113
|
+
'chat.toolCall': '调用工具: {name}',
|
|
114
|
+
'chat.contextCompressed': '上下文已压缩',
|
|
115
|
+
'chat.chooseModel': '选模型',
|
|
116
|
+
'chat.selectModel': '选择模型',
|
|
117
|
+
'chat.attachTip': '添加图片/视频(粘贴或点击)',
|
|
118
|
+
'chat.phGroup': '输入消息… 支持 @角色名 指定发言,/new 新话题,/pass 跳过',
|
|
119
|
+
'chat.phSingle': '输入消息… /new 开始新话题',
|
|
120
|
+
'chat.mentionHead': '@ 点名角色',
|
|
121
|
+
'chat.stopTip': '停止生成',
|
|
122
|
+
'chat.sendTip': '发送',
|
|
123
|
+
'chat.hintSend': 'Enter 发送 · Shift+Enter 换行',
|
|
124
|
+
'chat.hintMagic': '魔法命令: /new · /pass',
|
|
125
|
+
'chat.hintMention': ' · @角色名 点名',
|
|
126
|
+
'chat.cmdNew': '开始新话题(截断上下文)',
|
|
127
|
+
'chat.cmdPass': '跳过本轮发言(群聊中,不修改对话历史)',
|
|
128
|
+
'chat.modelEmpty': '暂无可用模型列表。请先在「设置 → 模型服务 Provider」添加并测试连接。',
|
|
129
|
+
'chat.noActions': '该消息无可用操作',
|
|
130
|
+
'chat.regenerate': '重新生成回复',
|
|
131
|
+
'chat.viewRaw': '查看原始日志',
|
|
132
|
+
'chat.viewMemory': '查看记忆',
|
|
133
|
+
'chat.fork': '创建分支 / Fork',
|
|
134
|
+
'chat.rawLogTitle': '原始日志 · 消息 #{id}',
|
|
135
|
+
'chat.requestBody': '请求体',
|
|
136
|
+
'chat.fullResponse': '完整响应',
|
|
137
|
+
'chat.exportTip': '下载为 txt 文件',
|
|
138
|
+
'chat.noRequestBody': '(无请求体)',
|
|
139
|
+
'chat.fullResponseJson': '完整回复 JSON',
|
|
140
|
+
'chat.fromSse': '从流式 SSE 分片拼装',
|
|
141
|
+
'chat.noRawContent': '该消息没有可拼装的流式内容',
|
|
142
|
+
'chat.pasteImage': '粘贴图片.{ext}',
|
|
143
|
+
'chat.loop': '第 {n} 轮',
|
|
144
|
+
'chat.current': '当前',
|
|
145
|
+
'chat.queueTitle': '发言队列',
|
|
146
|
+
'chat.queueLoops': '共 {n} 轮',
|
|
147
|
+
'chat.queueRandom': '随机顺序(每轮洗牌)',
|
|
148
|
+
'chat.queueFixed': '固定座位顺序',
|
|
149
|
+
'chat.queueLocateSelf': '点击定位到你的发言',
|
|
150
|
+
'chat.queueLocateNpc': '点击定位到 {name} 的发言',
|
|
151
|
+
'chat.sortRandom': '随机顺序',
|
|
152
|
+
'chat.collpMore': '(点击展开完整内容)',
|
|
153
|
+
|
|
154
|
+
// ---- 仪表盘 ----
|
|
155
|
+
'dash.welcome': '欢迎回到酒馆 🫖',
|
|
156
|
+
'dash.hero': 'Tavern Harness — 更自由、更好玩、更易上手的酒馆!\n支持任意 OpenAI 兼容 API,支持本地模型,全部数据都存储在你的本地;\n支持受控本地技能,接入更多系统权限,解锁更多文本对话无法实现的游戏;\n酒馆已经完成智能体原生装修,有什么想要的,',
|
|
157
|
+
'dash.heroAccent': '为什么不问问酒馆老板呢~',
|
|
158
|
+
'dash.newChat': '新建对话',
|
|
159
|
+
'dash.goCharacters': '前往角色工坊',
|
|
160
|
+
'dash.openWiki': '酒馆经营手册',
|
|
161
|
+
'dash.continue': '与角色继续对话',
|
|
162
|
+
'dash.noCharConv': '还没有角色对话,从下面的角色卡片开始',
|
|
163
|
+
'dash.groupSession': '群聊',
|
|
164
|
+
'dash.newSession': '新对话',
|
|
165
|
+
'dash.timeJustNow': '刚刚',
|
|
166
|
+
'dash.timeMinAgo': '{n} 分钟前',
|
|
167
|
+
'dash.timeHourAgo': '{n} 小时前',
|
|
168
|
+
'dash.timeDayAgo': '{n} 天前',
|
|
169
|
+
'dash.regulars': '酒馆常客',
|
|
170
|
+
'dash.startChat': '开始对话',
|
|
171
|
+
|
|
172
|
+
// ---- 设置 ----
|
|
173
|
+
'settings.title': '设置',
|
|
174
|
+
'settings.sub': '模型 API 配置、超参数与主题',
|
|
175
|
+
'settings.genParams': '🎛 生成参数',
|
|
176
|
+
'settings.tempDesc': '创造性与随机性',
|
|
177
|
+
'settings.topPDesc': '核采样',
|
|
178
|
+
'settings.topKDesc': '候选数量',
|
|
179
|
+
'settings.maxTokensDesc': '0 = 不限制',
|
|
180
|
+
'settings.freqDesc': '重复惩罚',
|
|
181
|
+
'settings.presDesc': '话题新颖度',
|
|
182
|
+
'settings.repDesc': '重复惩罚',
|
|
183
|
+
'settings.effortDesc': '思考强度',
|
|
184
|
+
'settings.contextCompression': 'Context Window Size',
|
|
185
|
+
'settings.contextCompressionDesc': '上下文压缩触发窗口阈值',
|
|
186
|
+
'settings.off': '关闭',
|
|
187
|
+
'settings.theme': '🎨 主题',
|
|
188
|
+
'settings.appearance': '外观模式',
|
|
189
|
+
'settings.system': '跟随系统',
|
|
190
|
+
'settings.light': '浅色',
|
|
191
|
+
'settings.dark': '深色',
|
|
192
|
+
'settings.themeTip': '默认赛博琥珀主题色,外观模式可跟随系统',
|
|
193
|
+
'settings.language': '🌐 界面语言/Language',
|
|
194
|
+
'settings.languageLabel': '界面语言/Language',
|
|
195
|
+
'settings.followBrowser': '跟随系统语言',
|
|
196
|
+
'settings.resetTitle': '⚠️ 重置当前酒馆',
|
|
197
|
+
'settings.resetDesc': '清除当前酒馆的全部本地数据并恢复初始状态,不影响其他端口或浏览器中打开的酒馆。',
|
|
198
|
+
'settings.resetBtn': '重置当前酒馆',
|
|
199
|
+
'settings.resetConfirmTitle': '重置当前酒馆',
|
|
200
|
+
'settings.resetConfirm': '确定重置当前酒馆吗?所有对话、角色、世界书、技能、模型服务与统计数据都会被删除,且无法恢复。其他端口或浏览器中打开的酒馆不受影响。',
|
|
201
|
+
|
|
202
|
+
// ---- 内置角色「酒馆老板」(首次启动种子数据,随语言切换) ----
|
|
203
|
+
'builtinNpc.name': '酒馆老板',
|
|
204
|
+
'builtinNpc.prompt': '神秘的酒馆老板,可以响应客人的任何需求',
|
|
205
|
+
'builtinNpc.greeting': '你来啦!快坐下~',
|
|
206
|
+
'builtinWorldbook.name': '酒馆经营手册(精华版)',
|
|
207
|
+
'settings.providerTitle': '🔌 模型服务 Provider(多端点)',
|
|
208
|
+
'settings.noProvider': '还没有配置 Provider,点击下方按钮添加',
|
|
209
|
+
'settings.enable': '启用',
|
|
210
|
+
'settings.disable': '停用',
|
|
211
|
+
'settings.testing': '测试中',
|
|
212
|
+
'settings.test': '测试',
|
|
213
|
+
'settings.models': '模型:',
|
|
214
|
+
'settings.emptyModels': '空',
|
|
215
|
+
'settings.modelAlias': '展示别名',
|
|
216
|
+
'settings.modelAliasPh': '留空则显示原模型名',
|
|
217
|
+
'settings.addProvider': '添加 Provider',
|
|
218
|
+
'settings.editProvider': '编辑 Provider',
|
|
219
|
+
'settings.deleteProvider': '删除 Provider',
|
|
220
|
+
'settings.providerName': '名称',
|
|
221
|
+
'settings.providerNamePh': '如:DeepSeek 官方',
|
|
222
|
+
'settings.baseUrl': 'Base URL',
|
|
223
|
+
'settings.baseUrlPh': 'https://api.deepseek.com/v1/ 或 http://192.168.x.x:8788/',
|
|
224
|
+
'settings.apiKey': 'API Key',
|
|
225
|
+
'settings.apiKeyPh': 'sk-...',
|
|
226
|
+
|
|
227
|
+
// ---- 角色工坊 ----
|
|
228
|
+
'workshop.title': '角色工坊',
|
|
229
|
+
'workshop.sub': '管理 NPC、世界书与技能',
|
|
230
|
+
'workshop.tabCharacters': '👤 角色卡',
|
|
231
|
+
'workshop.tabWorldbooks': '📖 世界书',
|
|
232
|
+
'workshop.tabSkills': '🛠 技能表',
|
|
233
|
+
'workshop.charCount': '角色卡({n})',
|
|
234
|
+
'workshop.pngImport': 'PNG 导入',
|
|
235
|
+
'workshop.pngImportTip': '导入 SillyTavern PNG 角色卡(chara V2 / ccv3)',
|
|
236
|
+
'workshop.pngImportWbTip': '导入 SillyTavern PNG 角色卡(chara V2 / ccv3),自动解析内嵌世界书',
|
|
237
|
+
'workshop.newChar': '新建角色',
|
|
238
|
+
'workshop.noGreeting': '(无开场白)',
|
|
239
|
+
'workshop.protectedTip': '内置角色受保护',
|
|
240
|
+
'workshop.uploadAvatar': '上传头像',
|
|
241
|
+
'workshop.name': '名称',
|
|
242
|
+
'workshop.charNamePh': '角色名',
|
|
243
|
+
'workshop.promptLabel': '人设 Prompt(注入 system prompt)',
|
|
244
|
+
'workshop.promptPh': '例如:神秘的酒馆老板,可以响应客人的任何需求',
|
|
245
|
+
'workshop.greetingLabel': '开场白 Greeting',
|
|
246
|
+
'workshop.greetingRandomHint': '(如果创建了多个开场白,每次新对话将从中随机选择一个)',
|
|
247
|
+
'workshop.greetingPh': '你来啦!快坐下~',
|
|
248
|
+
'workshop.addAltGreeting': '添加开场白',
|
|
249
|
+
'workshop.resetChar': '恢复默认',
|
|
250
|
+
'workshop.resetCharTip': '恢复酒馆老板的默认人设、开场白和装备技能',
|
|
251
|
+
'workshop.enableSkills': '启用技能({a}/{b})',
|
|
252
|
+
'workshop.noSkills': '暂无技能',
|
|
253
|
+
'workshop.newCharTitle': '新建角色',
|
|
254
|
+
'workshop.editCharTitle': '编辑角色 · {name}',
|
|
255
|
+
'workshop.deleteCharTitle': '删除角色',
|
|
256
|
+
'workshop.wbCount': '世界书({n})',
|
|
257
|
+
'workshop.newWb': '新建世界书',
|
|
258
|
+
'workshop.wbEmpty': '暂无世界书,为角色建立世界观吧',
|
|
259
|
+
'workshop.wbChars': '{n} 字',
|
|
260
|
+
'workshop.browseEdit': '浏览/编辑',
|
|
261
|
+
'workshop.editWbTitle': '编辑世界书',
|
|
262
|
+
'workshop.newWbTitle': '新建世界书',
|
|
263
|
+
'workshop.wbName': '世界书名',
|
|
264
|
+
'workshop.wbNamePh': '如:北境大陆',
|
|
265
|
+
'workshop.wbContentLabel': '内容(附加在角色人设之后)',
|
|
266
|
+
'workshop.wbContentPh': '世界观设定文本…',
|
|
267
|
+
'workshop.wbMarkdownRender': 'Markdown 渲染',
|
|
268
|
+
'workshop.resetWb': '恢复默认',
|
|
269
|
+
'workshop.resetWbTip': '恢复内置世界书的默认内容',
|
|
270
|
+
'workshop.deleteWbTitle': '删除世界书',
|
|
271
|
+
'workshop.skillGroupBuiltin': '内置技能({n})',
|
|
272
|
+
'workshop.skillGroupCustom': '自定义技能({n})',
|
|
273
|
+
'workshop.skillGroupImported': '导入技能({n})',
|
|
274
|
+
'workshop.implType': '实现类型: {t}',
|
|
275
|
+
'workshop.sortHint': '上下拖动卡片即可调整排序',
|
|
276
|
+
'workshop.deleteSkillTitle': '删除技能',
|
|
277
|
+
'workshop.skillDetails': '技能详情',
|
|
278
|
+
'workshop.skillName': '技能名称',
|
|
279
|
+
'workshop.skillNameInvalid': '技能名称须以小写字母开头,并仅含小写字母、数字或下划线(3–40 个字符)',
|
|
280
|
+
'workshop.skillNameExists': '该技能名称已存在',
|
|
281
|
+
'workshop.skillDescriptionLong': '描述不能超过 500 个字符',
|
|
282
|
+
'workshop.skillSchemaJson': '参数 Schema 必须是有效的 JSON',
|
|
283
|
+
'workshop.skillSchemaObject': '参数 Schema 必须是 JSON 对象',
|
|
284
|
+
'workshop.viewDetails': '详情',
|
|
285
|
+
'workshop.skillDescription': '描述',
|
|
286
|
+
'workshop.skillParameters': '参数 Schema',
|
|
287
|
+
'workshop.skillExecution': '声明式实现',
|
|
288
|
+
'workshop.skillRawJson': '原始 JSON(OpenAI Tool 定义)',
|
|
289
|
+
'workshop.skillNoExecution': '(无声明式实现,由内置逻辑执行)',
|
|
290
|
+
'workshop.skillNoParams': '(无参数)',
|
|
291
|
+
'workshop.skillBadJson': '(JSON 解析失败)',
|
|
292
|
+
|
|
293
|
+
// ---- 成就 / 统计 ----
|
|
294
|
+
'stats.title': '🏆 成就',
|
|
295
|
+
'stats.sub': '你的成就奖杯会陈列在这里',
|
|
296
|
+
'stats.resetBtn': '重置统计',
|
|
297
|
+
'stats.resetConfirm': '确定重置生涯统计?(不会清除已解锁的成就奖杯)',
|
|
298
|
+
'stats.shelfTitle': '🎖️ 奖杯陈列柜',
|
|
299
|
+
'stats.unlocked': '已解锁',
|
|
300
|
+
'stats.trophies': '枚奖杯',
|
|
301
|
+
'stats.locked': '未解锁',
|
|
302
|
+
'stats.unlockedStatus': '✓ 已解锁',
|
|
303
|
+
'stats.hidden': '???',
|
|
304
|
+
'stats.unlockedAt': '解锁',
|
|
305
|
+
'stats.totalTokens': '总 Tokens(输入 + 输出)',
|
|
306
|
+
'stats.inputTokens': '输入 Tokens',
|
|
307
|
+
'stats.outputTokens': '输出 Tokens',
|
|
308
|
+
'stats.rounds': '对话轮数',
|
|
309
|
+
'stats.sessions': '累计会话',
|
|
310
|
+
'stats.avgRounds': '平均轮数 / 会话',
|
|
311
|
+
'stats.mostActive': '🏆 最活跃角色',
|
|
312
|
+
'stats.roundsCount': '{n} 轮对话',
|
|
313
|
+
'stats.noData': '暂无数据,去和角色聊聊天吧',
|
|
314
|
+
'stats.ranking': '🎭 角色轮数排行',
|
|
315
|
+
'stats.colChar': '角色',
|
|
316
|
+
'stats.colRounds': '轮数',
|
|
317
|
+
|
|
318
|
+
// ---- 成就定义 ----
|
|
319
|
+
'ach.traveler.name': '旅人',
|
|
320
|
+
'ach.traveler.desc': '累计使用 1,000 Token',
|
|
321
|
+
'ach.oldFriend.name': '老友',
|
|
322
|
+
'ach.oldFriend.desc': '累计使用 10,000 Token',
|
|
323
|
+
'ach.bard.name': '吟游诗人',
|
|
324
|
+
'ach.bard.desc': '累计使用 100,000 Token',
|
|
325
|
+
'ach.nightOwl.name': '午夜不归人',
|
|
326
|
+
'ach.nightOwl.desc': '累计使用 1,000,000 Token',
|
|
327
|
+
'ach.legend.name': '酒馆传奇',
|
|
328
|
+
'ach.legend.desc': '累计使用 100,000,000 Token',
|
|
329
|
+
|
|
330
|
+
// ---- 成就解锁弹窗 ----
|
|
331
|
+
'achModal.title': '🎉 成就解锁!',
|
|
332
|
+
'achModal.total': '生涯已累计 {n} Tokens',
|
|
333
|
+
'achModal.accept': '收下奖杯',
|
|
334
|
+
|
|
335
|
+
// ---- 新建对话 ----
|
|
336
|
+
'newSession.title': '创建对话',
|
|
337
|
+
'newSession.name': '对话名称',
|
|
338
|
+
'newSession.unnamed': '未命名',
|
|
339
|
+
'newSession.participants': '参与角色',
|
|
340
|
+
'newSession.noNpcWarn': '⚠️ 请先在「角色工坊」创建角色卡!',
|
|
341
|
+
'newSession.notSelected': '未选择',
|
|
342
|
+
'newSession.npcChat': 'NPC 对话',
|
|
343
|
+
'newSession.groupChat': '群聊({n} 人)',
|
|
344
|
+
'newSession.orderHint': '拖动设置发言顺序,或开启随机顺序。',
|
|
345
|
+
'newSession.mentionOnly': '仅 @ 模式',
|
|
346
|
+
'newSession.mentionOnlyHint': '用户必须 @ 角色名;角色未 @ 时自动将对话权交还给用户。',
|
|
347
|
+
'newSession.greeting': '启用开场白',
|
|
348
|
+
'newSession.greetingHint': '单人对话启用 NPC 开场白;群聊仅当首位发言者是角色时启用其开场白。',
|
|
349
|
+
'newSession.persona': '用户人设(可选)',
|
|
350
|
+
'newSession.noPersona': '不使用用户人设',
|
|
351
|
+
'newSession.worldbook': '世界书(可选)',
|
|
352
|
+
'newSession.noWorldbook': '不使用世界书',
|
|
353
|
+
'newSession.addCharTip': '添加角色',
|
|
354
|
+
|
|
355
|
+
// ---- 工具确认弹窗 ----
|
|
356
|
+
'confirm.modify': '模型请求执行一个修改操作,请确认是否允许。',
|
|
357
|
+
'confirm.modifyTitle': '修改操作「{name}」需要确认',
|
|
358
|
+
'confirm.title': '工具执行确认',
|
|
359
|
+
'confirm.tool': '工具:',
|
|
360
|
+
'confirm.reject': '拒绝',
|
|
361
|
+
'confirm.allow': '允许',
|
|
362
|
+
'confirm.limitTitle': 'Agent 执行限制',
|
|
363
|
+
'confirm.limitMessage': '{reason}\n\n是否重置本轮计数并继续执行?',
|
|
364
|
+
'confirm.stop': '停止执行',
|
|
365
|
+
'confirm.continue': '继续执行',
|
|
366
|
+
'confirm.depthLimit': '已达到连续工具调用深度上限({limit})。',
|
|
367
|
+
'confirm.callLimit': '已执行 {limit} 次工具调用,达到本轮总数上限。',
|
|
368
|
+
'confirm.identicalLimit': '工具 {name} 使用相同参数连续请求 {limit} 次。',
|
|
369
|
+
'confirm.failureLimit': '工具调用已连续失败 {limit} 次。',
|
|
370
|
+
'confirm.limitCanceled': 'CANCELLED: 用户在达到 Agent 执行限制后选择停止',
|
|
371
|
+
|
|
372
|
+
// ---- 提示 ----
|
|
373
|
+
'toast.sessionDeleted': '会话已删除',
|
|
374
|
+
'toast.sessionUpdated': '会话设置已更新',
|
|
375
|
+
'toast.sessionReset': '会话已重置',
|
|
376
|
+
'toast.sessionPinned': '已置顶对话',
|
|
377
|
+
'toast.sessionUnpinned': '已取消置顶',
|
|
378
|
+
'toast.exportCanceled': '已取消导出',
|
|
379
|
+
'toast.gameplayExported': '游戏已导出',
|
|
380
|
+
'toast.gameplayImported': '已导入游戏「{title}」:新角色 {npc} 个、世界书 {wb}、技能 {tools} 个、对话 {msgs} 条',
|
|
381
|
+
'toast.exportFailed': '导出失败: {msg}',
|
|
382
|
+
'toast.busy': '正在生成中,请稍候',
|
|
383
|
+
'toast.passOnlyGroup': '/pass 仅在群聊中有效',
|
|
384
|
+
'toast.mentionRequired': '仅 @ 模式下,请先 @ 一位角色再发送消息',
|
|
385
|
+
'toast.noEndpoint': '请在「模型服务 Provider」中添加并启用一个端点,并选择模型',
|
|
386
|
+
'toast.genFailed': '生成失败: {msg}',
|
|
387
|
+
'toast.contextCompressionFailed': '上下文压缩失败,将继续使用原始历史: {msg}',
|
|
388
|
+
'toast.rawExported': '已导出原始日志(自动脱敏)',
|
|
389
|
+
'toast.pickOneNpc': '请至少选择一位角色',
|
|
390
|
+
'toast.dupNpc': '该角色已在会话中',
|
|
391
|
+
'toast.importedChar': '已解析角色卡「{name}」({version}),请确认后保存',
|
|
392
|
+
'toast.noEmbeddedWb': '该角色卡「{name}」未包含内嵌世界书',
|
|
393
|
+
'toast.importFailed': '导入失败: {msg}',
|
|
394
|
+
'toast.charDeleted': '已删除角色「{name}」',
|
|
395
|
+
'toast.nameRequired': '名称不能为空',
|
|
396
|
+
'toast.charCreated': '已创建角色「{name}」',
|
|
397
|
+
'toast.charUpdated': '已更新角色「{name}」',
|
|
398
|
+
'toast.wbDraftImported': '已解析角色卡内的世界书「{name}」,请确认后保存',
|
|
399
|
+
'toast.wbNameContentRequired': '世界书名与内容不能为空',
|
|
400
|
+
'toast.wbUpdated': '已更新世界书',
|
|
401
|
+
'toast.wbCreated': '已创建世界书',
|
|
402
|
+
'toast.wbDeleted': '已删除世界书',
|
|
403
|
+
'toast.skillDeleted': '已删除技能',
|
|
404
|
+
'toast.providerDisabled': '已停用「{name}」',
|
|
405
|
+
'toast.providerEnabled': '已启用「{name}」',
|
|
406
|
+
'toast.providerOk': '✅ 连通正常,拉取到 {n} 个模型{via}',
|
|
407
|
+
'toast.providerViaProxy': '(经本地代理转发)',
|
|
408
|
+
'toast.providerFail': '❌ 连接失败: {msg}{hint}',
|
|
409
|
+
'toast.providerFailCors': '。疑似 CORS 跨域被拦截(目标服务未开放 CORS 头,已自动尝试内置代理失败)',
|
|
410
|
+
'toast.providerDeleted': '已删除 Provider',
|
|
411
|
+
'toast.providerUpdated': '已更新 Provider',
|
|
412
|
+
'toast.providerAdded': '已添加 Provider',
|
|
413
|
+
'toast.providerDuplicateModel': '模型「{model}」存在同名模型,请设置别名后再保存。',
|
|
414
|
+
'toast.statsReset': '统计已重置',
|
|
415
|
+
'toast.canceled': 'CANCELLED: 用户取消了 {name}',
|
|
416
|
+
|
|
417
|
+
// ---- 工具执行输出(用户可见部分) ----
|
|
418
|
+
'tool.timeout': '请求超时',
|
|
419
|
+
'tool.diceRoll': '掷骰 {expr}: [{rolls}] = {total}{crit}',
|
|
420
|
+
'tool.diceCrit20': ' 大成功!',
|
|
421
|
+
'tool.diceCrit1': ' 大失败!',
|
|
422
|
+
'tool.gateShellNonAllowlistedTitle': '确认执行非白名单命令',
|
|
423
|
+
'tool.gateShellNonAllowlistedMsg': '不在命令白名单中,是否允许执行?',
|
|
424
|
+
'tool.shellDenied': 'CANCELLED: 用户取消了 {name}',
|
|
425
|
+
'tool.truncated': '\n…(已截断)',
|
|
426
|
+
'tool.noResult': '(无结果)',
|
|
427
|
+
'tool.fileDisplaySuccess': 'OK: 已在弹窗中展示 {path}(kind={kind})',
|
|
428
|
+
'tool.fileWriteSuccess': 'OK: 已写入 {path}({count} 个字符)',
|
|
429
|
+
'tool.fileEditSuccess': 'OK: 已编辑 {path},替换 {count} 处',
|
|
430
|
+
'tool.skillCreated': 'OK: 已创建技能 {name}。注意:新技能默认未对任何角色启用,可用 update_character 的 enable_skills 启用。',
|
|
431
|
+
'tool.skillUpdated': 'OK: 已更新技能 {name}',
|
|
432
|
+
'tool.skillDeleted': 'OK: 已删除技能 {name}',
|
|
433
|
+
'tool.characterCreated': 'OK: 已创建角色 {name}',
|
|
434
|
+
'tool.characterUpdated': 'OK: 已更新角色 {name}',
|
|
435
|
+
'tool.characterDeleted': 'OK: 已删除角色 {name}',
|
|
436
|
+
'tool.lorebookCreated': 'OK: 已创建世界书 {name}',
|
|
437
|
+
'tool.lorebookUpdated': 'OK: 已更新世界书 {name}',
|
|
438
|
+
'tool.lorebookDeleted': 'OK: 已删除世界书 {name}',
|
|
439
|
+
|
|
440
|
+
// ---- 文件展示弹窗(file_display) ----
|
|
441
|
+
'display.view': '查看',
|
|
442
|
+
'display.loading': '正在加载文件…',
|
|
443
|
+
'display.fileError': '无法读取文件:{path}',
|
|
444
|
+
'display.imageLoadError': '图片加载失败',
|
|
445
|
+
'display.zoomIn': '放大',
|
|
446
|
+
'display.zoomOut': '缩小',
|
|
447
|
+
'display.resetView': '重置视图(双击图片也可)',
|
|
448
|
+
'display.htmlZoomIn': '放大 HTML 内容',
|
|
449
|
+
'display.htmlZoomOut': '缩小 HTML 内容',
|
|
450
|
+
'display.resetHtmlZoom': '重置 HTML 缩放',
|
|
451
|
+
'display.enableWrap': '开启自动换行',
|
|
452
|
+
'display.disableWrap': '关闭自动换行',
|
|
453
|
+
'display.pictureInPicture': '画中画',
|
|
454
|
+
'display.exitPictureInPicture': '退出画中画',
|
|
455
|
+
'display.resizePictureInPicture': '调整画中画大小',
|
|
456
|
+
'display.htmlSandboxNote': 'HTML 已在沙箱中渲染(脚本可运行,但与外部隔离)',
|
|
457
|
+
|
|
458
|
+
// ---- 系统提示词 ----
|
|
459
|
+
'prompt.worldBook': '=== 世界书 ===',
|
|
460
|
+
'prompt.userInfo': '=== USER INFO (用户扮演的角色) ===',
|
|
461
|
+
'prompt.roleTag': '角色名',
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
// ---------------- 繁体中文 ----------------
|
|
465
|
+
'zh-TW': {
|
|
466
|
+
'common.cancel': '取消',
|
|
467
|
+
'common.delete': '刪除',
|
|
468
|
+
'common.save': '儲存',
|
|
469
|
+
'common.ok': '確定',
|
|
470
|
+
'common.clear': '清除',
|
|
471
|
+
'common.close': '關閉',
|
|
472
|
+
'common.user': '用戶',
|
|
473
|
+
'common.you': '你',
|
|
474
|
+
'common.me': '我',
|
|
475
|
+
'common.builtin': '內建',
|
|
476
|
+
'common.edit': '編輯',
|
|
477
|
+
'common.confirmDelete': '確定刪除「{name}」?此操作無法復原。',
|
|
478
|
+
'common.removeAttachment': '移除附件',
|
|
479
|
+
'common.attachment': '附件',
|
|
480
|
+
|
|
481
|
+
'nav.chat': '對話',
|
|
482
|
+
'nav.characters': '角色工坊',
|
|
483
|
+
'nav.achievements': '成就',
|
|
484
|
+
'nav.settings': '設定',
|
|
485
|
+
'nav.brandSub': '酒館營業時間 0:00~23:59',
|
|
486
|
+
'nav.sessions': '會話',
|
|
487
|
+
'nav.sessionActive': '正在生成或有新回覆',
|
|
488
|
+
'nav.newChat': '新增',
|
|
489
|
+
'nav.newChatTitle': '新建對話',
|
|
490
|
+
'nav.importGameplay': '匯入',
|
|
491
|
+
'nav.importGameplayTitle': '匯入遊戲(JSON 檔案,重建角色/世界書/技能/對話)',
|
|
492
|
+
'nav.exportGameplay': '匯出遊戲',
|
|
493
|
+
'nav.searchPh': '搜尋會話(標題或內容)…',
|
|
494
|
+
'nav.empty': '還沒有會話,先開一局?',
|
|
495
|
+
'nav.noMatch': '沒有符合的會話',
|
|
496
|
+
'nav.newConversation': '開始新對話…',
|
|
497
|
+
'nav.deleteSession': '刪除會話',
|
|
498
|
+
'nav.editSession': '編輯會話',
|
|
499
|
+
'nav.pinSession': '置頂對話',
|
|
500
|
+
'nav.unpinSession': '取消置頂',
|
|
501
|
+
'nav.newStandardTitle': '新對話',
|
|
502
|
+
'nav.newNpcTitle': '新角色對話',
|
|
503
|
+
'nav.newGroupTitle': '新群聊',
|
|
504
|
+
'nav.loading': '正在支起酒館…',
|
|
505
|
+
'nav.deleteSessionConfirm': '確定刪除會話「{title}」?此操作無法復原,會同時刪除其中的全部訊息,並清理會話的專屬工作目錄。',
|
|
506
|
+
'nav.resize': '拖動調整寬度',
|
|
507
|
+
'nav.home': '回到首頁',
|
|
508
|
+
|
|
509
|
+
'header.modeStandard': '標準對話',
|
|
510
|
+
'header.modeNpc': '角色對話',
|
|
511
|
+
'header.modeGroup': '群聊',
|
|
512
|
+
'header.editSessionTip': '編輯對話設定',
|
|
513
|
+
'header.resetSessionTip': '重置對話',
|
|
514
|
+
'header.resetSessionTitle': '重置對話',
|
|
515
|
+
'header.resetSessionConfirm': '確定重置對話「{title}」?將清空訊息並按最新設定重新開始。',
|
|
516
|
+
'header.resetSessionAction': '重置對話',
|
|
517
|
+
'header.exportGameplayTip': '匯出遊戲(角色/世界書/技能/對話設定,可匯入重建)',
|
|
518
|
+
'header.deleteTip': '刪除對話',
|
|
519
|
+
'header.deleteTitle': '刪除對話',
|
|
520
|
+
'header.deleteConfirm': '確定刪除對話「{title}」?此操作無法復原。',
|
|
521
|
+
'header.workspaceHint': '本對話的專屬工作目錄。檔案工具限制在該目錄內;本地指令以此為起始目錄,但不具備 OS 隔離。點擊可瀏覽目錄檔案(唯讀)。',
|
|
522
|
+
'header.workspaceRootHint': '酒館老闆的單人對話固定使用公共工作目錄。檔案工具限制在該目錄內;本地指令不具備 OS 隔離。點擊可瀏覽檔案(唯讀)。',
|
|
523
|
+
'header.fileManagerTitle': '工作區檔案',
|
|
524
|
+
'header.fileManagerReadonly': '唯讀瀏覽 · 點擊檔案可預覽',
|
|
525
|
+
'header.fileManagerHint': '點擊檔案可預覽,點擊刪除按鈕可刪除檔案',
|
|
526
|
+
'header.fileManagerDeleteTitle': '刪除工作區檔案',
|
|
527
|
+
'header.fileManagerEmpty': '此工作區還沒有檔案',
|
|
528
|
+
'header.fileManagerError': '無法讀取工作區檔案',
|
|
529
|
+
'header.workspaceRoot': 'public',
|
|
530
|
+
|
|
531
|
+
'gameplay.exportTitle': '匯出遊戲',
|
|
532
|
+
'gameplay.importTitle': '匯入遊戲',
|
|
533
|
+
'gameplay.includeHistory': '包含對話歷史',
|
|
534
|
+
'gameplay.includeHistoryHint': '勾選後,匯出的 JSON 將會包含全部訊息記錄',
|
|
535
|
+
'gameplay.exportSummary': '匯出內容將包含:全部參與角色(人設 / 開場白 / 頭像 / 啟用的技能)、世界書與所需的生成式技能。對方匯入後可完全重建這一局遊戲。',
|
|
536
|
+
'gameplay.exportAction': '匯出遊戲',
|
|
537
|
+
'gameplay.importAction': '匯入並重建',
|
|
538
|
+
'gameplay.importSummary': '選擇遊戲 JSON 檔案後,將自動建立所需角色、世界書、技能與對話;若檔案包含對話歷史,會一併重建。名稱相同的角色 / 世界書 / 技能會建立副本(不覆蓋已有資料),會話一律新建。',
|
|
539
|
+
'gameplay.chooseFile': '選擇遊戲 JSON 檔案…',
|
|
540
|
+
'gameplay.invalidFile': '所選檔案不是有效的遊戲 JSON',
|
|
541
|
+
'gameplay.busy': '處理中…',
|
|
542
|
+
|
|
543
|
+
'chat.emptyStandardTitle': '開始一段新的對話',
|
|
544
|
+
'chat.emptyStandardSub': '輸入訊息開始聊天',
|
|
545
|
+
'chat.emptyNpcTitle': '與 {name} 對話',
|
|
546
|
+
'chat.emptyGroupTitle': '群聊',
|
|
547
|
+
'chat.emptyGroupSub': '多位角色輪番發言',
|
|
548
|
+
'chat.speakerChar': '角色',
|
|
549
|
+
'chat.speakerAssistant': '助手',
|
|
550
|
+
'chat.thinking': '思考',
|
|
551
|
+
'chat.editMsg': '編輯訊息',
|
|
552
|
+
'chat.addAttachment': '新增附件',
|
|
553
|
+
'chat.saveHint': '⌘/Ctrl+Enter 儲存 · Esc 取消',
|
|
554
|
+
'chat.saveRegenerate': '儲存並重新產生',
|
|
555
|
+
'chat.saveOnly': '僅儲存',
|
|
556
|
+
'chat.toolCall': '呼叫工具: {name}',
|
|
557
|
+
'chat.contextCompressed': '上下文已壓縮',
|
|
558
|
+
'chat.chooseModel': '選模型',
|
|
559
|
+
'chat.selectModel': '選擇模型',
|
|
560
|
+
'chat.attachTip': '新增圖片/影片(貼上或點擊)',
|
|
561
|
+
'chat.phGroup': '輸入訊息… 支援 @角色名 指定發言,/new 新話題,/pass 跳過',
|
|
562
|
+
'chat.phSingle': '輸入訊息… /new 開始新話題',
|
|
563
|
+
'chat.mentionHead': '@ 點名角色',
|
|
564
|
+
'chat.stopTip': '停止產生',
|
|
565
|
+
'chat.sendTip': '傳送',
|
|
566
|
+
'chat.hintSend': 'Enter 傳送 · Shift+Enter 換行',
|
|
567
|
+
'chat.hintMagic': '魔法指令: /new · /pass',
|
|
568
|
+
'chat.hintMention': ' · @角色名 點名',
|
|
569
|
+
'chat.cmdNew': '開始新話題(截斷上下文)',
|
|
570
|
+
'chat.cmdPass': '跳過本輪發言(群聊中,不修改對話歷史)',
|
|
571
|
+
'chat.modelEmpty': '暫無可用模型清單。請先在「設定 → 模型服務 Provider」新增並測試連線。',
|
|
572
|
+
'chat.noActions': '此訊息沒有可用的操作',
|
|
573
|
+
'chat.regenerate': '重新產生回覆',
|
|
574
|
+
'chat.viewRaw': '檢視原始日誌',
|
|
575
|
+
'chat.viewMemory': '檢視記憶',
|
|
576
|
+
'chat.fork': '建立分支 / Fork',
|
|
577
|
+
'chat.rawLogTitle': '原始日誌 · 訊息 #{id}',
|
|
578
|
+
'chat.requestBody': '請求體',
|
|
579
|
+
'chat.fullResponse': '完整回應',
|
|
580
|
+
'chat.exportTip': '下載為 txt 檔案',
|
|
581
|
+
'chat.noRequestBody': '(無請求體)',
|
|
582
|
+
'chat.fullResponseJson': '完整回覆 JSON',
|
|
583
|
+
'chat.fromSse': '從串流 SSE 分片拼接',
|
|
584
|
+
'chat.noRawContent': '此訊息沒有可拼接的串流內容',
|
|
585
|
+
'chat.pasteImage': '貼上圖片.{ext}',
|
|
586
|
+
'chat.loop': '第 {n} 輪',
|
|
587
|
+
'chat.current': '目前',
|
|
588
|
+
'chat.queueTitle': '發言佇列',
|
|
589
|
+
'chat.queueLoops': '共 {n} 輪',
|
|
590
|
+
'chat.queueRandom': '隨機順序(每輪洗牌)',
|
|
591
|
+
'chat.queueFixed': '固定座位順序',
|
|
592
|
+
'chat.queueLocateSelf': '點擊定位到你的發言',
|
|
593
|
+
'chat.queueLocateNpc': '點擊定位到 {name} 的發言',
|
|
594
|
+
'chat.sortRandom': '隨機順序',
|
|
595
|
+
'chat.collpMore': '(點擊展開完整內容)',
|
|
596
|
+
|
|
597
|
+
'dash.welcome': '歡迎回到酒館 🫖',
|
|
598
|
+
'dash.hero': 'Tavern Harness — 更自由、更好玩、更易上手的酒館!\n支援任意 OpenAI 相容 API,支援本地模型,全部資料都儲存在你的本地;\n支援受控本地技能,接入更多系統權限,解鎖更多文字對話無法實現的遊戲;\n酒館已經完成智慧體原生裝修,有什麼想要的,',
|
|
599
|
+
'dash.heroAccent': '為什麼不問問酒館老闆呢~',
|
|
600
|
+
'dash.newChat': '新建對話',
|
|
601
|
+
'dash.goCharacters': '前往角色工坊',
|
|
602
|
+
'dash.openWiki': '開啟使用 Wiki',
|
|
603
|
+
'dash.continue': '與角色繼續對話',
|
|
604
|
+
'dash.noCharConv': '還沒有角色對話,從下面的角色卡片開始',
|
|
605
|
+
'dash.groupSession': '群聊',
|
|
606
|
+
'dash.newSession': '新對話',
|
|
607
|
+
'dash.timeJustNow': '剛剛',
|
|
608
|
+
'dash.timeMinAgo': '{n} 分鐘前',
|
|
609
|
+
'dash.timeHourAgo': '{n} 小時前',
|
|
610
|
+
'dash.timeDayAgo': '{n} 天前',
|
|
611
|
+
'dash.regulars': '酒館常客',
|
|
612
|
+
'dash.startChat': '開始對話',
|
|
613
|
+
|
|
614
|
+
'settings.title': '設定',
|
|
615
|
+
'settings.sub': '模型 API 設定、超參數與主題',
|
|
616
|
+
'settings.genParams': '🎛 產生參數',
|
|
617
|
+
'settings.tempDesc': '創造性與隨機性',
|
|
618
|
+
'settings.topPDesc': '核採樣',
|
|
619
|
+
'settings.topKDesc': '候選數量',
|
|
620
|
+
'settings.maxTokensDesc': '0 = 不限制',
|
|
621
|
+
'settings.freqDesc': '重複懲罰',
|
|
622
|
+
'settings.presDesc': '話題新穎度',
|
|
623
|
+
'settings.repDesc': '重複懲罰',
|
|
624
|
+
'settings.effortDesc': '思考強度',
|
|
625
|
+
'settings.contextCompression': 'Context Window Size',
|
|
626
|
+
'settings.contextCompressionDesc': '上下文壓縮觸發視窗閾值',
|
|
627
|
+
'settings.off': '關閉',
|
|
628
|
+
'settings.theme': '🎨 主題',
|
|
629
|
+
'settings.appearance': '外觀模式',
|
|
630
|
+
'settings.system': '跟隨系統',
|
|
631
|
+
'settings.light': '淺色',
|
|
632
|
+
'settings.dark': '深色',
|
|
633
|
+
'settings.themeTip': '預設賽博琥珀主題色,外觀模式可跟隨系統',
|
|
634
|
+
'settings.language': '🌐 介面語言/Language',
|
|
635
|
+
'settings.languageLabel': '介面語言/Language',
|
|
636
|
+
'settings.followBrowser': '跟隨系統語言',
|
|
637
|
+
'settings.resetTitle': '⚠️ 重設目前酒館',
|
|
638
|
+
'settings.resetDesc': '清除目前酒館的所有本機資料並還原為初始狀態,不影響其他連接埠或瀏覽器中開啟的酒館。',
|
|
639
|
+
'settings.resetBtn': '重設目前酒館',
|
|
640
|
+
'settings.resetConfirmTitle': '重設目前酒館',
|
|
641
|
+
'settings.resetConfirm': '確定要重設目前酒館嗎?所有對話、角色、世界書、技能、模型服務與統計資料都會被刪除,且無法復原。其他連接埠或瀏覽器中開啟的酒館不受影響。',
|
|
642
|
+
|
|
643
|
+
// ---- 內建角色「酒館老闆」(首次啟動種子資料,隨語言切換) ----
|
|
644
|
+
'builtinNpc.name': '酒館老闆',
|
|
645
|
+
'builtinNpc.prompt': '神秘的酒館老闆,可以回應客人的任何需求',
|
|
646
|
+
'builtinNpc.greeting': '你來啦!快坐下~',
|
|
647
|
+
'builtinWorldbook.name': '酒館經營手冊(精華版)',
|
|
648
|
+
'settings.providerTitle': '🔌 模型服務 Provider(多端點)',
|
|
649
|
+
'settings.noProvider': '還沒有設定 Provider,點擊下方按鈕新增',
|
|
650
|
+
'settings.enable': '啟用',
|
|
651
|
+
'settings.disable': '停用',
|
|
652
|
+
'settings.testing': '測試中',
|
|
653
|
+
'settings.test': '測試',
|
|
654
|
+
'settings.models': '模型:',
|
|
655
|
+
'settings.emptyModels': '空',
|
|
656
|
+
'settings.modelAlias': '顯示別名',
|
|
657
|
+
'settings.modelAliasPh': '留空則顯示原模型名稱',
|
|
658
|
+
'settings.addProvider': '新增 Provider',
|
|
659
|
+
'settings.editProvider': '編輯 Provider',
|
|
660
|
+
'settings.deleteProvider': '刪除 Provider',
|
|
661
|
+
'settings.providerName': '名稱',
|
|
662
|
+
'settings.providerNamePh': '如:DeepSeek 官方',
|
|
663
|
+
'settings.baseUrl': 'Base URL',
|
|
664
|
+
'settings.baseUrlPh': 'https://api.deepseek.com/v1/ 或 http://192.168.x.x:8788/',
|
|
665
|
+
'settings.apiKey': 'API Key',
|
|
666
|
+
'settings.apiKeyPh': 'sk-...',
|
|
667
|
+
|
|
668
|
+
'workshop.title': '角色工坊',
|
|
669
|
+
'workshop.sub': '管理 NPC、世界書與技能',
|
|
670
|
+
'workshop.tabCharacters': '👤 角色卡',
|
|
671
|
+
'workshop.tabWorldbooks': '📖 世界書',
|
|
672
|
+
'workshop.tabSkills': '🛠 技能表',
|
|
673
|
+
'workshop.charCount': '角色卡({n})',
|
|
674
|
+
'workshop.pngImport': 'PNG 匯入',
|
|
675
|
+
'workshop.pngImportTip': '匯入 SillyTavern PNG 角色卡(chara V2 / ccv3)',
|
|
676
|
+
'workshop.pngImportWbTip': '匯入 SillyTavern PNG 角色卡(chara V2 / ccv3),自動解析內嵌世界書',
|
|
677
|
+
'workshop.newChar': '新建角色',
|
|
678
|
+
'workshop.noGreeting': '(無開場白)',
|
|
679
|
+
'workshop.protectedTip': '內建角色受保護',
|
|
680
|
+
'workshop.uploadAvatar': '上傳頭像',
|
|
681
|
+
'workshop.name': '名稱',
|
|
682
|
+
'workshop.charNamePh': '角色名',
|
|
683
|
+
'workshop.promptLabel': '人設 Prompt(注入 system prompt)',
|
|
684
|
+
'workshop.promptPh': '例如:神祕的酒館老闆,可以回應客人的任何需求',
|
|
685
|
+
'workshop.greetingLabel': '開場白 Greeting',
|
|
686
|
+
'workshop.greetingRandomHint': '(若建立了多個開場白,每次新對話將從中隨機選擇一個)',
|
|
687
|
+
'workshop.greetingPh': '你來啦!快坐下~',
|
|
688
|
+
'workshop.addAltGreeting': '新增開場白',
|
|
689
|
+
'workshop.resetChar': '恢復預設',
|
|
690
|
+
'workshop.resetCharTip': '恢復酒館老闆的預設人設、開場白和裝備技能',
|
|
691
|
+
'workshop.enableSkills': '啟用技能({a}/{b})',
|
|
692
|
+
'workshop.noSkills': '暫無技能',
|
|
693
|
+
'workshop.newCharTitle': '新建角色',
|
|
694
|
+
'workshop.editCharTitle': '編輯角色 · {name}',
|
|
695
|
+
'workshop.deleteCharTitle': '刪除角色',
|
|
696
|
+
'workshop.wbCount': '世界書({n})',
|
|
697
|
+
'workshop.newWb': '新建世界書',
|
|
698
|
+
'workshop.wbEmpty': '暫無世界書,為角色建立世界觀吧',
|
|
699
|
+
'workshop.wbChars': '{n} 字',
|
|
700
|
+
'workshop.browseEdit': '瀏覽/編輯',
|
|
701
|
+
'workshop.editWbTitle': '編輯世界書',
|
|
702
|
+
'workshop.newWbTitle': '新建世界書',
|
|
703
|
+
'workshop.wbName': '世界書名',
|
|
704
|
+
'workshop.wbNamePh': '如:北境大陸',
|
|
705
|
+
'workshop.wbContentLabel': '內容(附加在角色人設之後)',
|
|
706
|
+
'workshop.wbContentPh': '世界觀設定文字…',
|
|
707
|
+
'workshop.wbMarkdownRender': 'Markdown 渲染',
|
|
708
|
+
'workshop.resetWb': '恢復預設',
|
|
709
|
+
'workshop.resetWbTip': '恢復內建世界書的預設內容',
|
|
710
|
+
'workshop.deleteWbTitle': '刪除世界書',
|
|
711
|
+
'workshop.skillGroupBuiltin': '內建技能({n})',
|
|
712
|
+
'workshop.skillGroupCustom': '自定義技能({n})',
|
|
713
|
+
'workshop.skillGroupImported': '匯入技能({n})',
|
|
714
|
+
'workshop.implType': '實作類型: {t}',
|
|
715
|
+
'workshop.sortHint': '上下拖動卡片即可調整排序',
|
|
716
|
+
'workshop.deleteSkillTitle': '刪除技能',
|
|
717
|
+
'workshop.skillDetails': '技能詳情',
|
|
718
|
+
'workshop.skillName': '技能名稱',
|
|
719
|
+
'workshop.skillNameInvalid': '技能名稱須以小寫字母開頭,並僅含小寫字母、數字或底線(3–40 個字元)',
|
|
720
|
+
'workshop.skillNameExists': '該技能名稱已存在',
|
|
721
|
+
'workshop.skillDescriptionLong': '描述不能超過 500 個字元',
|
|
722
|
+
'workshop.skillSchemaJson': '參數 Schema 必須是有效的 JSON',
|
|
723
|
+
'workshop.skillSchemaObject': '參數 Schema 必須是 JSON 物件',
|
|
724
|
+
'workshop.viewDetails': '詳情',
|
|
725
|
+
'workshop.skillDescription': '描述',
|
|
726
|
+
'workshop.skillParameters': '參數 Schema',
|
|
727
|
+
'workshop.skillExecution': '宣告式實作',
|
|
728
|
+
'workshop.skillRawJson': '原始 JSON(OpenAI Tool 定義)',
|
|
729
|
+
'workshop.skillNoExecution': '(無宣告式實作,由內建邏輯執行)',
|
|
730
|
+
'workshop.skillNoParams': '(無參數)',
|
|
731
|
+
'workshop.skillBadJson': '(JSON 解析失敗)',
|
|
732
|
+
|
|
733
|
+
'stats.title': '🏆 成就',
|
|
734
|
+
'stats.sub': '你的成就獎盃會陳列在這裡',
|
|
735
|
+
'stats.resetBtn': '重置統計',
|
|
736
|
+
'stats.resetConfirm': '確定重置生涯統計?(不會清除已解鎖的成就獎盃)',
|
|
737
|
+
'stats.shelfTitle': '🎖️ 獎盃陳列櫃',
|
|
738
|
+
'stats.unlocked': '已解鎖',
|
|
739
|
+
'stats.trophies': '枚獎盃',
|
|
740
|
+
'stats.locked': '未解鎖',
|
|
741
|
+
'stats.unlockedStatus': '✓ 已解鎖',
|
|
742
|
+
'stats.hidden': '???',
|
|
743
|
+
'stats.unlockedAt': '解鎖',
|
|
744
|
+
'stats.totalTokens': '總 Tokens(輸入 + 輸出)',
|
|
745
|
+
'stats.inputTokens': '輸入 Tokens',
|
|
746
|
+
'stats.outputTokens': '輸出 Tokens',
|
|
747
|
+
'stats.rounds': '對話輪數',
|
|
748
|
+
'stats.sessions': '累計會話',
|
|
749
|
+
'stats.avgRounds': '平均輪數 / 會話',
|
|
750
|
+
'stats.mostActive': '🏆 最活躍角色',
|
|
751
|
+
'stats.roundsCount': '{n} 輪對話',
|
|
752
|
+
'stats.noData': '暫無資料,去和角色聊聊天吧',
|
|
753
|
+
'stats.ranking': '🎭 角色輪數排行',
|
|
754
|
+
'stats.colChar': '角色',
|
|
755
|
+
'stats.colRounds': '輪數',
|
|
756
|
+
|
|
757
|
+
'ach.traveler.name': '旅人',
|
|
758
|
+
'ach.traveler.desc': '累計使用 1,000 Token',
|
|
759
|
+
'ach.oldFriend.name': '老友',
|
|
760
|
+
'ach.oldFriend.desc': '累計使用 10,000 Token',
|
|
761
|
+
'ach.bard.name': '吟遊詩人',
|
|
762
|
+
'ach.bard.desc': '累計使用 100,000 Token',
|
|
763
|
+
'ach.nightOwl.name': '午夜不歸人',
|
|
764
|
+
'ach.nightOwl.desc': '累計使用 1,000,000 Token',
|
|
765
|
+
'ach.legend.name': '酒館傳奇',
|
|
766
|
+
'ach.legend.desc': '累計使用 100,000,000 Token',
|
|
767
|
+
|
|
768
|
+
'achModal.title': '🎉 成就解鎖!',
|
|
769
|
+
'achModal.total': '生涯已累計 {n} Tokens',
|
|
770
|
+
'achModal.accept': '收下獎盃',
|
|
771
|
+
|
|
772
|
+
'newSession.title': '建立對話',
|
|
773
|
+
'newSession.name': '對話名稱',
|
|
774
|
+
'newSession.unnamed': '未命名',
|
|
775
|
+
'newSession.participants': '參與角色',
|
|
776
|
+
'newSession.noNpcWarn': '⚠️ 請先在「角色工坊」建立角色卡!',
|
|
777
|
+
'newSession.notSelected': '未選擇',
|
|
778
|
+
'newSession.npcChat': 'NPC 對話',
|
|
779
|
+
'newSession.groupChat': '群聊({n} 人)',
|
|
780
|
+
'newSession.orderHint': '拖動設定發言順序,或開啟隨機順序。',
|
|
781
|
+
'newSession.mentionOnly': '僅 @ 模式',
|
|
782
|
+
'newSession.mentionOnlyHint': '使用者必須 @ 角色名;角色未 @ 時自動將對話權交還給使用者。',
|
|
783
|
+
'newSession.greeting': '啟用開場白',
|
|
784
|
+
'newSession.greetingHint': '單人對話啟用 NPC 開場白;群聊僅當首位發言者是角色時啟用其開場白。',
|
|
785
|
+
'newSession.persona': '用戶人設(可選)',
|
|
786
|
+
'newSession.noPersona': '不使用用戶人設',
|
|
787
|
+
'newSession.worldbook': '世界書(可選)',
|
|
788
|
+
'newSession.noWorldbook': '不使用世界書',
|
|
789
|
+
'newSession.addCharTip': '新增角色',
|
|
790
|
+
|
|
791
|
+
'confirm.modify': '模型請求執行一個修改操作,請確認是否允許。',
|
|
792
|
+
'confirm.modifyTitle': '修改操作「{name}」需要確認',
|
|
793
|
+
'confirm.title': '工具執行確認',
|
|
794
|
+
'confirm.tool': '工具:',
|
|
795
|
+
'confirm.reject': '拒絕',
|
|
796
|
+
'confirm.allow': '允許',
|
|
797
|
+
'confirm.limitTitle': 'Agent 執行限制',
|
|
798
|
+
'confirm.limitMessage': '{reason}\n\n是否重設本輪計數並繼續執行?',
|
|
799
|
+
'confirm.stop': '停止執行',
|
|
800
|
+
'confirm.continue': '繼續執行',
|
|
801
|
+
'confirm.depthLimit': '已達到連續工具呼叫深度上限({limit})。',
|
|
802
|
+
'confirm.callLimit': '已執行 {limit} 次工具呼叫,達到本輪總數上限。',
|
|
803
|
+
'confirm.identicalLimit': '工具 {name} 使用相同參數連續請求 {limit} 次。',
|
|
804
|
+
'confirm.failureLimit': '工具呼叫已連續失敗 {limit} 次。',
|
|
805
|
+
'confirm.limitCanceled': 'CANCELLED: 使用者在達到 Agent 執行限制後選擇停止',
|
|
806
|
+
|
|
807
|
+
'toast.gameplayExported': '遊戲已匯出',
|
|
808
|
+
'toast.gameplayImported': '已匯入遊戲「{title}」:新角色 {npc} 個、世界書 {wb}、技能 {tools} 個、對話 {msgs} 條',
|
|
809
|
+
'toast.sessionDeleted': '會話已刪除',
|
|
810
|
+
'toast.sessionUpdated': '會話設定已更新',
|
|
811
|
+
'toast.sessionReset': '會話已重置',
|
|
812
|
+
'toast.sessionPinned': '已置頂對話',
|
|
813
|
+
'toast.sessionUnpinned': '已取消置頂',
|
|
814
|
+
'toast.exportCanceled': '已取消匯出',
|
|
815
|
+
'toast.exportFailed': '匯出失敗: {msg}',
|
|
816
|
+
'toast.busy': '正在產生中,請稍候',
|
|
817
|
+
'toast.passOnlyGroup': '/pass 僅在群聊中有效',
|
|
818
|
+
'toast.mentionRequired': '僅 @ 模式下,請先 @ 一位角色再傳送訊息',
|
|
819
|
+
'toast.noEndpoint': '請在「模型服務 Provider」中新增並啟用一個端點,並選擇模型',
|
|
820
|
+
'toast.genFailed': '產生失敗: {msg}',
|
|
821
|
+
'toast.contextCompressionFailed': '上下文壓縮失敗,將繼續使用原始歷史: {msg}',
|
|
822
|
+
'toast.rawExported': '已匯出原始日誌(自動脫敏)',
|
|
823
|
+
'toast.pickOneNpc': '請至少選擇一位角色',
|
|
824
|
+
'toast.dupNpc': '該角色已在會話中',
|
|
825
|
+
'toast.importedChar': '已解析角色卡「{name}」({version}),請確認後儲存',
|
|
826
|
+
'toast.noEmbeddedWb': '該角色卡「{name}」未包含內嵌世界書',
|
|
827
|
+
'toast.importFailed': '匯入失敗: {msg}',
|
|
828
|
+
'toast.charDeleted': '已刪除角色「{name}」',
|
|
829
|
+
'toast.nameRequired': '名稱不能為空',
|
|
830
|
+
'toast.charCreated': '已建立角色「{name}」',
|
|
831
|
+
'toast.charUpdated': '已更新角色「{name}」',
|
|
832
|
+
'toast.wbDraftImported': '已解析角色卡內的世界書「{name}」,請確認後儲存',
|
|
833
|
+
'toast.wbNameContentRequired': '世界書名與內容不能為空',
|
|
834
|
+
'toast.wbUpdated': '已更新世界書',
|
|
835
|
+
'toast.wbCreated': '已建立世界書',
|
|
836
|
+
'toast.wbDeleted': '已刪除世界書',
|
|
837
|
+
'toast.skillDeleted': '已刪除技能',
|
|
838
|
+
'toast.providerDisabled': '已停用「{name}」',
|
|
839
|
+
'toast.providerEnabled': '已啟用「{name}」',
|
|
840
|
+
'toast.providerOk': '✅ 連通正常,拉取到 {n} 個模型{via}',
|
|
841
|
+
'toast.providerViaProxy': '(經本地代理轉發)',
|
|
842
|
+
'toast.providerFail': '❌ 連線失敗: {msg}{hint}',
|
|
843
|
+
'toast.providerFailCors': '。疑似 CORS 跨域被封鎖(目標服務未開放 CORS 頭,已自動嘗試內建代理失敗)',
|
|
844
|
+
'toast.providerDeleted': '已刪除 Provider',
|
|
845
|
+
'toast.providerUpdated': '已更新 Provider',
|
|
846
|
+
'toast.providerAdded': '已新增 Provider',
|
|
847
|
+
'toast.providerDuplicateModel': '模型「{model}」存在同名模型,請設定別名後再儲存。',
|
|
848
|
+
'toast.statsReset': '統計已重置',
|
|
849
|
+
'toast.canceled': 'CANCELLED: 使用者取消了 {name}',
|
|
850
|
+
|
|
851
|
+
'tool.timeout': '請求逾時',
|
|
852
|
+
'tool.diceRoll': '擲骰 {expr}: [{rolls}] = {total}{crit}',
|
|
853
|
+
'tool.diceCrit20': ' 大成功!',
|
|
854
|
+
'tool.diceCrit1': ' 大失敗!',
|
|
855
|
+
'tool.gateShellNonAllowlistedTitle': '確認執行非白名單指令',
|
|
856
|
+
'tool.gateShellNonAllowlistedMsg': '不在指令白名單中,是否允許執行?',
|
|
857
|
+
'tool.shellDenied': 'CANCELLED: 用戶取消了 {name}',
|
|
858
|
+
'tool.truncated': '\n…(已截斷)',
|
|
859
|
+
'tool.noResult': '(無結果)',
|
|
860
|
+
'tool.fileDisplaySuccess': 'OK: 已在彈窗中展示 {path}(kind={kind})',
|
|
861
|
+
'tool.fileWriteSuccess': 'OK: 已寫入 {path}({count} 個字元)',
|
|
862
|
+
'tool.fileEditSuccess': 'OK: 已編輯 {path},替換 {count} 處',
|
|
863
|
+
'tool.skillCreated': 'OK: 已建立技能 {name}。注意:新技能預設未對任何角色啟用,可用 update_character 的 enable_skills 啟用。',
|
|
864
|
+
'tool.skillUpdated': 'OK: 已更新技能 {name}',
|
|
865
|
+
'tool.skillDeleted': 'OK: 已刪除技能 {name}',
|
|
866
|
+
'tool.characterCreated': 'OK: 已建立角色 {name}',
|
|
867
|
+
'tool.characterUpdated': 'OK: 已更新角色 {name}',
|
|
868
|
+
'tool.characterDeleted': 'OK: 已刪除角色 {name}',
|
|
869
|
+
'tool.lorebookCreated': 'OK: 已建立世界書 {name}',
|
|
870
|
+
'tool.lorebookUpdated': 'OK: 已更新世界書 {name}',
|
|
871
|
+
'tool.lorebookDeleted': 'OK: 已刪除世界書 {name}',
|
|
872
|
+
|
|
873
|
+
// ---- 檔案展示彈窗(file_display) ----
|
|
874
|
+
'display.view': '查看',
|
|
875
|
+
'display.loading': '正在載入檔案…',
|
|
876
|
+
'display.fileError': '無法讀取檔案:{path}',
|
|
877
|
+
'display.imageLoadError': '圖片載入失敗',
|
|
878
|
+
'display.zoomIn': '放大',
|
|
879
|
+
'display.zoomOut': '縮小',
|
|
880
|
+
'display.resetView': '重設檢視(雙擊圖片也可)',
|
|
881
|
+
'display.htmlZoomIn': '放大 HTML 內容',
|
|
882
|
+
'display.htmlZoomOut': '縮小 HTML 內容',
|
|
883
|
+
'display.resetHtmlZoom': '重設 HTML 縮放',
|
|
884
|
+
'display.enableWrap': '開啟自動換行',
|
|
885
|
+
'display.disableWrap': '關閉自動換行',
|
|
886
|
+
'display.pictureInPicture': '畫中畫',
|
|
887
|
+
'display.exitPictureInPicture': '退出畫中畫',
|
|
888
|
+
'display.resizePictureInPicture': '調整畫中畫大小',
|
|
889
|
+
'display.htmlSandboxNote': 'HTML 已在沙箱中渲染(腳本可執行,但與外部隔離)',
|
|
890
|
+
|
|
891
|
+
'prompt.worldBook': '=== 世界書 ===',
|
|
892
|
+
'prompt.userInfo': '=== USER INFO (用戶扮演的角色) ===',
|
|
893
|
+
'prompt.roleTag': '角色名',
|
|
894
|
+
},
|
|
895
|
+
|
|
896
|
+
// ---------------- English ----------------
|
|
897
|
+
en: {
|
|
898
|
+
'common.cancel': 'Cancel',
|
|
899
|
+
'common.delete': 'Delete',
|
|
900
|
+
'common.save': 'Save',
|
|
901
|
+
'common.ok': 'OK',
|
|
902
|
+
'common.clear': 'Clear',
|
|
903
|
+
'common.close': 'Close',
|
|
904
|
+
'common.user': 'User',
|
|
905
|
+
'common.you': 'You',
|
|
906
|
+
'common.me': 'Me',
|
|
907
|
+
'common.builtin': 'Built-in',
|
|
908
|
+
'common.edit': 'Edit',
|
|
909
|
+
'common.confirmDelete': 'Delete "{name}"? This cannot be undone.',
|
|
910
|
+
'common.removeAttachment': 'Remove attachment',
|
|
911
|
+
'common.attachment': 'Attachment',
|
|
912
|
+
|
|
913
|
+
'nav.chat': 'Chat',
|
|
914
|
+
'nav.characters': 'Characters',
|
|
915
|
+
'nav.achievements': 'Achievements',
|
|
916
|
+
'nav.settings': 'Settings',
|
|
917
|
+
'nav.brandSub': 'Open daily, 0:00–23:59',
|
|
918
|
+
'nav.sessions': 'Sessions',
|
|
919
|
+
'nav.sessionActive': 'Generating or has a new reply',
|
|
920
|
+
'nav.newChat': 'New',
|
|
921
|
+
'nav.newChatTitle': 'New chat',
|
|
922
|
+
'nav.importGameplay': 'Import',
|
|
923
|
+
'nav.importGameplayTitle': 'Import a gameplay (JSON file — rebuilds characters, Lorebooks, skills and chats)',
|
|
924
|
+
'nav.exportGameplay': 'Export gameplay',
|
|
925
|
+
'nav.searchPh': 'Search sessions (title or content)…',
|
|
926
|
+
'nav.empty': 'No sessions yet — start one?',
|
|
927
|
+
'nav.noMatch': 'No matching sessions',
|
|
928
|
+
'nav.newConversation': 'Start a new conversation…',
|
|
929
|
+
'nav.deleteSession': 'Delete session',
|
|
930
|
+
'nav.editSession': 'Edit conversation',
|
|
931
|
+
'nav.pinSession': 'Pin conversation',
|
|
932
|
+
'nav.unpinSession': 'Unpin',
|
|
933
|
+
'nav.newStandardTitle': 'New conversation',
|
|
934
|
+
'nav.newNpcTitle': 'New character chat',
|
|
935
|
+
'nav.newGroupTitle': 'New group chat',
|
|
936
|
+
'nav.loading': 'Setting up the tavern…',
|
|
937
|
+
'nav.deleteSessionConfirm': 'Delete session "{title}"? This cannot be undone and will delete all its messages and its dedicated workspace folder.',
|
|
938
|
+
'nav.resize': 'Drag to resize',
|
|
939
|
+
'nav.home': 'Back to home',
|
|
940
|
+
|
|
941
|
+
'header.modeStandard': 'Standard chat',
|
|
942
|
+
'header.modeNpc': 'Character chat',
|
|
943
|
+
'header.modeGroup': 'Group chat',
|
|
944
|
+
'header.editSessionTip': 'Edit conversation settings',
|
|
945
|
+
'header.resetSessionTip': 'Reset conversation',
|
|
946
|
+
'header.resetSessionTitle': 'Reset conversation',
|
|
947
|
+
'header.resetSessionConfirm': 'Reset conversation "{title}"? This clears messages and restarts with the latest settings.',
|
|
948
|
+
'header.resetSessionAction': 'Reset conversation',
|
|
949
|
+
'header.exportGameplayTip': 'Export gameplay (characters, Lorebook, skills, conversation settings — importable to rebuild)',
|
|
950
|
+
'header.deleteTip': 'Delete conversation',
|
|
951
|
+
'header.deleteTitle': 'Delete conversation',
|
|
952
|
+
'header.deleteConfirm': 'Delete conversation "{title}"? This cannot be undone.',
|
|
953
|
+
'header.workspaceHint': 'This conversation\'s private workspace. File tools are restricted to it; local commands start here but are not OS-isolated. Click to browse its files (read-only).',
|
|
954
|
+
'header.workspaceRootHint': 'A one-on-one conversation with the Tavern Keeper always uses the public workspace. File tools are restricted to it; local commands are not OS-isolated. Click to browse files (read-only).',
|
|
955
|
+
'header.fileManagerTitle': 'Workspace files',
|
|
956
|
+
'header.fileManagerReadonly': 'Read-only · click a file to preview',
|
|
957
|
+
'header.fileManagerHint': 'Click a file to preview it, or use the delete button to remove it',
|
|
958
|
+
'header.fileManagerDeleteTitle': 'Delete workspace file',
|
|
959
|
+
'header.fileManagerEmpty': 'No files in this workspace yet',
|
|
960
|
+
'header.fileManagerError': 'Could not read workspace files',
|
|
961
|
+
'header.workspaceRoot': 'public',
|
|
962
|
+
|
|
963
|
+
'gameplay.exportTitle': 'Export gameplay',
|
|
964
|
+
'gameplay.importTitle': 'Import gameplay',
|
|
965
|
+
'gameplay.includeHistory': 'Include chat history',
|
|
966
|
+
'gameplay.includeHistoryHint': 'When checked, the exported JSON includes all messages.',
|
|
967
|
+
'gameplay.exportSummary': 'The export includes: every participant character (persona, greeting, avatar, enabled skills), the Lorebook and required generated skills. Another player can import it to fully rebuild this gameplay.',
|
|
968
|
+
'gameplay.exportAction': 'Export gameplay',
|
|
969
|
+
'gameplay.importAction': 'Import & rebuild',
|
|
970
|
+
'gameplay.importSummary': 'Pick a gameplay JSON file — it will create the required characters, Lorebook, skills and conversation; if the file includes chat history, it will be rebuilt too. Characters / Lorebooks / skills with the same name are imported as copies (your existing data is never overwritten); the session is always created fresh.',
|
|
971
|
+
'gameplay.chooseFile': 'Choose a gameplay JSON file…',
|
|
972
|
+
'gameplay.invalidFile': 'The selected file is not a valid gameplay JSON',
|
|
973
|
+
'gameplay.busy': 'Working…',
|
|
974
|
+
|
|
975
|
+
'chat.emptyStandardTitle': 'Start a new conversation',
|
|
976
|
+
'chat.emptyStandardSub': 'Type a message to begin',
|
|
977
|
+
'chat.emptyNpcTitle': 'Chat with {name}',
|
|
978
|
+
'chat.emptyGroupTitle': 'Group chat',
|
|
979
|
+
'chat.emptyGroupSub': 'Multiple characters take turns speaking',
|
|
980
|
+
'chat.speakerChar': 'Character',
|
|
981
|
+
'chat.speakerAssistant': 'Assistant',
|
|
982
|
+
'chat.thinking': 'Thinking',
|
|
983
|
+
'chat.editMsg': 'Edit message',
|
|
984
|
+
'chat.addAttachment': 'Add attachment',
|
|
985
|
+
'chat.saveHint': '⌘/Ctrl+Enter to save · Esc to cancel',
|
|
986
|
+
'chat.saveRegenerate': 'Save & regenerate',
|
|
987
|
+
'chat.saveOnly': 'Save only',
|
|
988
|
+
'chat.toolCall': 'Tool: {name}',
|
|
989
|
+
'chat.contextCompressed': 'Context compressed',
|
|
990
|
+
'chat.chooseModel': 'Model',
|
|
991
|
+
'chat.selectModel': 'Select model',
|
|
992
|
+
'chat.attachTip': 'Add image/video (paste or click)',
|
|
993
|
+
'chat.phGroup': 'Type a message… use @name to address a character, /new for a new topic, /pass to skip',
|
|
994
|
+
'chat.phSingle': 'Type a message… /new starts a new topic',
|
|
995
|
+
'chat.mentionHead': '@ Mention a character',
|
|
996
|
+
'chat.stopTip': 'Stop generating',
|
|
997
|
+
'chat.sendTip': 'Send',
|
|
998
|
+
'chat.hintSend': 'Enter to send · Shift+Enter for a new line',
|
|
999
|
+
'chat.hintMagic': 'Magic commands: /new · /pass',
|
|
1000
|
+
'chat.hintMention': ' · @name to address',
|
|
1001
|
+
'chat.cmdNew': 'Start a new topic (truncates context)',
|
|
1002
|
+
'chat.cmdPass': 'Skip this turn (group chat, keeps history)',
|
|
1003
|
+
'chat.modelEmpty': 'No models available. Add and test a connection under Settings → Model Providers first.',
|
|
1004
|
+
'chat.noActions': 'No actions available for this message',
|
|
1005
|
+
'chat.regenerate': 'Regenerate reply',
|
|
1006
|
+
'chat.viewRaw': 'View raw log',
|
|
1007
|
+
'chat.viewMemory': 'View memory',
|
|
1008
|
+
'chat.fork': 'Fork conversation',
|
|
1009
|
+
'chat.rawLogTitle': 'Raw log · Message #{id}',
|
|
1010
|
+
'chat.requestBody': 'Request body',
|
|
1011
|
+
'chat.fullResponse': 'Full response',
|
|
1012
|
+
'chat.exportTip': 'Download as txt file',
|
|
1013
|
+
'chat.noRequestBody': '(No request body)',
|
|
1014
|
+
'chat.fullResponseJson': 'Full response JSON',
|
|
1015
|
+
'chat.fromSse': 'Assembled from streaming SSE chunks',
|
|
1016
|
+
'chat.noRawContent': 'This message has no streamable content',
|
|
1017
|
+
'chat.pasteImage': 'Pasted image.{ext}',
|
|
1018
|
+
'chat.loop': 'Round {n}',
|
|
1019
|
+
'chat.current': 'Current',
|
|
1020
|
+
'chat.queueTitle': 'Speaking queue',
|
|
1021
|
+
'chat.queueLoops': '{n} rounds',
|
|
1022
|
+
'chat.queueRandom': 'Random order (shuffled each round)',
|
|
1023
|
+
'chat.queueFixed': 'Fixed seat order',
|
|
1024
|
+
'chat.queueLocateSelf': 'Click to locate your message',
|
|
1025
|
+
'chat.queueLocateNpc': 'Click to locate {name}\'s message',
|
|
1026
|
+
'chat.sortRandom': 'Random order',
|
|
1027
|
+
'chat.collpMore': '(Click to expand full content)',
|
|
1028
|
+
|
|
1029
|
+
'dash.welcome': 'Welcome back to the Tavern 🫖',
|
|
1030
|
+
'dash.hero': 'Tavern Harness — a freer, more fun, easier-to-use tavern!\nSupports any OpenAI-compatible API and local models, with all data staying on your device;\nControlled local skills unlock more system capabilities and gameplay that plain text chat can\'t achieve;\nThe tavern comes agent-native furnished — want something,',
|
|
1031
|
+
'dash.heroAccent': 'why not ask the tavern keeper~',
|
|
1032
|
+
'dash.newChat': 'New conversation',
|
|
1033
|
+
'dash.goCharacters': 'Go to Characters',
|
|
1034
|
+
'dash.openWiki': 'Open user Wiki',
|
|
1035
|
+
'dash.continue': 'Continue character conversations',
|
|
1036
|
+
'dash.noCharConv': 'No character conversations yet — start from the cards below',
|
|
1037
|
+
'dash.groupSession': 'Group chat',
|
|
1038
|
+
'dash.newSession': 'New conversation',
|
|
1039
|
+
'dash.timeJustNow': 'Just now',
|
|
1040
|
+
'dash.timeMinAgo': '{n} min ago',
|
|
1041
|
+
'dash.timeHourAgo': '{n} hr ago',
|
|
1042
|
+
'dash.timeDayAgo': '{n} days ago',
|
|
1043
|
+
'dash.regulars': 'Tavern regulars',
|
|
1044
|
+
'dash.startChat': 'Start chat',
|
|
1045
|
+
|
|
1046
|
+
'settings.title': 'Settings',
|
|
1047
|
+
'settings.sub': 'Model API config, hyperparameters and theme',
|
|
1048
|
+
'settings.genParams': '🎛 Generation parameters',
|
|
1049
|
+
'settings.tempDesc': 'Creativity & randomness',
|
|
1050
|
+
'settings.topPDesc': 'Nucleus sampling',
|
|
1051
|
+
'settings.topKDesc': 'Candidate count',
|
|
1052
|
+
'settings.maxTokensDesc': '0 = unlimited',
|
|
1053
|
+
'settings.freqDesc': 'Repetition penalty',
|
|
1054
|
+
'settings.presDesc': 'Topic novelty',
|
|
1055
|
+
'settings.repDesc': 'Repetition penalty',
|
|
1056
|
+
'settings.effortDesc': 'Reasoning intensity',
|
|
1057
|
+
'settings.contextCompression': 'Context Window Size',
|
|
1058
|
+
'settings.contextCompressionDesc': 'Context compression',
|
|
1059
|
+
'settings.off': 'Off',
|
|
1060
|
+
'settings.theme': '🎨 Theme',
|
|
1061
|
+
'settings.appearance': 'Appearance mode',
|
|
1062
|
+
'settings.system': 'Follow system',
|
|
1063
|
+
'settings.light': 'Light',
|
|
1064
|
+
'settings.dark': 'Dark',
|
|
1065
|
+
'settings.themeTip': 'Fixed Cyber Amber theme color; appearance mode follows the system.',
|
|
1066
|
+
'settings.language': '🌐 Interface Language',
|
|
1067
|
+
'settings.languageLabel': 'Interface Language',
|
|
1068
|
+
'settings.followBrowser': 'Follow system language',
|
|
1069
|
+
'settings.resetTitle': '⚠️ Reset this tavern',
|
|
1070
|
+
'settings.resetDesc': 'Clear all local data for this tavern and restore its initial state. Other ports and browser instances are unaffected.',
|
|
1071
|
+
'settings.resetBtn': 'Reset this tavern',
|
|
1072
|
+
'settings.resetConfirmTitle': 'Reset this tavern',
|
|
1073
|
+
'settings.resetConfirm': 'Reset this tavern? All conversations, characters, lorebooks, skills, model providers, and statistics will be deleted and cannot be restored. Other ports and browser instances are unaffected.',
|
|
1074
|
+
|
|
1075
|
+
// ---- Built-in NPC "Tavern Keeper" (seeded on first launch, follows language) ----
|
|
1076
|
+
'builtinNpc.name': 'Tavern Keeper',
|
|
1077
|
+
'builtinNpc.prompt': 'A mysterious tavern keeper who can fulfill any request from guests',
|
|
1078
|
+
'builtinNpc.greeting': 'Welcome! Have a seat~',
|
|
1079
|
+
'builtinWorldbook.name': 'Tavern Management Handbook (Essentials)',
|
|
1080
|
+
'settings.providerTitle': '🔌 Model Providers (multiple endpoints)',
|
|
1081
|
+
'settings.noProvider': 'No providers configured yet — add one below',
|
|
1082
|
+
'settings.enable': 'Enabled',
|
|
1083
|
+
'settings.disable': 'Disabled',
|
|
1084
|
+
'settings.testing': 'Testing',
|
|
1085
|
+
'settings.test': 'Test',
|
|
1086
|
+
'settings.models': 'Models: ',
|
|
1087
|
+
'settings.emptyModels': 'None',
|
|
1088
|
+
'settings.modelAlias': 'Display alias',
|
|
1089
|
+
'settings.modelAliasPh': 'Leave blank to show the model ID',
|
|
1090
|
+
'settings.addProvider': 'Add Provider',
|
|
1091
|
+
'settings.editProvider': 'Edit Provider',
|
|
1092
|
+
'settings.deleteProvider': 'Delete Provider',
|
|
1093
|
+
'settings.providerName': 'Name',
|
|
1094
|
+
'settings.providerNamePh': 'e.g. DeepSeek Official',
|
|
1095
|
+
'settings.baseUrl': 'Base URL',
|
|
1096
|
+
'settings.baseUrlPh': 'https://api.deepseek.com/v1/ or http://192.168.x.x:8788/',
|
|
1097
|
+
'settings.apiKey': 'API Key',
|
|
1098
|
+
'settings.apiKeyPh': 'sk-...',
|
|
1099
|
+
|
|
1100
|
+
'workshop.title': 'Character Workshop',
|
|
1101
|
+
'workshop.sub': 'Manage NPCs, Lorebooks and skills',
|
|
1102
|
+
'workshop.tabCharacters': '👤 Characters',
|
|
1103
|
+
'workshop.tabWorldbooks': '📖 Lorebooks',
|
|
1104
|
+
'workshop.tabSkills': '🛠 Skills',
|
|
1105
|
+
'workshop.charCount': 'Characters ({n})',
|
|
1106
|
+
'workshop.pngImport': 'PNG import',
|
|
1107
|
+
'workshop.pngImportTip': 'Import SillyTavern PNG character card (chara V2 / ccv3)',
|
|
1108
|
+
'workshop.pngImportWbTip': 'Import SillyTavern PNG character card (chara V2 / ccv3) — parses embedded Lorebook',
|
|
1109
|
+
'workshop.newChar': 'New character',
|
|
1110
|
+
'workshop.noGreeting': '(No greeting)',
|
|
1111
|
+
'workshop.protectedTip': 'Built-in characters are protected',
|
|
1112
|
+
'workshop.uploadAvatar': 'Upload avatar',
|
|
1113
|
+
'workshop.name': 'Name',
|
|
1114
|
+
'workshop.charNamePh': 'Character name',
|
|
1115
|
+
'workshop.promptLabel': 'Persona prompt (injected into system prompt)',
|
|
1116
|
+
'workshop.promptPh': 'e.g. A mysterious tavern keeper who can fulfill any request',
|
|
1117
|
+
'workshop.greetingLabel': 'Greeting',
|
|
1118
|
+
'workshop.greetingRandomHint': '(if you create multiple greetings, each new chat picks one at random)',
|
|
1119
|
+
'workshop.greetingPh': 'Welcome! Have a seat~',
|
|
1120
|
+
'workshop.addAltGreeting': 'Add greeting',
|
|
1121
|
+
'workshop.resetChar': 'Restore default',
|
|
1122
|
+
'workshop.resetCharTip': 'Restore the Tavern Keeper default persona, greeting, and equipped skills',
|
|
1123
|
+
'workshop.enableSkills': 'Enabled skills ({a}/{b})',
|
|
1124
|
+
'workshop.noSkills': 'No skills',
|
|
1125
|
+
'workshop.newCharTitle': 'New character',
|
|
1126
|
+
'workshop.editCharTitle': 'Edit character · {name}',
|
|
1127
|
+
'workshop.deleteCharTitle': 'Delete character',
|
|
1128
|
+
'workshop.wbCount': 'Lorebooks ({n})',
|
|
1129
|
+
'workshop.newWb': 'New Lorebook',
|
|
1130
|
+
'workshop.wbEmpty': 'No Lorebooks yet — build a worldview for your characters',
|
|
1131
|
+
'workshop.wbChars': '{n} chars',
|
|
1132
|
+
'workshop.browseEdit': 'View / edit',
|
|
1133
|
+
'workshop.editWbTitle': 'Edit Lorebook',
|
|
1134
|
+
'workshop.newWbTitle': 'New Lorebook',
|
|
1135
|
+
'workshop.wbName': 'Lorebook name',
|
|
1136
|
+
'workshop.wbNamePh': 'e.g. The Northern Continent',
|
|
1137
|
+
'workshop.wbContentLabel': 'Content (appended after the character persona)',
|
|
1138
|
+
'workshop.wbContentPh': 'Worldbuilding text…',
|
|
1139
|
+
'workshop.wbMarkdownRender': 'Render Markdown',
|
|
1140
|
+
'workshop.resetWb': 'Restore default',
|
|
1141
|
+
'workshop.resetWbTip': 'Restore the built-in Lorebook default content',
|
|
1142
|
+
'workshop.deleteWbTitle': 'Delete Lorebook',
|
|
1143
|
+
'workshop.skillGroupBuiltin': 'Built-in skills ({n})',
|
|
1144
|
+
'workshop.skillGroupCustom': 'Custom skills ({n})',
|
|
1145
|
+
'workshop.skillGroupImported': 'Imported skills ({n})',
|
|
1146
|
+
'workshop.implType': 'Implementation type: {t}',
|
|
1147
|
+
'workshop.sortHint': 'Drag cards vertically to reorder',
|
|
1148
|
+
'workshop.deleteSkillTitle': 'Delete skill',
|
|
1149
|
+
'workshop.skillDetails': 'Skill details',
|
|
1150
|
+
'workshop.skillName': 'Skill name',
|
|
1151
|
+
'workshop.skillNameInvalid': 'Skill names must start with a lowercase letter and contain only lowercase letters, numbers, or underscores (3–40 characters)',
|
|
1152
|
+
'workshop.skillNameExists': 'A skill with this name already exists',
|
|
1153
|
+
'workshop.skillDescriptionLong': 'Description cannot exceed 500 characters',
|
|
1154
|
+
'workshop.skillSchemaJson': 'Parameters schema must be valid JSON',
|
|
1155
|
+
'workshop.skillSchemaObject': 'Parameters schema must be a JSON object',
|
|
1156
|
+
'workshop.viewDetails': 'Details',
|
|
1157
|
+
'workshop.skillDescription': 'Description',
|
|
1158
|
+
'workshop.skillParameters': 'Parameters schema',
|
|
1159
|
+
'workshop.skillExecution': 'Declarative implementation',
|
|
1160
|
+
'workshop.skillRawJson': 'Raw JSON (OpenAI tool definition)',
|
|
1161
|
+
'workshop.skillNoExecution': '(No declarative implementation — handled by built-in logic)',
|
|
1162
|
+
'workshop.skillNoParams': '(No parameters)',
|
|
1163
|
+
'workshop.skillBadJson': '(Failed to parse JSON)',
|
|
1164
|
+
|
|
1165
|
+
'stats.title': '🏆 Achievements',
|
|
1166
|
+
'stats.sub': 'Your achievement trophies are displayed here',
|
|
1167
|
+
'stats.resetBtn': 'Reset stats',
|
|
1168
|
+
'stats.resetConfirm': 'Reset career stats? (Unlocked trophies won\'t be cleared)',
|
|
1169
|
+
'stats.shelfTitle': '🎖️ Trophy Shelf',
|
|
1170
|
+
'stats.unlocked': 'Unlocked',
|
|
1171
|
+
'stats.trophies': 'trophies',
|
|
1172
|
+
'stats.locked': 'Locked',
|
|
1173
|
+
'stats.unlockedStatus': '✓ Unlocked',
|
|
1174
|
+
'stats.hidden': '???',
|
|
1175
|
+
'stats.unlockedAt': 'Unlocked',
|
|
1176
|
+
'stats.totalTokens': 'Total tokens (input + output)',
|
|
1177
|
+
'stats.inputTokens': 'Input tokens',
|
|
1178
|
+
'stats.outputTokens': 'Output tokens',
|
|
1179
|
+
'stats.rounds': 'Conversation rounds',
|
|
1180
|
+
'stats.sessions': 'Total sessions',
|
|
1181
|
+
'stats.avgRounds': 'Avg rounds / session',
|
|
1182
|
+
'stats.mostActive': '🏆 Most Active Character',
|
|
1183
|
+
'stats.roundsCount': '{n} rounds',
|
|
1184
|
+
'stats.noData': 'No data yet — go chat with a character',
|
|
1185
|
+
'stats.ranking': '🎭 Character Round Ranking',
|
|
1186
|
+
'stats.colChar': 'Character',
|
|
1187
|
+
'stats.colRounds': 'Rounds',
|
|
1188
|
+
|
|
1189
|
+
'ach.traveler.name': 'Traveler',
|
|
1190
|
+
'ach.traveler.desc': 'Cumulatively used 1,000 tokens',
|
|
1191
|
+
'ach.oldFriend.name': 'Old Friend',
|
|
1192
|
+
'ach.oldFriend.desc': 'Cumulatively used 10,000 tokens',
|
|
1193
|
+
'ach.bard.name': 'Bard',
|
|
1194
|
+
'ach.bard.desc': 'Cumulatively used 100,000 tokens',
|
|
1195
|
+
'ach.nightOwl.name': 'Night Owl',
|
|
1196
|
+
'ach.nightOwl.desc': 'Cumulatively used 1,000,000 tokens',
|
|
1197
|
+
'ach.legend.name': 'Tavern Legend',
|
|
1198
|
+
'ach.legend.desc': 'Cumulatively used 100,000,000 tokens',
|
|
1199
|
+
|
|
1200
|
+
'achModal.title': '🎉 Achievement unlocked!',
|
|
1201
|
+
'achModal.total': 'Lifetime total {n} tokens',
|
|
1202
|
+
'achModal.accept': 'Claim trophy',
|
|
1203
|
+
|
|
1204
|
+
'newSession.title': 'Create conversation',
|
|
1205
|
+
'newSession.name': 'Conversation name',
|
|
1206
|
+
'newSession.unnamed': 'Untitled',
|
|
1207
|
+
'newSession.participants': 'Participants',
|
|
1208
|
+
'newSession.noNpcWarn': '⚠️ Create character cards in Workshop first!',
|
|
1209
|
+
'newSession.notSelected': 'Not selected',
|
|
1210
|
+
'newSession.npcChat': 'NPC chat',
|
|
1211
|
+
'newSession.groupChat': 'Group chat ({n} people)',
|
|
1212
|
+
'newSession.orderHint': 'Drag to set speaking order or enable random order.',
|
|
1213
|
+
'newSession.mentionOnly': 'Mention-only mode',
|
|
1214
|
+
'newSession.mentionOnlyHint': 'The player must @ a character; a character that does not @ anyone automatically returns the turn to the player.',
|
|
1215
|
+
'newSession.greeting': 'Enable greeting',
|
|
1216
|
+
'newSession.greetingHint': 'NPC chats use the character greeting; group chats only use the first speaker greeting when the user does not speak first.',
|
|
1217
|
+
'newSession.persona': 'User persona (optional)',
|
|
1218
|
+
'newSession.noPersona': 'No user persona',
|
|
1219
|
+
'newSession.worldbook': 'Lorebook (optional)',
|
|
1220
|
+
'newSession.noWorldbook': 'No Lorebook',
|
|
1221
|
+
'newSession.addCharTip': 'Add character',
|
|
1222
|
+
|
|
1223
|
+
'confirm.modifyTitle': 'Confirm modify operation "{name}"',
|
|
1224
|
+
'confirm.modify': 'The model is requesting to perform a modify operation. Allow it?',
|
|
1225
|
+
'confirm.title': 'Confirm tool execution',
|
|
1226
|
+
'confirm.tool': 'Tool: ',
|
|
1227
|
+
'confirm.reject': 'Reject',
|
|
1228
|
+
'confirm.allow': 'Allow',
|
|
1229
|
+
'confirm.limitTitle': 'Agent execution limit',
|
|
1230
|
+
'confirm.limitMessage': '{reason}\n\nReset the current counters and continue?',
|
|
1231
|
+
'confirm.stop': 'Stop',
|
|
1232
|
+
'confirm.continue': 'Continue',
|
|
1233
|
+
'confirm.depthLimit': 'The consecutive tool-call depth limit ({limit}) has been reached.',
|
|
1234
|
+
'confirm.callLimit': '{limit} tool calls have run, reaching the per-turn limit.',
|
|
1235
|
+
'confirm.identicalLimit': 'Tool {name} requested the same arguments {limit} times consecutively.',
|
|
1236
|
+
'confirm.failureLimit': 'Tool calls have failed {limit} times consecutively.',
|
|
1237
|
+
'confirm.limitCanceled': 'CANCELLED: user stopped after an Agent execution limit was reached',
|
|
1238
|
+
|
|
1239
|
+
'toast.sessionDeleted': 'Session deleted',
|
|
1240
|
+
'toast.sessionUpdated': 'Conversation settings updated',
|
|
1241
|
+
'toast.sessionReset': 'Conversation reset',
|
|
1242
|
+
'toast.sessionPinned': 'Conversation pinned',
|
|
1243
|
+
'toast.sessionUnpinned': 'Conversation unpinned',
|
|
1244
|
+
'toast.exportCanceled': 'Export canceled',
|
|
1245
|
+
'toast.gameplayExported': 'Gameplay exported',
|
|
1246
|
+
'toast.gameplayImported': 'Imported gameplay "{title}": {npc} new characters, Lorebook {wb}, {tools} skills, {msgs} messages',
|
|
1247
|
+
'toast.exportFailed': 'Export failed: {msg}',
|
|
1248
|
+
'toast.busy': 'Generating, please wait',
|
|
1249
|
+
'toast.passOnlyGroup': '/pass only works in group chats',
|
|
1250
|
+
'toast.mentionRequired': 'Mention-only mode requires @ a character before sending',
|
|
1251
|
+
'toast.noEndpoint': 'Add and enable an endpoint under "Model Providers" and select a model',
|
|
1252
|
+
'toast.genFailed': 'Generation failed: {msg}',
|
|
1253
|
+
'toast.contextCompressionFailed': 'Context compression failed; using the original history: {msg}',
|
|
1254
|
+
'toast.rawExported': 'Raw log exported (auto-sanitized)',
|
|
1255
|
+
'toast.pickOneNpc': 'Choose at least one character',
|
|
1256
|
+
'toast.dupNpc': 'This character is already in the conversation',
|
|
1257
|
+
'toast.importedChar': 'Parsed character card "{name}" ({version}) — review and save',
|
|
1258
|
+
'toast.noEmbeddedWb': 'The character card "{name}" has no embedded Lorebook',
|
|
1259
|
+
'toast.importFailed': 'Import failed: {msg}',
|
|
1260
|
+
'toast.charDeleted': 'Deleted character "{name}"',
|
|
1261
|
+
'toast.nameRequired': 'Name cannot be empty',
|
|
1262
|
+
'toast.charCreated': 'Created character "{name}"',
|
|
1263
|
+
'toast.charUpdated': 'Updated character "{name}"',
|
|
1264
|
+
'toast.wbDraftImported': 'Parsed the Lorebook "{name}" from the character card — review and save',
|
|
1265
|
+
'toast.wbNameContentRequired': 'Lorebook name and content cannot be empty',
|
|
1266
|
+
'toast.wbUpdated': 'Lorebook updated',
|
|
1267
|
+
'toast.wbCreated': 'Lorebook created',
|
|
1268
|
+
'toast.wbDeleted': 'Lorebook deleted',
|
|
1269
|
+
'toast.skillDeleted': 'Skill deleted',
|
|
1270
|
+
'toast.providerDisabled': 'Disabled "{name}"',
|
|
1271
|
+
'toast.providerEnabled': 'Enabled "{name}"',
|
|
1272
|
+
'toast.providerOk': '✅ Connected. Fetched {n} models{via}',
|
|
1273
|
+
'toast.providerViaProxy': ' (via local proxy)',
|
|
1274
|
+
'toast.providerFail': '❌ Connection failed: {msg}{hint}',
|
|
1275
|
+
'toast.providerFailCors': '. Likely blocked by CORS (the target API does not send CORS headers; the built-in proxy was tried automatically and failed)',
|
|
1276
|
+
'toast.providerDeleted': 'Provider deleted',
|
|
1277
|
+
'toast.providerUpdated': 'Provider updated',
|
|
1278
|
+
'toast.providerAdded': 'Provider added',
|
|
1279
|
+
'toast.providerDuplicateModel': 'A model named "{model}" already exists. Set an alias before saving.',
|
|
1280
|
+
'toast.statsReset': 'Stats reset',
|
|
1281
|
+
'toast.canceled': 'CANCELLED: user canceled {name}',
|
|
1282
|
+
|
|
1283
|
+
'tool.timeout': 'Request timed out',
|
|
1284
|
+
'tool.diceRoll': '{expr}: [{rolls}] = {total}{crit}',
|
|
1285
|
+
'tool.diceCrit20': ' Critical hit!',
|
|
1286
|
+
'tool.diceCrit1': ' Critical fail!',
|
|
1287
|
+
'tool.gateShellNonAllowlistedTitle': 'Confirm non-allowlisted command',
|
|
1288
|
+
'tool.gateShellNonAllowlistedMsg': 'is not on the command allowlist. Allow it to run?',
|
|
1289
|
+
'tool.shellDenied': 'CANCELLED: user rejected {name}',
|
|
1290
|
+
'tool.truncated': '\n…(truncated)',
|
|
1291
|
+
'tool.noResult': '(no result)',
|
|
1292
|
+
'tool.fileDisplaySuccess': 'OK: Displayed {path} in a popup (kind={kind})',
|
|
1293
|
+
'tool.fileWriteSuccess': 'OK: Wrote {count} characters to {path}',
|
|
1294
|
+
'tool.fileEditSuccess': 'OK: Edited {path}; replaced {count} occurrence(s)',
|
|
1295
|
+
'tool.skillCreated': 'OK: Created skill {name}. Note: it is not enabled for any character by default; use update_character with enable_skills to enable it.',
|
|
1296
|
+
'tool.skillUpdated': 'OK: Updated skill {name}',
|
|
1297
|
+
'tool.skillDeleted': 'OK: Deleted skill {name}',
|
|
1298
|
+
'tool.characterCreated': 'OK: Created character {name}',
|
|
1299
|
+
'tool.characterUpdated': 'OK: Updated character {name}',
|
|
1300
|
+
'tool.characterDeleted': 'OK: Deleted character {name}',
|
|
1301
|
+
'tool.lorebookCreated': 'OK: Created Lorebook {name}',
|
|
1302
|
+
'tool.lorebookUpdated': 'OK: Updated Lorebook {name}',
|
|
1303
|
+
'tool.lorebookDeleted': 'OK: Deleted Lorebook {name}',
|
|
1304
|
+
|
|
1305
|
+
// ---- File display modal (file_display) ----
|
|
1306
|
+
'display.view': 'View',
|
|
1307
|
+
'display.loading': 'Loading file…',
|
|
1308
|
+
'display.fileError': 'Cannot read file: {path}',
|
|
1309
|
+
'display.imageLoadError': 'Failed to load image',
|
|
1310
|
+
'display.zoomIn': 'Zoom in',
|
|
1311
|
+
'display.zoomOut': 'Zoom out',
|
|
1312
|
+
'display.resetView': 'Reset view (double-click image also works)',
|
|
1313
|
+
'display.htmlZoomIn': 'Zoom in HTML content',
|
|
1314
|
+
'display.htmlZoomOut': 'Zoom out HTML content',
|
|
1315
|
+
'display.resetHtmlZoom': 'Reset HTML zoom',
|
|
1316
|
+
'display.enableWrap': 'Enable word wrap',
|
|
1317
|
+
'display.disableWrap': 'Disable word wrap',
|
|
1318
|
+
'display.pictureInPicture': 'Picture in picture',
|
|
1319
|
+
'display.exitPictureInPicture': 'Exit picture in picture',
|
|
1320
|
+
'display.resizePictureInPicture': 'Resize picture in picture',
|
|
1321
|
+
'display.htmlSandboxNote': 'HTML rendered in a sandbox (scripts run, isolated from the outside)',
|
|
1322
|
+
|
|
1323
|
+
'prompt.worldBook': '=== LOREBOOK ===',
|
|
1324
|
+
'prompt.userInfo': '=== USER INFO (role played by the user) ===',
|
|
1325
|
+
'prompt.roleTag': 'character name',
|
|
1326
|
+
},
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
// ------------------------------------------------------------------
|
|
1330
|
+
// 语言状态(模块级),供非组件模块(store / 工具)使用
|
|
1331
|
+
// ------------------------------------------------------------------
|
|
1332
|
+
|
|
1333
|
+
function detectBrowserLanguage(): LangKey {
|
|
1334
|
+
const nav = (typeof navigator !== 'undefined' ? navigator.language : '') || '';
|
|
1335
|
+
const lower = nav.toLowerCase();
|
|
1336
|
+
if (lower.startsWith('zh')) {
|
|
1337
|
+
if (lower.includes('tw') || lower.includes('hk') || lower.includes('hant') || lower.includes('mo')) return 'zh-TW';
|
|
1338
|
+
return 'zh-CN';
|
|
1339
|
+
}
|
|
1340
|
+
return 'en';
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
let currentLang: LangKey = detectBrowserLanguage();
|
|
1344
|
+
const listeners = new Set<() => void>();
|
|
1345
|
+
|
|
1346
|
+
/** 设置当前语言(null = 跟随浏览器),同步 <html lang> 并通知订阅者 */
|
|
1347
|
+
export function setLanguage(lang: AppLanguage | null): void {
|
|
1348
|
+
currentLang = lang ?? detectBrowserLanguage();
|
|
1349
|
+
try {
|
|
1350
|
+
document.documentElement.lang = currentLang;
|
|
1351
|
+
} catch {
|
|
1352
|
+
/* SSR / 非浏览器环境忽略 */
|
|
1353
|
+
}
|
|
1354
|
+
listeners.forEach((fn) => fn());
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
export function currentLanguage(): LangKey {
|
|
1358
|
+
return currentLang;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/** 当前语言对应的 Intl locale(用于日期/数字格式化) */
|
|
1362
|
+
export function currentLocale(): string {
|
|
1363
|
+
return currentLang === 'zh-CN' ? 'zh-CN' : currentLang === 'zh-TW' ? 'zh-TW' : 'en-US';
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function translateFor(lang: LangKey, key: string, params?: Record<string, string | number>): string {
|
|
1367
|
+
const table = MESSAGES[lang] ?? MESSAGES['zh-CN'];
|
|
1368
|
+
let s = table[key] ?? MESSAGES['zh-CN'][key] ?? key;
|
|
1369
|
+
if (params) {
|
|
1370
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1371
|
+
s = s.split(`{${k}}`).join(String(v));
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return s;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function subscribe(fn: () => void): () => void {
|
|
1378
|
+
listeners.add(fn);
|
|
1379
|
+
return () => {
|
|
1380
|
+
listeners.delete(fn);
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function getSnapshot(): LangKey {
|
|
1385
|
+
return currentLang;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* React 响应式翻译 hook:语言切换时自动重渲染。
|
|
1390
|
+
* 用法:const t = useT(); t('nav.chat') / t('toast.genFailed', { msg })
|
|
1391
|
+
*/
|
|
1392
|
+
export function useT() {
|
|
1393
|
+
const lang = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
1394
|
+
return useCallback(
|
|
1395
|
+
(key: string, params?: Record<string, string | number>) => translateFor(lang, key, params),
|
|
1396
|
+
[lang]
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/** 静态翻译:读当前语言快照(store / 工具执行器使用) */
|
|
1401
|
+
export function translate(key: string, params?: Record<string, string | number>): string {
|
|
1402
|
+
return translateFor(currentLang, key, params);
|
|
1403
|
+
}
|