feat: localize journal components to Chinese
This commit is contained in:
parent
9a41f541e9
commit
1ef41f04fc
|
|
@ -68,7 +68,7 @@ export const ComposePanel: Component = () => {
|
|||
disabled={sending()}
|
||||
class="ml-auto bg-blue-600 text-white rounded px-3 py-1 text-xs hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{sending() ? "..." : "Send"}
|
||||
{sending() ? "..." : "发送"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export const ConnectionBar: Component = () => {
|
|||
};
|
||||
|
||||
const handleDeleteSession = (id: string) => {
|
||||
if (confirm(`Delete session "${id}"? This cannot be undone.`)) {
|
||||
if (confirm(`确定要删除会话 "${id}"?此操作不可撤销。`)) {
|
||||
deleteSession(id);
|
||||
}
|
||||
};
|
||||
|
|
@ -41,7 +41,7 @@ export const ConnectionBar: Component = () => {
|
|||
await hydrateFromServer(id);
|
||||
setSessionId(id);
|
||||
} catch (e) {
|
||||
setError("Failed to load session");
|
||||
setError("无法加载会话");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ export const ConnectionBar: Component = () => {
|
|||
{/* Status row */}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-green-500" />
|
||||
<span class="text-xs text-gray-500">connected</span>
|
||||
<span class="text-xs text-gray-500">已连接</span>
|
||||
<Show when={stream.sessionId}>
|
||||
<span class="text-xs font-mono bg-gray-100 px-1.5 py-0.5 rounded">
|
||||
{stream.sessionName || stream.sessionId}
|
||||
|
|
@ -62,12 +62,12 @@ export const ConnectionBar: Component = () => {
|
|||
</Show>
|
||||
<div class="flex-1" />
|
||||
<Show when={stream.myName !== "gm"}>
|
||||
<span class="text-xs text-gray-400">Playing as {stream.myName}</span>
|
||||
<span class="text-xs text-gray-400">扮演 {stream.myName}</span>
|
||||
</Show>
|
||||
<button
|
||||
onClick={handleDisconnect}
|
||||
class="text-xs text-gray-400 hover:text-red-500"
|
||||
title="Disconnect"
|
||||
title="断开连接"
|
||||
>
|
||||
⏻
|
||||
</button>
|
||||
|
|
@ -77,12 +77,12 @@ export const ConnectionBar: Component = () => {
|
|||
<Show when={stream.myName === "gm"}>
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-xs text-gray-500">Sessions:</span>
|
||||
<span class="text-xs text-gray-500">会话列表:</span>
|
||||
<button
|
||||
onClick={() => setShowNewSession((v) => !v)}
|
||||
class="text-xs text-blue-600 hover:underline"
|
||||
>
|
||||
+ new
|
||||
+ 新建
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ export const ConnectionBar: Component = () => {
|
|||
type="text"
|
||||
value={newSessionName()}
|
||||
onInput={(e) => setNewSessionName(e.currentTarget.value)}
|
||||
placeholder="Session name"
|
||||
placeholder="会话名称"
|
||||
class="flex-1 border border-gray-300 rounded px-2 py-0.5 text-xs"
|
||||
onKeyDown={(e) => e.key === "Enter" && handleCreateSession()}
|
||||
/>
|
||||
|
|
@ -100,7 +100,7 @@ export const ConnectionBar: Component = () => {
|
|||
onClick={handleCreateSession}
|
||||
class="bg-green-600 text-white rounded px-2 py-0.5 text-xs hover:bg-green-700"
|
||||
>
|
||||
Create
|
||||
Create Create
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
@ -126,7 +126,7 @@ export const ConnectionBar: Component = () => {
|
|||
handleDeleteSession(id);
|
||||
}}
|
||||
class="text-gray-400 hover:text-red-500 text-xs"
|
||||
title="Delete session"
|
||||
title="删除会话"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const SchemaFields: Component<{
|
|||
const shape = unwrapShape(props.schema);
|
||||
|
||||
if (!shape) {
|
||||
return <p class="text-xs text-gray-400">No fields</p>;
|
||||
return <p class="text-xs text-gray-400">无字段</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -98,7 +98,7 @@ const SchemaFields: Component<{
|
|||
checked={isSet()}
|
||||
onChange={toggleOptional}
|
||||
class="mt-1.5"
|
||||
title={`Include ${key}`}
|
||||
title={`包含 ${key}`}
|
||||
/>
|
||||
</Show>
|
||||
<div class={`flex-1 ${isOptional && !isSet() ? "opacity-40" : ""}`}>
|
||||
|
|
@ -204,7 +204,7 @@ const FieldInput: Component<{
|
|||
}}
|
||||
disabled={props.disabled}
|
||||
class={inputClass}
|
||||
placeholder="comma-separated"
|
||||
placeholder="逗号分隔"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,20 +44,19 @@ function parseInput(raw: string): ParsedInput {
|
|||
if (raw.startsWith("/roll ")) {
|
||||
const notation = raw.slice("/roll ".length).trim();
|
||||
if (!notation)
|
||||
return { type: "roll", payload: {}, error: "Dice notation required" };
|
||||
return { type: "roll", payload: {}, error: "需要骰子表达式" };
|
||||
return { type: "roll", payload: { notation, label: notation } };
|
||||
}
|
||||
|
||||
if (raw.startsWith("/spark ")) {
|
||||
const key = raw.slice("/spark ".length).trim();
|
||||
if (!key)
|
||||
return { type: "spark", payload: {}, error: "Spark table key required" };
|
||||
if (!key) return { type: "spark", payload: {}, error: "需要种子表键名" };
|
||||
return { type: "spark", payload: { key } };
|
||||
}
|
||||
|
||||
if (raw.startsWith("/link ")) {
|
||||
const arg = raw.slice("/link ".length).trim();
|
||||
if (!arg) return { type: "link", payload: {}, error: "Path required" };
|
||||
if (!arg) return { type: "link", payload: {}, error: "需要路径" };
|
||||
const hashIdx = arg.indexOf("#");
|
||||
const path = hashIdx === -1 ? arg : arg.slice(0, hashIdx);
|
||||
const section =
|
||||
|
|
@ -67,7 +66,7 @@ function parseInput(raw: string): ParsedInput {
|
|||
|
||||
// /roll, /spark, or /link with no space — need to complete, don't send
|
||||
if (raw === "/roll" || raw === "/spark" || raw === "/link") {
|
||||
return { type: "chat", payload: {}, error: "Complete the command" };
|
||||
return { type: "chat", payload: {}, error: "请补全命令" };
|
||||
}
|
||||
|
||||
return { type: "chat", payload: { text: raw } };
|
||||
|
|
@ -168,7 +167,7 @@ export const JournalInput: Component = () => {
|
|||
textareaRef?.focus();
|
||||
return;
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "Failed to roll spark table");
|
||||
setError(e instanceof Error ? e.message : "种子表掷骰失败");
|
||||
setSending(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -216,7 +215,7 @@ export const JournalInput: Component = () => {
|
|||
);
|
||||
return matches.length > 0
|
||||
? matches
|
||||
: [{ label: "Unknown command", kind: "no-results", insertText: "" }];
|
||||
: [{ label: "未知命令", kind: "no-results", insertText: "" }];
|
||||
}
|
||||
|
||||
// After /roll — show dice suggestions
|
||||
|
|
@ -230,7 +229,7 @@ export const JournalInput: Component = () => {
|
|||
)
|
||||
.slice(0, 8);
|
||||
if (matches.length === 0) {
|
||||
return [{ label: "No dice found", kind: "no-results", insertText: "" }];
|
||||
return [{ label: "未找到骰子", kind: "no-results", insertText: "" }];
|
||||
}
|
||||
return matches.map((d) => ({
|
||||
label: d.notation,
|
||||
|
|
@ -252,7 +251,7 @@ export const JournalInput: Component = () => {
|
|||
if (matches.length === 0) {
|
||||
return [
|
||||
{
|
||||
label: "No spark tables found",
|
||||
label: "未找到种子表",
|
||||
kind: "no-results",
|
||||
insertText: "",
|
||||
},
|
||||
|
|
@ -276,9 +275,7 @@ export const JournalInput: Component = () => {
|
|||
)
|
||||
.slice(0, 8);
|
||||
if (matches.length === 0) {
|
||||
return [
|
||||
{ label: "No links found", kind: "no-results", insertText: "" },
|
||||
];
|
||||
return [{ label: "未找到链接", kind: "no-results", insertText: "" }];
|
||||
}
|
||||
return matches.map((l) => {
|
||||
const insert = l.section
|
||||
|
|
@ -459,7 +456,7 @@ export const JournalInput: Component = () => {
|
|||
ref={completionsRef}
|
||||
class="absolute bottom-full left-0 right-0 bg-white border border-gray-200 rounded-t shadow-lg mb-0.5 z-50 px-3 py-2 text-xs text-gray-400 italic"
|
||||
>
|
||||
Loading completions…
|
||||
正在加载补全数据…
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={comp.state.status === "error"}>
|
||||
|
|
@ -467,7 +464,7 @@ export const JournalInput: Component = () => {
|
|||
ref={completionsRef}
|
||||
class="absolute bottom-full left-0 right-0 bg-white border border-red-200 rounded-t shadow-lg mb-0.5 z-50 px-3 py-2 text-xs text-red-500"
|
||||
>
|
||||
{(comp.state as any).message || "Failed to load completions"}
|
||||
{(comp.state as any).message || "无法加载补全数据"}
|
||||
</div>
|
||||
</Match>
|
||||
<Match
|
||||
|
|
@ -486,8 +483,7 @@ export const JournalInput: Component = () => {
|
|||
when={!isObserver()}
|
||||
fallback={
|
||||
<div class="px-3 py-4 text-xs text-gray-400 italic text-center">
|
||||
You are observing. Open this panel on another device to join as GM
|
||||
or player.
|
||||
你正在观察模式。在另一台设备上打开此面板以主持人或玩家身份加入。
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
@ -498,8 +494,8 @@ export const JournalInput: Component = () => {
|
|||
onKeyDown={handleKeyDown}
|
||||
placeholder={
|
||||
isGm()
|
||||
? "Type a message, or /roll, /spark, or /link..."
|
||||
: "Type a message..."
|
||||
? "输入消息,或使用 /roll、/spark、/link 命令..."
|
||||
: "输入消息..."
|
||||
}
|
||||
rows={2}
|
||||
class="w-full resize-none border-0 px-3 pt-2.5 pb-1 text-sm
|
||||
|
|
@ -520,7 +516,7 @@ export const JournalInput: Component = () => {
|
|||
hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed
|
||||
transition-colors"
|
||||
>
|
||||
Send
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ export const JournalPanel: Component<JournalPanelProps> = (props) => {
|
|||
<button
|
||||
onClick={() => setShowInvite(true)}
|
||||
class="text-xs text-gray-400 hover:text-blue-600 px-1.5 py-0.5 rounded border border-dashed border-gray-300 hover:border-blue-400 whitespace-nowrap shrink-0"
|
||||
title="Invite a player"
|
||||
title="邀请玩家"
|
||||
>
|
||||
+ invite
|
||||
+ 邀请
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
@ -161,9 +161,7 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
|||
<Show
|
||||
when={stream.connected && stream.sessionId}
|
||||
fallback={
|
||||
<h2 class="text-sm font-semibold text-gray-700 truncate">
|
||||
Disconnected
|
||||
</h2>
|
||||
<h2 class="text-sm font-semibold text-gray-700 truncate">未连接</h2>
|
||||
}
|
||||
>
|
||||
<div class="min-w-0">
|
||||
|
|
@ -221,7 +219,7 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
|||
onInput={(e) =>
|
||||
setNewSessionName(e.currentTarget.value)
|
||||
}
|
||||
placeholder="Session name"
|
||||
placeholder="会话名称"
|
||||
class="flex-1 border border-gray-300 rounded px-1.5 py-0.5 text-xs"
|
||||
autofocus
|
||||
onKeyDown={(e) => {
|
||||
|
|
@ -242,7 +240,7 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
|||
onClick={() => setShowNewInput(true)}
|
||||
class="w-full text-left px-3 py-1 text-xs text-blue-600 hover:bg-gray-100"
|
||||
>
|
||||
+ New session
|
||||
+ 新建会话
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
@ -257,7 +255,7 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
|||
<button
|
||||
onClick={handleDisconnect}
|
||||
class="text-xs text-gray-400 hover:text-red-500 px-1"
|
||||
title="Disconnect"
|
||||
title="断开连接"
|
||||
>
|
||||
⏻
|
||||
</button>
|
||||
|
|
@ -265,7 +263,7 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
|||
<button
|
||||
onClick={props.onClose}
|
||||
class="text-gray-400 hover:text-gray-600 text-sm px-1"
|
||||
title="Hide panel"
|
||||
title="隐藏面板"
|
||||
>
|
||||
▸
|
||||
</button>
|
||||
|
|
@ -332,16 +330,16 @@ const ConnectDialog: Component = () => {
|
|||
when={!autoJoined() || error()}
|
||||
fallback={
|
||||
<p class="text-sm text-gray-500 text-center">
|
||||
{connecting() ? "Joining session…" : "Joined!"}
|
||||
{connecting() ? "正在加入会话…" : "已加入!"}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<div class="w-full max-w-sm space-y-3">
|
||||
<p class="text-sm text-gray-600 text-center">
|
||||
Enter your name and role to join the session.
|
||||
请输入你的名字和角色来加入会话。
|
||||
<br />
|
||||
<span class="text-xs text-gray-400">
|
||||
Connecting to {window.location.host}
|
||||
连接到 {window.location.host}
|
||||
</span>
|
||||
</p>
|
||||
<input
|
||||
|
|
@ -349,7 +347,7 @@ const ConnectDialog: Component = () => {
|
|||
value={playerName()}
|
||||
onInput={(e) => setPlayerName(e.currentTarget.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleConnect()}
|
||||
placeholder="Your name"
|
||||
placeholder="你的名字"
|
||||
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm"
|
||||
autofocus
|
||||
/>
|
||||
|
|
@ -364,7 +362,7 @@ const ConnectDialog: Component = () => {
|
|||
: "bg-white text-gray-600 border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
{r === "gm" ? "GM" : r === "player" ? "Player" : "Observer"}
|
||||
{r === "gm" ? "主持人" : r === "player" ? "玩家" : "观察者"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -374,7 +372,7 @@ const ConnectDialog: Component = () => {
|
|||
class="w-full bg-blue-600 text-white rounded px-3 py-2 text-sm font-medium
|
||||
hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{connecting() ? "Connecting..." : "Join"}
|
||||
{connecting() ? "连接中..." : "加入"}
|
||||
</button>
|
||||
<Show when={error()}>
|
||||
<p class="text-red-500 text-sm text-center">{error()}</p>
|
||||
|
|
@ -424,7 +422,7 @@ const InviteDialog: Component<InviteDialogProps> = (props) => {
|
|||
<div class="absolute inset-0 z-50 flex items-center justify-center bg-black/20">
|
||||
<div class="bg-white rounded-lg border border-gray-200 shadow-xl w-[280px] p-4 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-gray-800">Invite Player</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-800">邀请玩家</h3>
|
||||
<button
|
||||
onClick={props.onClose}
|
||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||
|
|
@ -433,13 +431,13 @@ const InviteDialog: Component<InviteDialogProps> = (props) => {
|
|||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
Enter a player name and share the link. They will join as a player.
|
||||
输入玩家名字并分享链接,对方将以玩家身份加入。
|
||||
</p>
|
||||
<input
|
||||
type="text"
|
||||
value={playerName()}
|
||||
onInput={(e) => setPlayerName(e.currentTarget.value)}
|
||||
placeholder="Player name"
|
||||
placeholder="玩家名字"
|
||||
class="w-full border border-gray-300 rounded px-2 py-1 text-xs"
|
||||
autofocus
|
||||
/>
|
||||
|
|
@ -459,7 +457,7 @@ const InviteDialog: Component<InviteDialogProps> = (props) => {
|
|||
: "bg-blue-600 text-white hover:bg-blue-700"
|
||||
}`}
|
||||
>
|
||||
{copied() ? "Copied!" : "Copy"}
|
||||
{copied() ? "已复制!" : "复制"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const StreamMessageCard: Component<{
|
|||
<button
|
||||
onClick={() => revertLatest()}
|
||||
class="text-xs text-gray-300 hover:text-red-500 ml-auto px-0.5 leading-none"
|
||||
title="Revert this message"
|
||||
title="撤回此消息"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
|
@ -87,7 +87,7 @@ const RevealedCard: Component<{ msg: StreamMessageType }> = (props) => {
|
|||
return (
|
||||
<div class="bg-gray-100 rounded-lg border border-gray-200 opacity-50 px-2.5 py-1.5">
|
||||
<span class="text-xs text-gray-400 line-through">
|
||||
{props.msg.sender} — reverted
|
||||
{props.msg.sender} — 已撤回
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ export const StreamView: Component = () => {
|
|||
<Show when={messages().length === 0}>
|
||||
<p class="text-center text-gray-400 text-xs py-8">
|
||||
{stream.connected
|
||||
? "No messages yet. Start by posting something."
|
||||
: "Connect to a session to see messages."}
|
||||
? "暂无消息,发送点什么开始吧。"
|
||||
: "连接会话以查看消息。"}
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export type ChatPayload = z.infer<typeof schema>;
|
|||
|
||||
registerMessageType<ChatPayload>({
|
||||
type: "chat",
|
||||
label: "Chat",
|
||||
label: "聊天",
|
||||
emitters: ["gm", "player"],
|
||||
schema,
|
||||
defaultPayload: () => ({ text: "" }),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export type IntentPayload = z.infer<typeof intentSchema>;
|
|||
|
||||
registerMessageType<IntentPayload>({
|
||||
type: "intent",
|
||||
label: "Declare Intent",
|
||||
label: "声明意图",
|
||||
emitters: ["player"],
|
||||
schema: intentSchema,
|
||||
defaultPayload: () => ({ description: "" }),
|
||||
|
|
@ -26,7 +26,7 @@ registerMessageType<IntentPayload>({
|
|||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">🙋</span>
|
||||
<span class="text-sm font-medium text-yellow-700">Intent</span>
|
||||
<span class="text-sm font-medium text-yellow-700">意图</span>
|
||||
</div>
|
||||
<p class="text-sm">{p.description}</p>
|
||||
{p.context && Object.keys(p.context).length > 0 && (
|
||||
|
|
@ -52,14 +52,14 @@ export type ResolutionPayload = z.infer<typeof resolutionSchema>;
|
|||
|
||||
registerMessageType<ResolutionPayload>({
|
||||
type: "resolution",
|
||||
label: "Resolve Intent",
|
||||
label: "裁决意图",
|
||||
emitters: ["gm"],
|
||||
schema: resolutionSchema,
|
||||
render: (p) => (
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">✅</span>
|
||||
<span class="text-sm font-medium text-green-700">Resolution</span>
|
||||
<span class="text-sm font-medium text-green-700">裁决</span>
|
||||
<span class="text-xs text-gray-400">re: {p.intentId}</span>
|
||||
</div>
|
||||
<p class="text-sm">{p.outcome}</p>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const RevealLink: Component<LinkPayload> = (p) => {
|
|||
|
||||
registerMessageType<LinkPayload>({
|
||||
type: "link",
|
||||
label: "Link Article",
|
||||
label: "链接文章",
|
||||
emitters: ["gm"],
|
||||
schema,
|
||||
defaultPayload: () => ({ path: "/" }),
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export function resolveRollPayload(raw: {
|
|||
|
||||
registerMessageType<RollPayload>({
|
||||
type: "roll",
|
||||
label: "Roll Dice",
|
||||
label: "掷骰",
|
||||
emitters: ["gm"],
|
||||
schema,
|
||||
defaultPayload: () => ({
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ export async function resolveSparkPayload(raw: {
|
|||
|
||||
registerMessageType<SparkPayload>({
|
||||
type: "spark",
|
||||
label: "Spark Table",
|
||||
label: "种子表",
|
||||
emitters: ["gm"],
|
||||
schema,
|
||||
defaultPayload: () => ({
|
||||
|
|
|
|||
Loading…
Reference in New Issue