@zhin.js/plugin-idiom-chain 1.1.15 → 1.1.16

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/lib/view.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createMessages } from 'zhin.js/i18n';
1
2
  import { buildChoiceKeyboard } from '@zhin.js/game-kit';
2
3
  import { CHAIN_PREFIX, getGloss, idiomCount, modeLabel, promptLine } from './engine.js';
3
4
  const MAX_WRONG = 3;
@@ -8,17 +9,17 @@ export function buildChainView(session, eventLines = [], channelType) {
8
9
  const terminal = session.status !== 'active';
9
10
  const mode = sessionMode(session);
10
11
  const lines = [
11
- `📜 **成语接龙** · ${modeLabel(mode)}`,
12
+ messages(session.locale ?? undefined, 'text1', { p0: modeLabel(mode, session.locale ?? undefined) }),
12
13
  '',
13
- `词库 **${idiomCount()}** 条 · 局分 ${session.player_score}:${session.bot_score} · 连击 **${session.streak}**(最佳 ${session.best_streak})`,
14
+ messages(session.locale ?? undefined, 'text2', { p0: idiomCount(), p1: session.player_score, p2: session.bot_score, p3: session.streak, p4: session.best_streak }),
14
15
  ];
15
16
  if (session.last_idiom) {
16
17
  const gloss = getGloss(session.last_idiom);
17
- lines.push('', `上一句:**${session.last_idiom}**${gloss ? `(${gloss})` : ''}`);
18
- lines.push(promptLine(session.last_idiom, mode));
18
+ lines.push('', messages(session.locale ?? undefined, 'text3', { p0: session.last_idiom }) + (gloss ? `(${gloss})` : ''));
19
+ lines.push(promptLine(session.last_idiom, mode, session.locale ?? undefined));
19
20
  }
20
21
  if (session.wrong_count > 0 && session.status === 'active') {
21
- lines.push(`⚠️ 失误 ${session.wrong_count}/${MAX_WRONG}`);
22
+ lines.push(messages(session.locale ?? undefined, 'text4', { p0: session.wrong_count, p1: MAX_WRONG }));
22
23
  }
23
24
  if (eventLines.length) {
24
25
  lines.push('', ...eventLines);
@@ -26,19 +27,19 @@ export function buildChainView(session, eventLines = [], channelType) {
26
27
  if (terminal) {
27
28
  lines.push('');
28
29
  if (session.status === 'won')
29
- lines.push('🏆 **你赢了本局!** 机器人接不上啦。');
30
+ lines.push(messages(session.locale ?? undefined, 'text5'));
30
31
  else if (session.status === 'lost')
31
- lines.push('💀 **机器人获胜。** 发送「接龙 开始」再来。');
32
+ lines.push(messages(session.locale ?? undefined, 'text6'));
32
33
  }
33
34
  else {
34
- lines.push('', '直接回复四字成语,或点下方按钮:');
35
+ lines.push('', messages(session.locale ?? undefined, 'text7'));
35
36
  }
36
37
  const choices = terminal
37
- ? [{ id: 'restart', label: '🔄 再来一局', style: 'primary', keepEnabledWhenTerminal: true }]
38
+ ? [{ id: 'restart', label: messages(session.locale ?? undefined, 'text8'), style: 'primary', keepEnabledWhenTerminal: true }]
38
39
  : [
39
- { id: 'hint', label: '💡 提示', style: 'secondary' },
40
- { id: 'skip', label: '⏭️ 跳过', style: 'secondary' },
41
- { id: 'quit', label: '🏳️ 认输', style: 'danger' },
40
+ { id: 'hint', label: messages(session.locale ?? undefined, 'text9'), style: 'secondary' },
41
+ { id: 'skip', label: messages(session.locale ?? undefined, 'text10'), style: 'secondary' },
42
+ { id: 'quit', label: messages(session.locale ?? undefined, 'text11'), style: 'danger' },
42
43
  ];
43
44
  return buildChoiceKeyboard({
44
45
  gamePrefix: CHAIN_PREFIX,
@@ -47,10 +48,24 @@ export function buildChainView(session, eventLines = [], channelType) {
47
48
  choices,
48
49
  terminal,
49
50
  buttonsPerRow: 3,
50
- fallbackHint: '回复成语,或 1提示 2跳过 3认输',
51
+ fallbackHint: messages(session.locale ?? undefined, 'text12'),
51
52
  interactionProfile: terminal ? 'terminal' : 'gameplay',
52
53
  channelType,
53
54
  });
54
55
  }
55
56
  export { MAX_WRONG };
57
+ const messages = createMessages({
58
+ text1: { 'zh-CN': "📜 **成语接龙** · {p0}", en: "📜 **Chinese Idiom Chain** · {p0}" },
59
+ text2: { 'zh-CN': "词库 **{p0}** 条 · 局分 {p1}:{p2} · 连击 **{p3}**(最佳 {p4})", en: "Dictionary **{p0}** entries · Score {p1}:{p2} · Streak **{p3}** (best {p4})" },
60
+ text3: { 'zh-CN': "上一句:**{p0}**", en: "Last idiom: **{p0}**" },
61
+ text4: { 'zh-CN': "⚠️ 失误 {p0}/{p1}", en: "⚠️ Mistakes: {p0}/{p1}" },
62
+ text5: { 'zh-CN': "🏆 **你赢了本局!** 机器人接不上啦。", en: "🏆 **You won!** The bot has no next idiom." },
63
+ text6: { 'zh-CN': "💀 **机器人获胜。** 点击「再来一局」重新开始。", en: "💀 **The bot won.** Choose “Play again” to start again." },
64
+ text7: { 'zh-CN': "直接回复四字成语,或点下方按钮:", en: "Reply with a four-character Chinese idiom, or choose an option:" },
65
+ text8: { 'zh-CN': "🔄 再来一局", en: "🔄 Play again" },
66
+ text9: { 'zh-CN': "💡 提示", en: "💡 Hint" },
67
+ text10: { 'zh-CN': "⏭️ 跳过", en: "⏭️ Skip" },
68
+ text11: { 'zh-CN': "🏳️ 认输", en: "🏳️ Concede" },
69
+ text12: { 'zh-CN': "回复成语,或 1提示 2跳过 3认输", en: "Reply with an idiom, or 1 Hint · 2 Skip · 3 Concede" },
70
+ });
56
71
  //# sourceMappingURL=view.js.map
package/lib/view.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,20 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { defineGameCommandAliasMiddleware, isChainAction, messageFromCommandInput, normalizeChainAction, } from '@zhin.js/game-kit';
3
- import { CHAIN_HELP, runChainCommand } from "../../lib/chain-command.js";
3
+ import { getChainHelp, runChainCommand } from "../../lib/chain-command.js";
4
4
  import { resolveGameServices } from "../../lib/runtime-store.js";
5
5
  export default defineGameCommandAliasMiddleware({
6
6
  aliases: ['接龙', 'chain'],
7
7
  async run(action, input, context) {
8
+ const message = messageFromCommandInput(input, {
9
+ formatCommand: (path) => context.input.formatCommand?.(path, context.owner.id),
10
+ });
8
11
  const normalized = normalizeChainAction(String(action ?? ''));
9
12
  if (normalized === 'help')
10
- return CHAIN_HELP;
13
+ return getChainHelp(message);
11
14
  // action 无法识别:放行给后续中间件,避免劫持普通聊天
12
15
  if (!isChainAction(normalized))
13
16
  return null;
14
17
  const services = resolveGameServices(context);
15
- const message = messageFromCommandInput(input);
16
18
  return runChainCommand(services, message, normalized);
17
19
  },
18
20
  });
@@ -4,18 +4,20 @@ import {
4
4
  messageFromCommandInput,
5
5
  normalizeChainAction,
6
6
  } from '@zhin.js/game-kit';
7
- import { CHAIN_HELP, runChainCommand } from '../../src/chain-command.js';
7
+ import { getChainHelp, runChainCommand } from '../../src/chain-command.js';
8
8
  import { resolveGameServices } from '../../src/runtime-store.js';
9
9
 
10
10
  export default defineGameCommandAliasMiddleware({
11
11
  aliases: ['接龙', 'chain'],
12
12
  async run(action, input, context) {
13
+ const message = messageFromCommandInput(input, {
14
+ formatCommand: (path) => context.input.formatCommand?.(path, context.owner.id),
15
+ });
13
16
  const normalized = normalizeChainAction(String(action ?? ''));
14
- if (normalized === 'help') return CHAIN_HELP;
17
+ if (normalized === 'help') return getChainHelp(message);
15
18
  // action 无法识别:放行给后续中间件,避免劫持普通聊天
16
19
  if (!isChainAction(normalized)) return null;
17
20
  const services = resolveGameServices(context);
18
- const message = messageFromCommandInput(input);
19
21
  return runChainCommand(services, message, normalized);
20
22
  },
21
23
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-idiom-chain",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Zhin.js 成语接龙 — 四字成语文字接龙 (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -24,13 +24,13 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "chinese-idiom-chengyu": "^1.1.0",
27
- "@zhin.js/core": "1.1.50",
28
- "@zhin.js/game-kit": "1.1.15"
27
+ "@zhin.js/core": "1.1.51",
28
+ "@zhin.js/game-kit": "1.1.16"
29
29
  },
30
30
  "devDependencies": {
31
31
  "typescript": "^6.0.3",
32
32
  "vitest": "^4.1.10",
33
- "zhin.js": "1.1.15"
33
+ "zhin.js": "1.1.16"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
@@ -67,9 +67,9 @@
67
67
  "plugins": []
68
68
  },
69
69
  "peerDependencies": {
70
- "@zhin.js/command": "^1.1.10",
71
- "@zhin.js/middleware": "^1.1.9",
72
- "zhin.js": "^1.1.15"
70
+ "@zhin.js/command": "^1.1.11",
71
+ "@zhin.js/middleware": "^1.1.10",
72
+ "zhin.js": "^1.1.16"
73
73
  },
74
74
  "peerDependenciesMeta": {
75
75
  "@zhin.js/command": {
package/plugin.js CHANGED
@@ -15,6 +15,10 @@ export default defineGamePlugin({
15
15
  displayName: 'Idiom Chain',
16
16
  },
17
17
  game: {
18
+ commandPath: 'chain',
19
+ titleI18n: { en: 'Chinese Idiom Chain' },
20
+ descriptionI18n: { en: 'Connect Chinese idioms by sound or character' },
21
+ quickStartI18n: { en: 'start_pinyin' },
18
22
  id: 'chain',
19
23
  title: '成语接龙',
20
24
  icon: '📜',
@@ -1,25 +1,30 @@
1
- import {
2
- channelKey,
3
- type GameMessageLike,
4
- type GameReply,
5
- } from '@zhin.js/game-kit';
1
+ import { formatGameHelp, gameCommandFeedback, gameCommandHint, channelKey, type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
2
+ import { createMessages } from 'zhin.js/i18n';
6
3
  import { continueGame, startGame } from './game-flow.js';
7
4
  import { idiomCount, modeLabel, promptLine, type MatchMode } from './engine.js';
8
5
 
9
6
  import type { SessionService } from './session-service.js';
10
7
 
11
- export const CHAIN_HELP = [
12
- '📜 成语接龙(文字接龙 · 四字成语)',
13
- `开源词库 ${idiomCount()} 条 · 机器人先出第一句`,
14
- '接龙 / chain — 帮助',
15
- '接龙 开始 / 同音 — 同音接龙(默认,首字同音即可)',
16
- '接龙 同字 — 严格同字接龙',
17
- '接龙 继续 — 刷新界面',
18
- '接龙 放弃 — 结束对局',
19
- '',
20
- '进行中:直接回复四字成语',
21
- '按钮:提示 · 跳过 · 认输(失误 3 次判负)',
22
- ].join('\n');
8
+
9
+ const messages = createMessages({
10
+ dictionary: { 'zh-CN': '开源词库 {count} 条 · 机器人先出第一句', en: 'Dictionary: {count} entries · The bot plays first' },
11
+ pinyin: { 'zh-CN': '{command} — 同音接龙', en: '{command} — Match the pronunciation' },
12
+ char: { 'zh-CN': '{command} — 严格同字接龙', en: '{command} — Match the character' },
13
+ instructions: { 'zh-CN': '进行中直接回复四字成语。按钮:提示 · 跳过 · 认输(失误 3 次判负)。', en: 'Reply with four-character Chinese idioms. Options: Hint · Skip · Concede. Three mistakes lose the game.' },
14
+ active: { 'zh-CN': '进行中:{player} · {mode} · 局分 {playerScore}:{botScore} · 连击 {streak}', en: 'Playing: {player} · {mode} · Score {playerScore}:{botScore} · Streak {streak}' },
15
+ quit: { 'zh-CN': '已放弃成语接龙。', en: 'You quit the idiom chain.' },
16
+ });
17
+
18
+ export function getChainHelp(message: Pick<GameMessageLike, 'locale' | 'formatCommand'> = {}): string {
19
+ const locale = message.locale ?? 'zh-CN';
20
+ const command = message.formatCommand?.('chain');
21
+ return [formatGameHelp(message, { 'zh-CN': '📜 成语接龙(文字接龙 · 四字成语)', en: '📜 Chinese Idiom Chain' }, 'chain', ['continue', 'quit']),
22
+ messages(locale, 'dictionary', { count: idiomCount() }),
23
+ ...(command ? [messages(locale, 'pinyin', { command: command + ' start_pinyin' }), messages(locale, 'char', { command: command + ' start_char' })] : []),
24
+ messages(locale, 'instructions'),
25
+ ].join('\n');
26
+ }
27
+ export const CHAIN_HELP = getChainHelp();
23
28
 
24
29
  export async function runChainCommand(
25
30
  services: SessionService,
@@ -31,15 +36,15 @@ export async function runChainCommand(
31
36
 
32
37
  if (!action || action === 'help') {
33
38
  const active = await services.getActiveByChannel(ch);
34
- const lines = [CHAIN_HELP, ''];
39
+ const lines = [getChainHelp(message), ''];
35
40
  if (active) {
36
41
  const mode: MatchMode = active.match_mode === 'char' ? 'char' : 'pinyin';
37
42
  lines.push(
38
- `进行中:${active.player_name} · ${modeLabel(mode)} · 局分 ${active.player_score}:${active.bot_score} · 连击 ${active.streak}`,
43
+ messages(active.locale ?? undefined, 'active', { player: active.player_name, mode: modeLabel(mode, active.locale ?? undefined), playerScore: active.player_score, botScore: active.bot_score, streak: active.streak }),
39
44
  );
40
- if (active.last_idiom) lines.push(promptLine(active.last_idiom, mode));
45
+ if (active.last_idiom) lines.push(promptLine(active.last_idiom, mode, active.locale ?? undefined));
41
46
  } else {
42
- lines.push('暂无对局,发送「接龙 开始」或「接龙 同字」。');
47
+ lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'chain', 'start_pinyin'));
43
48
  }
44
49
  return lines.join('\n');
45
50
  }
@@ -50,10 +55,10 @@ export async function runChainCommand(
50
55
 
51
56
  if (action === 'quit') {
52
57
  const row = await services.getActiveForUser(ch, userId);
53
- if (!row) return '你没有进行中的接龙。';
58
+ if (!row) return gameCommandFeedback(message, 'noGame');
54
59
  await services.updateSession(row.id, { status: 'aborted' });
55
- return '已放弃成语接龙。';
60
+ return messages(row.locale ?? undefined, 'quit');
56
61
  }
57
62
 
58
- return `未知子命令:${action}\n\n${CHAIN_HELP}`;
63
+ return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getChainHelp(message);
59
64
  }
package/src/game-flow.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { createMessages, resolveLocale } from 'zhin.js/i18n';
1
2
  import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
3
  import {
3
4
  CHAIN_PREFIX,
@@ -35,6 +36,7 @@ async function botTurn(
35
36
  session: ChainSessionRow,
36
37
  used: Set<string>,
37
38
  ): Promise<{ session: ChainSessionRow; lines: string[]; playerWon: boolean }> {
39
+ const locale = resolveLocale(session.locale);
38
40
  const mode = sessionMode(session);
39
41
  const bot = pickBotIdiom(session.last_idiom, mode, used);
40
42
  if (!bot) {
@@ -43,7 +45,7 @@ async function botTurn(
43
45
  player_score: session.player_score + 1,
44
46
  });
45
47
  const updated = (await services.getById(session.id))!;
46
- return { session: updated, lines: ['🎉 机器人接不上,你赢本局!'], playerWon: true };
48
+ return { session: updated, lines: [messages(locale, 'text1')], playerWon: true };
47
49
  }
48
50
 
49
51
  used.add(bot.text);
@@ -57,8 +59,8 @@ async function botTurn(
57
59
  });
58
60
  const updated = (await services.getById(session.id))!;
59
61
  const lines = [
60
- `🤖 机器人:${bot.text}${gloss ? `(${gloss})` : ''}`,
61
- promptLine(bot.text, mode),
62
+ messages(locale, 'text2', { p0: bot.text }) + (gloss ? `(${gloss})` : ''),
63
+ promptLine(bot.text, mode, locale),
62
64
  ];
63
65
  return { session: updated, lines, playerWon: false };
64
66
  }
@@ -70,11 +72,12 @@ export async function startGame(
70
72
  ): Promise<GameReply | undefined> {
71
73
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
72
74
  const active = await services.getActiveByChannel(ch);
75
+ const locale = resolveLocale(active ? active.locale : message.locale);
73
76
  if (active) {
74
77
  if (active.player_id === message.$sender.id) {
75
- return '你已有进行中的接龙,发送「接龙 继续」刷新。';
78
+ return messages(locale, 'text3');
76
79
  }
77
- return `本频道 ${active.player_name} 正在成语接龙。`;
80
+ return messages(locale, 'text4', { p0: active.player_name });
78
81
  }
79
82
 
80
83
  const used = new Set<string>();
@@ -89,8 +92,8 @@ export async function startGame(
89
92
 
90
93
  const gloss = starter.gloss ?? getGloss(starter.text);
91
94
  return sendOrEditView(services, message, session, [
92
- `🎬 ${modeLabel(matchMode)}开局!我先出:**${starter.text}**${gloss ? `(${gloss})` : ''}`,
93
- promptLine(starter.text, matchMode),
95
+ messages(locale, 'text5', { p0: modeLabel(matchMode, locale), p1: starter.text }) + (gloss ? `(${gloss})` : ''),
96
+ promptLine(starter.text, matchMode, locale),
94
97
  ]);
95
98
  }
96
99
 
@@ -102,7 +105,8 @@ export async function continueGame(
102
105
  `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
103
106
  message.$sender.id,
104
107
  );
105
- if (!session) return '你没有进行中的接龙,发送「接龙 开始」。';
108
+ const locale = resolveLocale(session ? session.locale : message.locale);
109
+ if (!session) return messages(locale, 'text6');
106
110
  return sendOrEditView(services, message, session);
107
111
  }
108
112
 
@@ -113,6 +117,7 @@ export async function processIdiomText(
113
117
  ): Promise<GameReply | null> {
114
118
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
115
119
  const session = await services.getActiveForUser(ch, message.$sender.id);
120
+ const locale = resolveLocale(session ? session.locale : message.locale);
116
121
  if (!session || session.status !== 'active') return null;
117
122
 
118
123
  const mode = sessionMode(session);
@@ -120,9 +125,9 @@ export async function processIdiomText(
120
125
  const idiom = normalizeInput(raw);
121
126
  // 格式不合规(非四字)只提示、不扣失误:避免闲聊(「好的」「谢谢」)被累计判负
122
127
  if (!/^[\u4e00-\u9fff]{4}$/.test(idiom)) {
123
- return '接龙请输入**四字成语**(这条不算失误)。';
128
+ return messages(locale, 'text7');
124
129
  }
125
- const check = validatePlayerIdiom(idiom, session.last_idiom, mode, used);
130
+ const check = validatePlayerIdiom(idiom, session.last_idiom, mode, used, locale);
126
131
  if (!check.ok) {
127
132
  const wrong = session.wrong_count + 1;
128
133
  if (wrong >= MAX_WRONG) {
@@ -135,7 +140,7 @@ export async function processIdiomText(
135
140
  const updated = (await services.getById(session.id))!;
136
141
  return (await sendOrEditView(services, message, updated, [
137
142
  check.reason!,
138
- '失误过多,本局结束。',
143
+ messages(locale, 'text8'),
139
144
  ])) ?? null;
140
145
  }
141
146
  await services.updateSession(session.id, { wrong_count: wrong, streak: 0 });
@@ -159,7 +164,7 @@ export async function processIdiomText(
159
164
  });
160
165
 
161
166
  const current = (await services.getById(session.id))!;
162
- const userLine = `✅ 你:${idiom}${gloss ? `(${gloss})` : ''}`;
167
+ const userLine = messages(locale, 'text9', { p0: idiom }) + (gloss ? `(${gloss})` : '');
163
168
 
164
169
  const botResult = await botTurn(services, current, used);
165
170
  return (await sendOrEditView(services, message, botResult.session, [
@@ -175,16 +180,19 @@ export async function handleChoice(
175
180
  choiceId: string,
176
181
  ): Promise<GameReply | null> {
177
182
  const session = await services.getById(sessionId);
178
- if (!session) return '对局不存在。';
179
- if (session.player_id !== message.$sender.id) return '这是别人的接龙。';
183
+ const locale = resolveLocale(session ? session.locale : message.locale);
184
+ if (!session) return messages(locale, 'text10');
185
+ if (session.player_id !== message.$sender.id) return messages(locale, 'text11');
180
186
 
181
187
  const mode = sessionMode(session);
182
188
 
183
189
  if (choiceId === 'restart') {
190
+ const locale = resolveLocale(message.locale);
184
191
  const used = new Set<string>();
185
192
  const starter = pickStarterIdiom(used);
186
193
  used.add(starter.text);
187
194
  await services.updateSession(session.id, {
195
+ locale,
188
196
  last_idiom: starter.text,
189
197
  next_char: lastChar(starter.text),
190
198
  used_idioms: JSON.stringify([...used]),
@@ -196,13 +204,13 @@ export async function handleChoice(
196
204
  const updated = (await services.getById(session.id))!;
197
205
  const gloss = starter.gloss ?? getGloss(starter.text);
198
206
  return (await sendOrEditView(services, message, updated, [
199
- `🎬 新一局!我先出:**${starter.text}**${gloss ? `(${gloss})` : ''}`,
200
- promptLine(starter.text, mode),
207
+ messages(locale, 'text12', { p0: starter.text }) + (gloss ? `(${gloss})` : ''),
208
+ promptLine(starter.text, mode, locale),
201
209
  ])) ?? null;
202
210
  }
203
211
 
204
212
  if (session.status !== 'active') {
205
- return '本局已结束,请点击再来一局。';
213
+ return messages(locale, 'text13');
206
214
  }
207
215
 
208
216
  const used = parseUsed(session.used_idioms);
@@ -214,15 +222,15 @@ export async function handleChoice(
214
222
  await services.updateSession(session.id, { status: 'won', player_score: session.player_score + 1 });
215
223
  const updated = (await services.getById(session.id))!;
216
224
  return (await sendOrEditView(services, message, updated, [
217
- '💡 词库中暂无可用提示,你赢了!',
225
+ messages(locale, 'text14'),
218
226
  ])) ?? null;
219
227
  }
220
228
  const gloss = getGloss(hint);
221
229
  await services.updateSession(session.id, { hints_used: session.hints_used + 1, streak: 0 });
222
230
  const updated = (await services.getById(session.id))!;
223
231
  return (await sendOrEditView(services, message, updated, [
224
- `💡 提示:可试 **${hint}**${gloss ? `(${gloss})` : ''}`,
225
- '(使用提示会清零连击)',
232
+ messages(locale, 'text15', { p0: hint }) + (gloss ? `(${gloss})` : ''),
233
+ messages(locale, 'text16'),
226
234
  ])) ?? null;
227
235
  }
228
236
 
@@ -234,17 +242,39 @@ export async function handleChoice(
234
242
  });
235
243
  const updated = (await services.getById(session.id))!;
236
244
  return (await sendOrEditView(services, message, updated, [
237
- '⏭️ 你选择跳过,机器人得一分。',
245
+ messages(locale, 'text17'),
238
246
  ])) ?? null;
239
247
  }
240
248
 
241
249
  if (choiceId === 'quit') {
242
250
  await services.updateSession(session.id, { status: 'lost', bot_score: session.bot_score + 1 });
243
251
  const updated = (await services.getById(session.id))!;
244
- return (await sendOrEditView(services, message, updated, ['🏳️ 你认输了。'])) ?? null;
252
+ return (await sendOrEditView(services, message, updated, [messages(locale, 'text18')])) ?? null;
245
253
  }
246
254
 
247
- return '未知操作。';
255
+ return messages(locale, 'text19');
248
256
  }
249
257
 
250
258
  export { CHAIN_PREFIX };
259
+
260
+ const messages = createMessages({
261
+ text1: { 'zh-CN': "🎉 机器人接不上,你赢本局!", en: "🎉 The bot has no next idiom. You win!" },
262
+ text2: { 'zh-CN': "🤖 机器人:{p0}", en: "🤖 Bot: {p0}" },
263
+ text3: { 'zh-CN': "你已有进行中的接龙,请使用当前对局的操作入口继续。", en: "You already have an active idiom chain. Continue using its controls." },
264
+ text4: { 'zh-CN': "本频道 {p0} 正在成语接龙。", en: "{p0} is playing an idiom chain in this channel." },
265
+ text5: { 'zh-CN': "🎬 {p0}开局!我先出:**{p1}**", en: "🎬 Starting {p0}! My first idiom: **{p1}**" },
266
+ text6: { 'zh-CN': "你没有进行中的接龙。", en: "You have no active idiom chain." },
267
+ text7: { 'zh-CN': "接龙请输入**四字成语**(这条不算失误)。", en: "Enter a **four-character Chinese idiom** (this does not count as a mistake)." },
268
+ text8: { 'zh-CN': "失误过多,本局结束。", en: "Too many mistakes. This game has ended." },
269
+ text9: { 'zh-CN': "✅ 你:{p0}", en: "✅ You: {p0}" },
270
+ text10: { 'zh-CN': "对局不存在。", en: "This game no longer exists." },
271
+ text11: { 'zh-CN': "这是别人的接龙。", en: "This idiom chain belongs to another player." },
272
+ text12: { 'zh-CN': "🎬 新一局!我先出:**{p0}**", en: "🎬 New game! My first idiom: **{p0}**" },
273
+ text13: { 'zh-CN': "本局已结束,请点击再来一局。", en: "This game has ended. Choose Play again." },
274
+ text14: { 'zh-CN': "💡 词库中暂无可用提示,你赢了!", en: "💡 No next idiom is available in the dictionary. You win!" },
275
+ text15: { 'zh-CN': "💡 提示:可试 **{p0}**", en: "💡 Hint: try **{p0}**" },
276
+ text16: { 'zh-CN': "(使用提示会清零连击)", en: "(Using a hint resets your streak)" },
277
+ text17: { 'zh-CN': "⏭️ 你选择跳过,机器人得一分。", en: "⏭️ You skipped. The bot gets a point." },
278
+ text18: { 'zh-CN': "🏳️ 你认输了。", en: "🏳️ You conceded." },
279
+ text19: { 'zh-CN': "未知操作。", en: "Unknown action." },
280
+ });
@@ -1,3 +1,4 @@
1
+ import { createMessages, type Locale } from 'zhin.js/i18n';
1
2
  /**
2
3
  * 成语词库适配层 — 基于 npm `chinese-idiom-chengyu`(MIT,~3 万成语)
3
4
  * @see https://www.npmjs.com/package/chinese-idiom-chengyu
@@ -74,8 +75,8 @@ export function lastSyllableNoTone(idiom: string): string {
74
75
  return Util.pinyinToLetters(tail);
75
76
  }
76
77
 
77
- export function modeLabel(mode: MatchMode): string {
78
- return mode === 'char' ? '同字接龙' : '同音接龙';
78
+ export function modeLabel(mode: MatchMode, locale?: Locale): string {
79
+ return mode === 'char' ? messages(locale, 'text1') : messages(locale, 'text2');
79
80
  }
80
81
 
81
82
  export function getValidNext(
@@ -129,16 +130,17 @@ export function validatePlayerIdiom(
129
130
  prevIdiom: string,
130
131
  mode: MatchMode,
131
132
  used: Set<string>,
133
+ locale?: Locale,
132
134
  ): ValidateResult {
133
135
  const idiom = normalizeInput(text);
134
136
  if (!FOUR_CHAR.test(idiom)) {
135
- return { ok: false, reason: '请输入**四字成语**(不要空格或标点)。' };
137
+ return { ok: false, reason: messages(locale, 'text3') };
136
138
  }
137
139
  if (!isKnownIdiom(idiom)) {
138
- return { ok: false, reason: '开源词库中未收录该成语,请换一个常见四字成语。' };
140
+ return { ok: false, reason: messages(locale, 'text4') };
139
141
  }
140
142
  if (used.has(idiom)) {
141
- return { ok: false, reason: '这个成语本局已经用过了。' };
143
+ return { ok: false, reason: messages(locale, 'text5') };
142
144
  }
143
145
 
144
146
  const valid = getValidNext(prevIdiom, mode, used);
@@ -146,21 +148,33 @@ export function validatePlayerIdiom(
146
148
 
147
149
  const tail = lastChar(prevIdiom);
148
150
  if (mode === 'char') {
149
- return { ok: false, reason: `必须以「**${tail}**」开头(上一句尾字)。` };
151
+ return { ok: false, reason: messages(locale, 'text6', { p0: tail }) };
150
152
  }
151
153
 
152
154
  const py = lastSyllableNoTone(prevIdiom);
153
155
  return {
154
156
  ok: false,
155
- reason: `首字需**同音**(${py}),如「${tail}」音;也可同字「${tail}」开头。`,
157
+ reason: messages(locale, 'text7', { p0: py, p1: tail, p2: tail }),
156
158
  };
157
159
  }
158
160
 
159
- export function promptLine(prevIdiom: string, mode: MatchMode): string {
161
+ export function promptLine(prevIdiom: string, mode: MatchMode, locale?: Locale): string {
160
162
  const tail = lastChar(prevIdiom);
161
163
  if (mode === 'char') {
162
- return `请接「**${tail}**」字开头的四字成语`;
164
+ return messages(locale, 'text8', { p0: tail });
163
165
  }
164
166
  const py = lastSyllableNoTone(prevIdiom);
165
- return `请接**同音**「${py}」(尾字「${tail}」)开头的四字成语`;
166
- }
167
+ return messages(locale, 'text9', { p0: py, p1: tail });
168
+ }
169
+
170
+ const messages = createMessages({
171
+ text1: { 'zh-CN': "同字接龙", en: "Matching character" },
172
+ text2: { 'zh-CN': "同音接龙", en: "Matching pronunciation" },
173
+ text3: { 'zh-CN': "请输入**四字成语**(不要空格或标点)。", en: "Enter a **four-character Chinese idiom** without spaces or punctuation." },
174
+ text4: { 'zh-CN': "开源词库中未收录该成语,请换一个常见四字成语。", en: "This idiom is not in the dictionary. Try a common four-character idiom." },
175
+ text5: { 'zh-CN': "这个成语本局已经用过了。", en: "This idiom has already been used in this game." },
176
+ text6: { 'zh-CN': "必须以「**{p0}**」开头(上一句尾字)。", en: "The idiom must start with **{p0}**, the last character of the previous idiom." },
177
+ text7: { 'zh-CN': "首字需**同音**({p0}),如「{p1}」音;也可同字「{p2}」开头。", en: "The first character must have the **same pronunciation** ({p0}) as “{p1}”, or be the character “{p2}”." },
178
+ text8: { 'zh-CN': "请接「**{p0}**」字开头的四字成语", en: "Enter a four-character idiom starting with **{p0}**" },
179
+ text9: { 'zh-CN': "请接**同音**「{p0}」(尾字「{p1}」)开头的四字成语", en: "Enter a four-character idiom whose first character sounds like **{p0}** (last character: “{p1}”)" },
180
+ });
package/src/models.ts CHANGED
@@ -7,6 +7,7 @@ declare module '@zhin.js/core' {
7
7
  interface Models {
8
8
  idiom_chain_sessions: {
9
9
  id: string;
10
+ locale?: 'zh-CN' | 'en' | null;
10
11
  adapter: string;
11
12
  endpoint: string;
12
13
  channel_type: string;
@@ -36,10 +37,11 @@ export type ChainSessionRow = Models['idiom_chain_sessions'];
36
37
 
37
38
 
38
39
  export function defineHostTables(
39
- db: { define: (name: string, definition: Record<string, unknown>) => void },
40
+ db: { define: (name: string, definition: Record<string, unknown>, options?: { readonly addColumns?: readonly string[] }) => void },
40
41
  ): void {
41
42
  db.define('idiom_chain_sessions', {
42
43
  id: { type: 'text', primary: true },
44
+ locale: { type: 'text', nullable: true },
43
45
  adapter: { type: 'text', nullable: false },
44
46
  endpoint: { type: 'text', nullable: false },
45
47
  channel_type: { type: 'text', nullable: false },
@@ -61,5 +63,5 @@ export function defineHostTables(
61
63
  status: { type: 'text', default: 'active' },
62
64
  updated_at: { type: 'bigint', default: 0 },
63
65
  created_at: { type: 'bigint', default: 0 },
64
- });
66
+ }, { addColumns: ['locale'] });
65
67
  }
@@ -42,6 +42,7 @@ export class SessionService extends BaseSessionService<ChainSessionRow> {
42
42
  const now = Date.now();
43
43
  const row: ChainSessionRow = {
44
44
  id: generateSessionId(),
45
+ locale: message.locale ?? 'zh-CN',
45
46
  adapter: String(message.$adapter),
46
47
  endpoint: message.$endpoint,
47
48
  channel_type: message.$channel.type,