@zhin.js/plugin-group-suite 1.1.14 → 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.
Files changed (97) hide show
  1. package/README.md +12 -0
  2. package/commands/checkin/index.js +3 -3
  3. package/commands/checkin/index.ts +3 -3
  4. package/commands/forget/index.js +3 -3
  5. package/commands/forget/index.ts +3 -3
  6. package/commands/keyword-add/[keyword]/index.js +4 -3
  7. package/commands/keyword-add/[keyword]/index.ts +4 -3
  8. package/commands/keyword-list/index.js +4 -3
  9. package/commands/keyword-list/index.ts +4 -3
  10. package/commands/keyword-remove/[keyword]/index.js +5 -4
  11. package/commands/keyword-remove/[keyword]/index.ts +5 -4
  12. package/commands/mypoints/index.js +3 -3
  13. package/commands/mypoints/index.ts +3 -3
  14. package/commands/mystats/index.js +3 -3
  15. package/commands/mystats/index.ts +3 -3
  16. package/commands/rank/index.js +3 -3
  17. package/commands/rank/index.ts +3 -3
  18. package/commands/stats/index.js +6 -5
  19. package/commands/stats/index.ts +6 -5
  20. package/commands/stats-card/index.js +4 -3
  21. package/commands/stats-card/index.ts +4 -3
  22. package/commands/stats-rank/index.js +6 -5
  23. package/commands/stats-rank/index.ts +6 -5
  24. package/commands/stats-week/index.js +6 -5
  25. package/commands/stats-week/index.ts +6 -5
  26. package/commands/teach/index.js +3 -3
  27. package/commands/teach/index.ts +3 -3
  28. package/commands/teach-list/[[page]]/index.js +3 -3
  29. package/commands/teach-list/[[page]]/index.ts +3 -3
  30. package/commands/teach-regex/index.js +3 -3
  31. package/commands/teach-regex/index.ts +3 -3
  32. package/handlers/welcome/index.js +9 -1
  33. package/handlers/welcome/index.ts +8 -1
  34. package/lib/analysis-card.d.ts +1 -1
  35. package/lib/analysis-card.d.ts.map +1 -1
  36. package/lib/analysis-card.js +12 -11
  37. package/lib/analysis-card.js.map +1 -1
  38. package/lib/analysis.d.ts +2 -2
  39. package/lib/analysis.d.ts.map +1 -1
  40. package/lib/analysis.js +14 -20
  41. package/lib/analysis.js.map +1 -1
  42. package/lib/checkin-lib.d.ts +1 -1
  43. package/lib/checkin-lib.d.ts.map +1 -1
  44. package/lib/checkin-lib.js +23 -22
  45. package/lib/checkin-lib.js.map +1 -1
  46. package/lib/conversation-scope.d.ts +1 -0
  47. package/lib/conversation-scope.d.ts.map +1 -1
  48. package/lib/conversation-scope.js.map +1 -1
  49. package/lib/i18n.d.ts +2 -0
  50. package/lib/i18n.d.ts.map +1 -0
  51. package/lib/i18n.js +480 -0
  52. package/lib/i18n.js.map +1 -0
  53. package/lib/keywords.d.ts +1 -0
  54. package/lib/keywords.d.ts.map +1 -1
  55. package/lib/keywords.js +8 -7
  56. package/lib/keywords.js.map +1 -1
  57. package/lib/shared-runtime.d.ts +1 -0
  58. package/lib/shared-runtime.d.ts.map +1 -1
  59. package/lib/shared-runtime.js +2 -1
  60. package/lib/shared-runtime.js.map +1 -1
  61. package/lib/stats-card.d.ts +2 -2
  62. package/lib/stats-card.d.ts.map +1 -1
  63. package/lib/stats-card.js +15 -14
  64. package/lib/stats-card.js.map +1 -1
  65. package/lib/stats-data.d.ts +3 -2
  66. package/lib/stats-data.d.ts.map +1 -1
  67. package/lib/stats-data.js +13 -12
  68. package/lib/stats-data.js.map +1 -1
  69. package/lib/stats-lib.d.ts.map +1 -1
  70. package/lib/stats-lib.js +8 -7
  71. package/lib/stats-lib.js.map +1 -1
  72. package/lib/teach-lib.d.ts +1 -1
  73. package/lib/teach-lib.d.ts.map +1 -1
  74. package/lib/teach-lib.js +27 -26
  75. package/lib/teach-lib.js.map +1 -1
  76. package/package.json +12 -12
  77. package/skills/checkin/tools/checkin_query/index.js +5 -4
  78. package/skills/checkin/tools/checkin_query/index.ts +5 -4
  79. package/skills/checkin/tools/checkin_rank/index.js +4 -3
  80. package/skills/checkin/tools/checkin_rank/index.ts +4 -3
  81. package/skills/group-daily-analysis/tools/group_announce/index.js +3 -2
  82. package/skills/group-daily-analysis/tools/group_announce/index.ts +3 -2
  83. package/skills/group-daily-analysis/tools/stats_query/index.js +4 -3
  84. package/skills/group-daily-analysis/tools/stats_query/index.ts +4 -3
  85. package/skills/group-daily-analysis/tools/stats_user/index.js +4 -3
  86. package/skills/group-daily-analysis/tools/stats_user/index.ts +4 -3
  87. package/src/analysis-card.ts +13 -11
  88. package/src/analysis.ts +15 -13
  89. package/src/checkin-lib.ts +23 -21
  90. package/src/conversation-scope.ts +1 -0
  91. package/src/i18n.ts +480 -0
  92. package/src/keywords.ts +9 -7
  93. package/src/shared-runtime.ts +3 -1
  94. package/src/stats-card.ts +17 -16
  95. package/src/stats-data.ts +14 -12
  96. package/src/stats-lib.ts +8 -7
  97. package/src/teach-lib.ts +27 -26
@@ -1,3 +1,4 @@
1
+ import { messages as t } from '../../../../src/i18n.js';
1
2
  import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { getCheckinModel } from '../../../../src/db-store.js';
@@ -18,7 +19,7 @@ export default defineAgentTool<{ user_id?: string; group_id?: string }>({
18
19
  async execute({ user_id: userId, group_id: requestedGroupId }, context) {
19
20
  const runtime = context.use(groupSuiteRuntimeToken);
20
21
  const model = getCheckinModel(runtime.db);
21
- if (!model) return '签到数据库尚未就绪';
22
+ if (!model) return t(context.locale, 'checkinDb');
22
23
  const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
23
24
  const query = {
24
25
  ...(userId ? { user_id: userId } : {}),
@@ -26,12 +27,12 @@ export default defineAgentTool<{ user_id?: string; group_id?: string }>({
26
27
  };
27
28
  const rows = await model.select().where(query);
28
29
  if (userId) {
29
- if (rows.length === 0) return `用户 ${userId} 没有签到记录`;
30
+ if (rows.length === 0) return t(context.locale, 'userCheckinEmpty', { user: userId });
30
31
  const user = rows[0]!;
31
- return `${String(user.user_name ?? userId)}: 积分=${Number(user.points ?? 0)}, 累计=${Number(user.total_checkins ?? 0)}天, 连续=${Number(user.streak ?? 0)}天, 最长=${Number(user.max_streak ?? 0)}天, 上次=${String(user.last_checkin ?? '')}`;
32
+ return t(context.locale, 'userCheckinInfo', { name: String(user.user_name ?? userId), points: Number(user.points ?? 0), total: Number(user.total_checkins ?? 0), streak: Number(user.streak ?? 0), max: Number(user.max_streak ?? 0), last: String(user.last_checkin ?? '') });
32
33
  }
33
34
  const totalPoints = rows.reduce((sum, user) => sum + Number(user.points ?? 0), 0);
34
35
  const todayCount = rows.filter((user) => user.last_checkin === todayStr()).length;
35
- return `签到系统统计\n总用户: ${rows.length}\n总积分: ${totalPoints}\n今日签到: ${todayCount}人`;
36
+ return t(context.locale, 'checkinOverview', { users: rows.length, points: totalPoints, today: todayCount });
36
37
  },
37
38
  });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../lib/i18n.js";
2
3
  import { conversationKey, toolGroupConversation } from "../../../../lib/conversation-scope.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { getCheckinModel } from "../../../../lib/db-store.js";
@@ -17,7 +18,7 @@ export default defineAgentTool({
17
18
  async execute({ limit, group_id: requestedGroupId }, context) {
18
19
  const model = getCheckinModel(context.use(groupSuiteRuntimeToken).db);
19
20
  if (!model)
20
- return '签到数据库尚未就绪';
21
+ return t(context.locale, 'checkinDb');
21
22
  const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
22
23
  const rows = await model.select().where({ context_type: 'group', context_id: groupId });
23
24
  const count = Math.min(Math.max(Number(limit) || 10, 1), 50);
@@ -25,7 +26,7 @@ export default defineAgentTool({
25
26
  .sort((left, right) => Number(right.points ?? 0) - Number(left.points ?? 0))
26
27
  .slice(0, count);
27
28
  if (ranked.length === 0)
28
- return '暂无排行数据';
29
- return ranked.map((user, index) => `${index + 1}. ${String(user.user_name ?? user.user_id)} — ${Number(user.points ?? 0)}分 (连续${Number(user.streak ?? 0)}天)`).join('\n');
29
+ return t(context.locale, 'rankEmpty');
30
+ return ranked.map((user, index) => t(context.locale, 'pointsToolRow', { rank: index + 1, name: String(user.user_name ?? user.user_id), points: Number(user.points ?? 0), days: Number(user.streak ?? 0) })).join('\n');
30
31
  },
31
32
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from '../../../../src/i18n.js';
1
2
  import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { getCheckinModel } from '../../../../src/db-store.js';
@@ -16,16 +17,16 @@ export default defineAgentTool<{ limit?: number; group_id?: string }>({
16
17
  scopes: ['group'],
17
18
  async execute({ limit, group_id: requestedGroupId }, context) {
18
19
  const model = getCheckinModel(context.use(groupSuiteRuntimeToken).db);
19
- if (!model) return '签到数据库尚未就绪';
20
+ if (!model) return t(context.locale, 'checkinDb');
20
21
  const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
21
22
  const rows = await model.select().where({ context_type: 'group', context_id: groupId });
22
23
  const count = Math.min(Math.max(Number(limit) || 10, 1), 50);
23
24
  const ranked = [...rows]
24
25
  .sort((left, right) => Number(right.points ?? 0) - Number(left.points ?? 0))
25
26
  .slice(0, count);
26
- if (ranked.length === 0) return '暂无排行数据';
27
+ if (ranked.length === 0) return t(context.locale, 'rankEmpty');
27
28
  return ranked.map((user, index) =>
28
- `${index + 1}. ${String(user.user_name ?? user.user_id)} — ${Number(user.points ?? 0)}分 (连续${Number(user.streak ?? 0)}天)`,
29
+ t(context.locale, 'pointsToolRow', { rank: index + 1, name: String(user.user_name ?? user.user_id), points: Number(user.points ?? 0), days: Number(user.streak ?? 0) }),
29
30
  ).join('\n');
30
31
  },
31
32
  });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../lib/i18n.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  export default defineAgentTool({
4
5
  description: '生成群聊公告或通知消息',
@@ -8,7 +9,7 @@ export default defineAgentTool({
8
9
  required: ['message'],
9
10
  },
10
11
  requiresApproval: 'never',
11
- execute({ message }) {
12
- return `📢 群公告:\n${message}`;
12
+ execute({ message }, context) {
13
+ return t(context.locale, 'announcement', { message });
13
14
  },
14
15
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from '../../../../src/i18n.js';
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
 
3
4
  export default defineAgentTool<{ message: string }>({
@@ -8,7 +9,7 @@ export default defineAgentTool<{ message: string }>({
8
9
  required: ['message'],
9
10
  },
10
11
  requiresApproval: 'never',
11
- execute({ message }) {
12
- return `📢 群公告:\n${message}`;
12
+ execute({ message }, context) {
13
+ return t(context.locale, 'announcement', { message });
13
14
  },
14
15
  });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../lib/i18n.js";
2
3
  import { conversationKey, toolGroupConversation } from "../../../../lib/conversation-scope.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
@@ -24,11 +25,11 @@ export default defineAgentTool({
24
25
  : period === 'month' ? monthStartStr() : todayStr();
25
26
  const stats = await queryStats(groupId, fromDate, runtime);
26
27
  if (stats.size === 0)
27
- return `${period} 暂无消息统计数据`;
28
+ return t(context.locale, 'periodStatsEmpty', { period });
28
29
  const ranked = [...stats.values()].sort((left, right) => right.count - left.count);
29
30
  const total = ranked.reduce((sum, item) => sum + item.count, 0);
30
31
  const lines = ranked.slice(0, 10)
31
- .map((item, index) => `${index + 1}. ${item.name} — ${item.count}条`);
32
- return `消息统计 (${period})\n总消息: ${total}条, 活跃用户: ${stats.size}人\n${lines.join('\n')}`;
32
+ .map((item, index) => t(context.locale, 'statsRow', { prefix: `${index + 1}.`, name: item.name, count: item.count }));
33
+ return t(context.locale, 'statsOverview', { period, total, users: stats.size, lines: lines.join('\n') });
33
34
  },
34
35
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from '../../../../src/i18n.js';
1
2
  import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
@@ -25,11 +26,11 @@ export default defineAgentTool<{ group_id?: string; period?: Period }>({
25
26
  ? weekStartStr()
26
27
  : period === 'month' ? monthStartStr() : todayStr();
27
28
  const stats = await queryStats(groupId, fromDate, runtime);
28
- if (stats.size === 0) return `${period} 暂无消息统计数据`;
29
+ if (stats.size === 0) return t(context.locale, 'periodStatsEmpty', { period });
29
30
  const ranked = [...stats.values()].sort((left, right) => right.count - left.count);
30
31
  const total = ranked.reduce((sum, item) => sum + item.count, 0);
31
32
  const lines = ranked.slice(0, 10)
32
- .map((item, index) => `${index + 1}. ${item.name} — ${item.count}条`);
33
- return `消息统计 (${period})\n总消息: ${total}条, 活跃用户: ${stats.size}人\n${lines.join('\n')}`;
33
+ .map((item, index) => t(context.locale, 'statsRow', { prefix: `${index + 1}.`, name: item.name, count: item.count }));
34
+ return t(context.locale, 'statsOverview', { period, total, users: stats.size, lines: lines.join('\n') });
34
35
  },
35
36
  });
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../../../lib/i18n.js";
2
3
  import { conversationKey, toolGroupConversation } from "../../../../lib/conversation-scope.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { getStatsModel } from "../../../../lib/db-store.js";
@@ -22,14 +23,14 @@ export default defineAgentTool({
22
23
  await flushStatsBuffer(runtime);
23
24
  const model = getStatsModel(runtime.db);
24
25
  if (!model)
25
- return '统计数据库尚未就绪';
26
+ return t(context.locale, 'statsDb');
26
27
  const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
27
28
  const rows = await model.select().where({
28
29
  user_id: userId,
29
30
  group_id: groupId,
30
31
  });
31
32
  if (rows.length === 0)
32
- return `用户 ${userId} 暂无消息记录`;
33
+ return t(context.locale, 'userStatsEmpty', { user: userId });
33
34
  const today = todayStr();
34
35
  const weekStart = weekStartStr();
35
36
  let todayCount = 0;
@@ -44,6 +45,6 @@ export default defineAgentTool({
44
45
  todayCount += count;
45
46
  }
46
47
  const name = String(rows[0].user_name ?? userId);
47
- return `${name} 的统计\n今日: ${todayCount}条\n本周: ${weekCount}条\n总计: ${totalCount}条\n活跃天数: ${rows.length}天`;
48
+ return t(context.locale, 'userStatsInfo', { name, today: todayCount, week: weekCount, total: totalCount, days: rows.length });
48
49
  },
49
50
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from '../../../../src/i18n.js';
1
2
  import { conversationKey, toolGroupConversation } from '../../../../src/conversation-scope.js';
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { getStatsModel } from '../../../../src/db-store.js';
@@ -21,13 +22,13 @@ export default defineAgentTool<{ user_id: string; group_id?: string }>({
21
22
  const runtime = context.use(groupSuiteRuntimeToken);
22
23
  await flushStatsBuffer(runtime);
23
24
  const model = getStatsModel(runtime.db);
24
- if (!model) return '统计数据库尚未就绪';
25
+ if (!model) return t(context.locale, 'statsDb');
25
26
  const groupId = conversationKey(toolGroupConversation(context, requestedGroupId));
26
27
  const rows = await model.select().where({
27
28
  user_id: userId,
28
29
  group_id: groupId,
29
30
  });
30
- if (rows.length === 0) return `用户 ${userId} 暂无消息记录`;
31
+ if (rows.length === 0) return t(context.locale, 'userStatsEmpty', { user: userId });
31
32
  const today = todayStr();
32
33
  const weekStart = weekStartStr();
33
34
  let todayCount = 0;
@@ -40,6 +41,6 @@ export default defineAgentTool<{ user_id: string; group_id?: string }>({
40
41
  if (row.date === today) todayCount += count;
41
42
  }
42
43
  const name = String(rows[0]!.user_name ?? userId);
43
- return `${name} 的统计\n今日: ${todayCount}条\n本周: ${weekCount}条\n总计: ${totalCount}条\n活跃天数: ${rows.length}天`;
44
+ return t(context.locale, 'userStatsInfo', { name, today: todayCount, week: weekCount, total: totalCount, days: rows.length });
44
45
  },
45
46
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from './i18n.js';
1
2
  import { jsx, renderToHtml } from "@zhin.js/jsx";
2
3
  import {
3
4
  BarChart,
@@ -28,10 +29,11 @@ const T = {
28
29
  } as const;
29
30
 
30
31
  export async function buildAnalysisReportHtml(
31
- data: AnalysisReportData
32
+ data: AnalysisReportData,
33
+ locale?: import('zhin.js/i18n').Locale
32
34
  ): Promise<string> {
33
35
  const { stats, channelName, days, startDate, endDate, llm } = data;
34
- const title = channelName ? `${channelName} · 群日常分析` : "群日常分析";
36
+ const title = channelName ? t(locale, 'analysisNamed', { name: channelName }) : t(locale, 'analysis');
35
37
  const peakLabel = `${String(stats.mostActiveHour).padStart(2, "0")}:00`;
36
38
  const hourlyValues = Array.from(
37
39
  { length: 24 },
@@ -41,7 +43,7 @@ export async function buildAnalysisReportHtml(
41
43
  const llmBlocks = [
42
44
  llm?.topics?.length
43
45
  ? jsx(Section, {
44
- title: "热门话题",
46
+ title: t(locale, 'topics'),
45
47
  children: llm.topics.map((topic, i) =>
46
48
  jsx(TopicItem, {
47
49
  index: i + 1,
@@ -53,7 +55,7 @@ export async function buildAnalysisReportHtml(
53
55
  : "",
54
56
  llm?.quotes?.length
55
57
  ? jsx(Section, {
56
- title: "金句精选",
58
+ title: t(locale, 'quotes'),
57
59
  children: llm.quotes.map((quote, i) =>
58
60
  jsx(QuoteCard, {
59
61
  index: i + 1,
@@ -66,7 +68,7 @@ export async function buildAnalysisReportHtml(
66
68
  : "",
67
69
  llm?.userTitles?.length
68
70
  ? jsx(Section, {
69
- title: "用户画像",
71
+ title: t(locale, 'profiles'),
70
72
  children: llm.userTitles.map((user, i) =>
71
73
  jsx(ProfileRow, {
72
74
  index: i + 1,
@@ -82,27 +84,27 @@ export async function buildAnalysisReportHtml(
82
84
  const body = elevatedCard([
83
85
  jsx(CardHeader, {
84
86
  title,
85
- subtitle: `${startDate} 至 ${endDate} · 最近 ${days} 天`,
87
+ subtitle: t(locale, 'analysisRange', { start: startDate, end: endDate, days }),
86
88
  }),
87
89
  jsx(Row, {
88
90
  children: [
89
91
  jsx(StatChip, {
90
- label: "消息",
92
+ label: t(locale, 'messages'),
91
93
  value: formatCount(stats.messageCount),
92
94
  accent: T.accentMsg,
93
95
  }),
94
96
  jsx(StatChip, {
95
- label: "参与",
97
+ label: t(locale, 'participants'),
96
98
  value: formatCount(stats.participantCount),
97
99
  accent: T.accentPeople,
98
100
  }),
99
101
  jsx(StatChip, {
100
- label: "字数",
102
+ label: t(locale, 'characters'),
101
103
  value: formatCount(stats.totalChars),
102
104
  accent: T.accentChars,
103
105
  }),
104
106
  jsx(StatChip, {
105
- label: "活跃",
107
+ label: t(locale, 'active'),
106
108
  value: peakLabel,
107
109
  accent: T.accentHour,
108
110
  }),
@@ -111,7 +113,7 @@ export async function buildAnalysisReportHtml(
111
113
  style: "margin-bottom:4px",
112
114
  }),
113
115
  jsx(Section, {
114
- title: "每小时消息分布",
116
+ title: t(locale, 'hourDistribution'),
115
117
  children: jsx(BarChart, {
116
118
  values: hourlyValues,
117
119
  peakIndex: stats.mostActiveHour,
package/src/analysis.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { messages as t } from './i18n.js';
1
2
  /**
2
3
  * 群日常分析:从收件箱查询、基础统计、文本报告。
3
4
  * 数据源为 zhin 内置 unified_inbox_message 表(需 inbox.enabled)。
@@ -155,21 +156,22 @@ export function computeBasicStats(rows: InboxMessageRow[]): BasicStats {
155
156
  */
156
157
  export function formatTextReport(
157
158
  stats: BasicStats,
158
- options: { channelName?: string; days: number; startDate: string; endDate: string }
159
+ options: { channelName?: string; days: number; startDate: string; endDate: string },
160
+ locale?: import('zhin.js/i18n').Locale
159
161
  ): string {
160
162
  const lines: string[] = [];
161
163
  const title = options.channelName
162
- ? `【${options.channelName}】群日常分析`
163
- : "群日常分析";
164
+ ? t(locale, 'analysisTextNamed', { name: options.channelName })
165
+ : t(locale, 'analysis');
164
166
  lines.push(`📊 ${title}`);
165
- lines.push(`📅 统计区间:${options.startDate} 至 ${options.endDate}(最近 ${options.days} 天)`);
167
+ lines.push(t(locale, 'analysisTextRange', { start: options.startDate, end: options.endDate, days: options.days }));
166
168
  lines.push("");
167
- lines.push(`💬 消息总数:${stats.messageCount} 条`);
168
- lines.push(`👥 参与人数:${stats.participantCount} 人`);
169
- lines.push(`📝 总字数:${stats.totalChars} 字`);
170
- lines.push(`⏰ 最活跃时段:${stats.mostActiveHour}:00 - ${stats.mostActiveHour + 1}:00`);
169
+ lines.push(t(locale, 'analysisMessages', { count: stats.messageCount }));
170
+ lines.push(t(locale, 'analysisPeople', { count: stats.participantCount }));
171
+ lines.push(t(locale, 'analysisChars', { count: stats.totalChars }));
172
+ lines.push(t(locale, 'analysisPeak', { start: stats.mostActiveHour, end: stats.mostActiveHour + 1 }));
171
173
  lines.push("");
172
- lines.push("📈 每小时消息分布:");
174
+ lines.push(t(locale, 'hourDistributionText'));
173
175
  const maxH = Math.max(...Object.values(stats.hourlyDistribution), 1);
174
176
  for (let h = 0; h < 24; h++) {
175
177
  const c = stats.hourlyDistribution[h] || 0;
@@ -183,24 +185,24 @@ export function formatTextReport(
183
185
  * 将 LLM 分析结果追加到文本报告。
184
186
  * 话题/金句/用户画像 设计灵感来自 astrbot_plugin_qq_group_daily_analysis
185
187
  */
186
- export function appendLLMReport(textReport: string, llm: LLMAnalysis): string {
188
+ export function appendLLMReport(textReport: string, llm: LLMAnalysis, locale?: import('zhin.js/i18n').Locale): string {
187
189
  const lines: string[] = [textReport, ""];
188
190
  if (llm.topics?.length) {
189
- lines.push("🔥 热门话题(LLM 提取):");
191
+ lines.push(t(locale, 'llmTopics'));
190
192
  llm.topics.forEach((t, i) => {
191
193
  lines.push(` ${i + 1}. ${t.topic}${t.summary ? ` — ${t.summary}` : ""}`);
192
194
  });
193
195
  lines.push("");
194
196
  }
195
197
  if (llm.quotes?.length) {
196
- lines.push("💬 金句(LLM 筛选):");
198
+ lines.push(t(locale, 'llmQuotes'));
197
199
  llm.quotes.forEach((q, i) => {
198
200
  lines.push(` ${i + 1}. 「${q.content}」 — ${q.sender}(${q.reason})`);
199
201
  });
200
202
  lines.push("");
201
203
  }
202
204
  if (llm.userTitles?.length) {
203
- lines.push("👤 用户画像/称号(LLM):");
205
+ lines.push(t(locale, 'llmProfiles'));
204
206
  llm.userTitles.forEach((u, i) => {
205
207
  lines.push(` ${i + 1}. ${u.name} — ${u.title}${u.reason ? `(${u.reason})` : ""}`);
206
208
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from './i18n.js';
1
2
  import type { ConversationInput } from './conversation-scope.js';
2
3
  import type { GroupSuiteConfig } from './config.js';
3
4
  import { getCheckinModel } from './db-store.js';
@@ -70,19 +71,19 @@ export async function doCheckin(
70
71
  runtime: GroupSuiteRuntime,
71
72
  ): Promise<string> {
72
73
  const M = getCheckinModel(runtime.db);
73
- if (!M) return '签到数据库尚未就绪,请稍后重试';
74
+ if (!M) return t(input.locale, 'checkinRetry');
74
75
 
75
76
  const { id: userId, name: userName } = resolveSender(input);
76
- if (!userId) return '无法获取用户信息';
77
+ if (!userId) return t(input.locale, 'senderMissing');
77
78
 
78
79
  const { type: ctxType, id: ctxId } = resolveContextKey(input);
79
80
  return serializedCheckin(runtime.checkinChains, `${userId}:${ctxType}:${ctxId}`, async () => {
80
81
  const user = await getOrCreateUser(M, userId, userName, ctxType, ctxId);
81
- if (!user) return '签到失败,请重试';
82
+ if (!user) return t(input.locale, 'checkinFailed');
82
83
 
83
84
  const today = todayStr();
84
85
  if (user.last_checkin === today) {
85
- return `你今天已经签到过了哦~\n当前积分: ${user.points}\n连续签到: ${user.streak} 天`;
86
+ return t(input.locale, 'alreadyChecked', { points: String(user.points), days: String(user.streak) });
86
87
  }
87
88
 
88
89
  const isConsecutive = user.last_checkin === yesterdayStr();
@@ -106,9 +107,9 @@ export async function doCheckin(
106
107
  context_id: ctxId,
107
108
  });
108
109
 
109
- const lines = [`签到成功!`, `基础积分: +${base}`];
110
- if (bonus > 0) lines.push(`连续奖励: +${bonus} (连续${newStreak}天)`);
111
- lines.push(`本次获得: +${earned}`, `当前积分: ${newPoints}`);
110
+ const lines = [t(input.locale, 'checkinDone'), t(input.locale, 'basePoints', { points: base })];
111
+ if (bonus > 0) lines.push(t(input.locale, 'bonusPoints', { points: bonus, days: newStreak }));
112
+ lines.push(t(input.locale, 'earnedPoints', { points: earned }), t(input.locale, 'currentPoints', { points: newPoints }));
112
113
  return lines.join('\n');
113
114
  });
114
115
  }
@@ -116,9 +117,10 @@ export async function doCheckin(
116
117
  export async function myPoints(
117
118
  input: ConversationInput,
118
119
  runtime: GroupSuiteRuntime,
120
+ commandHead?: string,
119
121
  ): Promise<string> {
120
122
  const M = getCheckinModel(runtime.db);
121
- if (!M) return '签到数据库尚未就绪';
123
+ if (!M) return t(input.locale, 'checkinDb');
122
124
  const { id: userId } = resolveSender(input);
123
125
  const { type: ctxType, id: ctxId } = resolveContextKey(input);
124
126
  const rows = (await M.select().where({
@@ -126,16 +128,16 @@ export async function myPoints(
126
128
  context_type: ctxType,
127
129
  context_id: ctxId,
128
130
  })) as Record<string, unknown>[];
129
- if (rows.length === 0) return '你还没有签到记录哦,发送「checkin」开始吧!';
131
+ if (rows.length === 0) return commandHead ? t(input.locale, 'checkinHint', { command: commandHead }) : t(input.locale, 'checkinHintNoHead');
130
132
  const u = rows[0];
131
133
  const checkedToday = u.last_checkin === todayStr();
132
134
  return [
133
- `${u.user_name || '用户'} 的积分信息`,
134
- `积分: ${u.points}`,
135
- `累计签到: ${u.total_checkins} 天`,
136
- `连续签到: ${u.streak} 天`,
137
- `最长连续: ${u.max_streak} 天`,
138
- checkedToday ? '今日已签到 ✓' : '今日未签到',
135
+ t(input.locale, 'pointsTitle', { name: String(u.user_name || t(input.locale, 'user')) }),
136
+ t(input.locale, 'points', { points: String(u.points) }),
137
+ t(input.locale, 'checkinTotal', { days: String(u.total_checkins) }),
138
+ t(input.locale, 'checkinStreak', { days: String(u.streak) }),
139
+ t(input.locale, 'checkinMax', { days: String(u.max_streak) }),
140
+ checkedToday ? t(input.locale, 'checked') : t(input.locale, 'unchecked'),
139
141
  ].join('\n');
140
142
  }
141
143
 
@@ -145,23 +147,23 @@ export async function pointsRank(
145
147
  runtime: GroupSuiteRuntime,
146
148
  ): Promise<string> {
147
149
  const M = getCheckinModel(runtime.db);
148
- if (!M) return '签到数据库尚未就绪';
150
+ if (!M) return t(input.locale, 'checkinDb');
149
151
  const { type: ctxType, id: ctxId } = resolveContextKey(input);
150
152
  const all = (await M.select().where(
151
153
  { context_type: ctxType, context_id: ctxId },
152
154
  )) as Record<string, unknown>[];
153
- if (all.length === 0) return '还没有人签到过哦~';
155
+ if (all.length === 0) return t(input.locale, 'checkinEmpty');
154
156
  const sorted = all.sort((a, b) => Number(b.points || 0) - Number(a.points || 0));
155
157
  const top = sorted.slice(0, cfg.rankSize);
156
158
  const medals = ['🥇', '🥈', '🥉'];
157
159
  const lines = top.map((u, i) => {
158
160
  const prefix = i < 3 ? medals[i] : `${i + 1}.`;
159
- return `${prefix} ${u.user_name || u.user_id} — ${u.points}分 (${u.total_checkins}天)`;
161
+ return t(input.locale, 'pointsRow', { prefix, name: String(u.user_name || u.user_id), points: String(u.points), days: String(u.total_checkins) });
160
162
  });
161
- const title = ctxType === 'group' ? '本群积分排行'
162
- : ctxType === 'channel' ? '本频道积分排行' : '当前私聊积分排行';
163
+ const title = ctxType === 'group' ? t(input.locale, 'groupPoints')
164
+ : ctxType === 'channel' ? t(input.locale, 'channelPoints') : t(input.locale, 'privatePoints');
163
165
  const { id: userId } = resolveSender(input);
164
166
  const myRank = sorted.findIndex((u) => u.user_id === userId);
165
- const footer = myRank >= 0 ? `\n你的排名: 第${myRank + 1}名` : '';
167
+ const footer = myRank >= 0 ? t(input.locale, 'myRankFooter', { rank: myRank + 1 }) : '';
166
168
  return `${title}\n${lines.join('\n')}${footer}`;
167
169
  }
@@ -3,6 +3,7 @@ import { adapterFeatureId, type AdapterIndex } from 'zhin.js/adapter';
3
3
  import type { ToolInvocationOrigin } from '@zhin.js/tool';
4
4
 
5
5
  export interface ConversationInput {
6
+ readonly locale?: import('zhin.js/i18n').Locale;
6
7
  readonly conversation?: DeliveryTarget;
7
8
  readonly sender?: Readonly<{ id: string; name?: string }>;
8
9
  readonly content?: unknown;