@zhin.js/plugin-rss 1.1.9 → 1.1.11

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 (61) hide show
  1. package/README.md +8 -0
  2. package/commands/rss-add/[url]/index.js +12 -10
  3. package/commands/rss-add/[url]/index.ts +12 -10
  4. package/commands/rss-check/[[url]]/index.js +8 -7
  5. package/commands/rss-check/[[url]]/index.ts +8 -7
  6. package/commands/rss-list/index.js +9 -7
  7. package/commands/rss-list/index.ts +9 -7
  8. package/commands/rss-preview/[url]/index.js +7 -6
  9. package/commands/rss-preview/[url]/index.ts +7 -6
  10. package/commands/rss-remove/[url]/index.js +8 -7
  11. package/commands/rss-remove/[url]/index.ts +8 -7
  12. package/lib/admin-i18n.d.ts +219 -0
  13. package/lib/admin-i18n.d.ts.map +1 -0
  14. package/lib/admin-i18n.js +216 -0
  15. package/lib/admin-i18n.js.map +1 -0
  16. package/lib/admin-tasks.d.ts +40 -2
  17. package/lib/admin-tasks.d.ts.map +1 -1
  18. package/lib/admin-tasks.js +99 -72
  19. package/lib/admin-tasks.js.map +1 -1
  20. package/lib/channel.d.ts +2 -1
  21. package/lib/channel.d.ts.map +1 -1
  22. package/lib/channel.js +3 -3
  23. package/lib/channel.js.map +1 -1
  24. package/lib/feed.d.ts +2 -1
  25. package/lib/feed.d.ts.map +1 -1
  26. package/lib/feed.js +6 -5
  27. package/lib/feed.js.map +1 -1
  28. package/lib/i18n.d.ts +2 -0
  29. package/lib/i18n.d.ts.map +1 -0
  30. package/lib/i18n.js +168 -0
  31. package/lib/i18n.js.map +1 -0
  32. package/lib/poll.d.ts +3 -1
  33. package/lib/poll.d.ts.map +1 -1
  34. package/lib/poll.js +23 -18
  35. package/lib/poll.js.map +1 -1
  36. package/lib/quiet-hours.d.ts +5 -0
  37. package/lib/quiet-hours.d.ts.map +1 -1
  38. package/lib/quiet-hours.js +9 -4
  39. package/lib/quiet-hours.js.map +1 -1
  40. package/lib/runtime.d.ts +3 -0
  41. package/lib/runtime.d.ts.map +1 -1
  42. package/lib/runtime.js.map +1 -1
  43. package/lib/storage.d.ts.map +1 -1
  44. package/lib/storage.js +6 -5
  45. package/lib/storage.js.map +1 -1
  46. package/package.json +7 -7
  47. package/plugin.js +6 -2
  48. package/schema.json +6 -1
  49. package/skills/rss/tools/rss_list_subscriptions/index.js +4 -3
  50. package/skills/rss/tools/rss_list_subscriptions/index.ts +4 -3
  51. package/skills/rss/tools/rss_preview_feed/index.js +4 -3
  52. package/skills/rss/tools/rss_preview_feed/index.ts +4 -3
  53. package/src/admin-i18n.ts +218 -0
  54. package/src/admin-tasks.ts +95 -72
  55. package/src/channel.ts +4 -2
  56. package/src/feed.ts +7 -5
  57. package/src/i18n.ts +168 -0
  58. package/src/poll.ts +24 -18
  59. package/src/quiet-hours.ts +11 -4
  60. package/src/runtime.ts +3 -0
  61. package/src/storage.ts +6 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-rss",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "RSS/Atom feed subscription plugin for Zhin.js (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,20 +41,20 @@
41
41
  "license": "MIT",
42
42
  "dependencies": {
43
43
  "rss-parser": "^3.13.0",
44
- "@zhin.js/im-contract": "1.1.6",
45
- "@zhin.js/tool": "1.1.9",
46
- "@zhin.js/skill": "1.1.9"
44
+ "@zhin.js/im-contract": "1.1.8",
45
+ "@zhin.js/tool": "1.1.11",
46
+ "@zhin.js/skill": "1.1.10"
47
47
  },
48
48
  "devDependencies": {
49
49
  "typescript": "^6.0.3",
50
50
  "vitest": "^4.1.10",
51
51
  "zod": "^4.4.3",
52
- "zhin.js": "1.1.15"
52
+ "zhin.js": "1.1.17"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "zod": "^4.0.0",
56
- "@zhin.js/command": "^1.1.10",
57
- "zhin.js": "^1.1.15"
56
+ "@zhin.js/command": "^1.1.11",
57
+ "zhin.js": "^1.1.17"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "@zhin.js/command": {
package/plugin.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { projectLocaleToken, resolveLocale } from 'zhin.js/i18n';
2
3
  import { definePlugin, logger, databaseHostToken, outboundHostToken, scheduleHostToken, } from 'zhin.js';
3
4
  import { pluginTaskProviderToken } from 'zhin.js/plugin-runtime';
4
5
  import { createAdministratorRssState } from "./lib/storage.js";
@@ -18,7 +19,8 @@ function defineRssTables(db) {
18
19
  creator_id: { type: 'text', default: '' },
19
20
  creator_name: { type: 'text', default: '' },
20
21
  created_at: { type: 'text', default: '' },
21
- });
22
+ locale: { type: 'text', length: 16, default: 'zh-CN' },
23
+ }, { addColumns: ['locale'] });
22
24
  db.define(RSS_SEEN_TABLE, {
23
25
  feed_url: { type: 'text', nullable: false },
24
26
  item_guid: { type: 'text', nullable: false },
@@ -58,11 +60,13 @@ export default definePlugin({
58
60
  id: input.channelId,
59
61
  },
60
62
  content: input.content,
63
+ locale: input.locale,
61
64
  });
62
65
  };
63
66
  }
64
67
  const runtime = Object.freeze({
65
68
  db,
69
+ locale: resolveLocale(context.resources.use(projectLocaleToken)),
66
70
  config: Object.freeze({ ...config }),
67
71
  outbound: outboundPush,
68
72
  });
@@ -80,7 +84,7 @@ export default definePlugin({
80
84
  }
81
85
  catch { /* No persistent state port: no administrator provider. */ }
82
86
  if (state) {
83
- administrator = new AdministratorRssTasks(createAdministratorRssState(database), outbound, config);
87
+ administrator = new AdministratorRssTasks(createAdministratorRssState(database), outbound, config, undefined, runtime.locale);
84
88
  context.resources.provide(pluginTaskProviderToken, administrator);
85
89
  context.lifecycle.add(() => administrator?.stop());
86
90
  }
package/schema.json CHANGED
@@ -6,7 +6,12 @@
6
6
  "pollCron": {
7
7
  "type": "string",
8
8
  "default": "0 */5 * * * *",
9
- "description": "轮询频率 (6 段 Cron 表达式)"
9
+ "description": "轮询频率 (6 段 Cron 表达式)",
10
+ "x-i18n": {
11
+ "en": {
12
+ "description": "Polling schedule (6-field cron expression)."
13
+ }
14
+ }
10
15
  },
11
16
  "maxPerGroup": {
12
17
  "type": "number",
@@ -3,16 +3,17 @@ import { defineAgentTool } from '@zhin.js/tool';
3
3
  import { z } from 'zod';
4
4
  import { getRssSubs } from "../../../../lib/db-store.js";
5
5
  import { rssRuntimeToken } from "../../../../lib/runtime.js";
6
+ import { rssMessages as t } from "../../../../lib/i18n.js";
6
7
  export default defineAgentTool({
7
8
  description: '查询当前所有 RSS 订阅',
8
9
  inputSchema: z.object({}),
9
10
  async execute(_input, context) {
10
11
  const Subs = getRssSubs(context.use(rssRuntimeToken).db);
11
12
  if (!Subs)
12
- return 'RSS 数据库尚未就绪';
13
+ return t(context.locale, 'databaseUnavailable');
13
14
  const all = (await Subs.select());
14
15
  if (all.length === 0)
15
- return '暂无任何 RSS 订阅';
16
+ return t(context.locale, 'allEmpty');
16
17
  const grouped = new Map();
17
18
  for (const sub of all) {
18
19
  const key = `${sub.channel_type}:${sub.channel_id}`;
@@ -27,6 +28,6 @@ export default defineAgentTool({
27
28
  lines.push(` - ${s.feed_title || s.url}`);
28
29
  }
29
30
  }
30
- return `RSS 订阅总览 (${all.length} 条):\n${lines.join('\n')}`;
31
+ return t(context.locale, 'allList', { count: all.length, items: lines.join('\n') });
31
32
  },
32
33
  });
@@ -2,13 +2,14 @@ import { defineAgentTool } from '@zhin.js/tool';
2
2
  import { z } from 'zod';
3
3
  import { getRssSubs } from '../../../../src/db-store.js';
4
4
  import { rssRuntimeToken } from '../../../../src/runtime.js';
5
+ import { rssMessages as t } from '../../../../src/i18n.js';
5
6
 
6
7
  export default defineAgentTool({
7
8
  description: '查询当前所有 RSS 订阅',
8
9
  inputSchema: z.object({}),
9
10
  async execute(_input, context) {
10
11
  const Subs = getRssSubs(context.use(rssRuntimeToken).db);
11
- if (!Subs) return 'RSS 数据库尚未就绪';
12
+ if (!Subs) return t(context.locale, 'databaseUnavailable');
12
13
 
13
14
  const all = (await Subs.select()) as Array<{
14
15
  channel_type: string;
@@ -16,7 +17,7 @@ export default defineAgentTool({
16
17
  feed_title?: string;
17
18
  url: string;
18
19
  }>;
19
- if (all.length === 0) return '暂无任何 RSS 订阅';
20
+ if (all.length === 0) return t(context.locale, 'allEmpty');
20
21
 
21
22
  const grouped = new Map<string, typeof all>();
22
23
  for (const sub of all) {
@@ -33,6 +34,6 @@ export default defineAgentTool({
33
34
  lines.push(` - ${s.feed_title || s.url}`);
34
35
  }
35
36
  }
36
- return `RSS 订阅总览 (${all.length} 条):\n${lines.join('\n')}`;
37
+ return t(context.locale, 'allList', { count: all.length, items: lines.join('\n') });
37
38
  },
38
39
  });
@@ -3,24 +3,25 @@ import { defineAgentTool } from '@zhin.js/tool';
3
3
  import { z } from 'zod';
4
4
  import { fetchFeed } from "../../../../lib/feed.js";
5
5
  import { rssRuntimeToken } from "../../../../lib/runtime.js";
6
+ import { rssMessages as t } from "../../../../lib/i18n.js";
6
7
  export default defineAgentTool({
7
8
  description: '预览一个 RSS 源的最新内容',
8
9
  inputSchema: z.object({ url: z.string().min(1) }),
9
10
  async execute({ url }, context) {
10
11
  if (!url)
11
- return '请提供 RSS 源 URL';
12
+ return t(context.locale, 'urlRequired');
12
13
  try {
13
14
  const runtime = context.use(rssRuntimeToken);
14
15
  const { title, items } = await fetchFeed(url, runtime.config.timeout);
15
16
  if (items.length === 0)
16
- return `${title}: 无内容`;
17
+ return t(context.locale, 'toolPreviewEmpty', { title });
17
18
  return items
18
19
  .slice(0, 5)
19
20
  .map((item, i) => `${i + 1}. ${item.title}\n ${item.link}`)
20
21
  .join('\n');
21
22
  }
22
23
  catch (e) {
23
- return `解析失败: ${e.message}`;
24
+ return t(context.locale, 'toolPreviewFailed', { error: e.message });
24
25
  }
25
26
  },
26
27
  });
@@ -2,22 +2,23 @@ import { defineAgentTool } from '@zhin.js/tool';
2
2
  import { z } from 'zod';
3
3
  import { fetchFeed } from '../../../../src/feed.js';
4
4
  import { rssRuntimeToken } from '../../../../src/runtime.js';
5
+ import { rssMessages as t } from '../../../../src/i18n.js';
5
6
 
6
7
  export default defineAgentTool<{ url: string }>({
7
8
  description: '预览一个 RSS 源的最新内容',
8
9
  inputSchema: z.object({ url: z.string().min(1) }),
9
10
  async execute({ url }, context) {
10
- if (!url) return '请提供 RSS 源 URL';
11
+ if (!url) return t(context.locale, 'urlRequired');
11
12
  try {
12
13
  const runtime = context.use(rssRuntimeToken);
13
14
  const { title, items } = await fetchFeed(url, runtime.config.timeout);
14
- if (items.length === 0) return `${title}: 无内容`;
15
+ if (items.length === 0) return t(context.locale, 'toolPreviewEmpty', { title });
15
16
  return items
16
17
  .slice(0, 5)
17
18
  .map((item, i) => `${i + 1}. ${item.title}\n ${item.link}`)
18
19
  .join('\n');
19
20
  } catch (e) {
20
- return `解析失败: ${(e as Error).message}`;
21
+ return t(context.locale, 'toolPreviewFailed', { error: (e as Error).message });
21
22
  }
22
23
  },
23
24
  });
@@ -0,0 +1,218 @@
1
+ import { createMessages, type Locale } from 'zhin.js/i18n';
2
+ import { PluginTaskError } from 'zhin.js/plugin-runtime';
3
+
4
+ const catalog = {
5
+ "invalidTarget": {
6
+ "zh-CN": "请提供完整有效的目标账号、会话、父容器和线程",
7
+ "en": "Provide a complete valid account, conversation, parent container and thread."
8
+ },
9
+ "requestReused": {
10
+ "zh-CN": "同一请求 ID 已用于其他内容",
11
+ "en": "This request ID was already used for different content."
12
+ },
13
+ "invalidAction": {
14
+ "zh-CN": "任务 ID 或操作无效",
15
+ "en": "Invalid task ID or action."
16
+ },
17
+ "updateParameters": {
18
+ "zh-CN": "仅编辑操作接受参数",
19
+ "en": "Only the update action accepts parameters."
20
+ },
21
+ "finished": {
22
+ "zh-CN": "任务已结束,不能再次取消",
23
+ "en": "This task has ended and cannot be cancelled again."
24
+ },
25
+ "invalidMutation": {
26
+ "zh-CN": "操作须包含 UUID requestId 和读取到的 expectedRevision",
27
+ "en": "The operation requires a UUID requestId and the expectedRevision read from the task list."
28
+ },
29
+ "replayConflict": {
30
+ "zh-CN": "同一操作请求 ID 已用于其他内容,禁止重放",
31
+ "en": "This operation request ID was used for different content and cannot be replayed."
32
+ },
33
+ "delivering": {
34
+ "zh-CN": "投递中或未确认;不会自动重试",
35
+ "en": "Delivery is in progress or unconfirmed; no automatic retry."
36
+ },
37
+ "exactTarget": {
38
+ "zh-CN": "管理员 RSS 需要支持精确目标的 Outbound Host",
39
+ "en": "Administrator RSS requires an Outbound Host with exact-target delivery."
40
+ },
41
+ "quota": {
42
+ "zh-CN": "管理员 RSS 订阅已达 8 条上限,请先取消任务",
43
+ "en": "The administrator RSS limit of 8 subscriptions has been reached. Cancel a task first."
44
+ },
45
+ "duplicate": {
46
+ "zh-CN": "此完整目标已有该源的管理员订阅,请先核实原任务",
47
+ "en": "This exact target already has an administrator subscription for this feed. Check the existing task."
48
+ },
49
+ "duplicateUpdate": {
50
+ "zh-CN": "此完整目标已有该源的管理员订阅",
51
+ "en": "This exact target already has an administrator subscription for this feed."
52
+ },
53
+ "notFound": {
54
+ "zh-CN": "未找到当前实例的管理员 RSS 订阅;聊天订阅不在此入口管理",
55
+ "en": "No administrator RSS subscription was found in this instance. Chat subscriptions are managed separately."
56
+ },
57
+ "pauseState": {
58
+ "zh-CN": "仅待轮询订阅可以暂停",
59
+ "en": "Only scheduled subscriptions can be paused."
60
+ },
61
+ "resumeState": {
62
+ "zh-CN": "仅暂停订阅可以恢复;未确认投递不能恢复或重试",
63
+ "en": "Only paused subscriptions can be resumed. Unconfirmed deliveries cannot be resumed or retried."
64
+ },
65
+ "updateState": {
66
+ "zh-CN": "编辑前请先暂停订阅;未确认投递不能编辑",
67
+ "en": "Pause the subscription before editing. Unconfirmed deliveries cannot be edited."
68
+ },
69
+ "stopped": {
70
+ "zh-CN": "管理员 RSS 服务属于已停止的插件实例",
71
+ "en": "This administrator RSS service belongs to a stopped plugin instance."
72
+ },
73
+ "retired": {
74
+ "zh-CN": "管理员 RSS 服务属于未发布或已退役的 generation",
75
+ "en": "This administrator RSS service belongs to an unpublished or retired generation."
76
+ },
77
+ "invalidParameters": {
78
+ "zh-CN": "参数须包含有效 HTTP(S) RSS 地址及可选的三项静默设置",
79
+ "en": "Provide a valid HTTP(S) feed URL and optionally all three quiet-hours settings."
80
+ },
81
+ "invalidQuiet": {
82
+ "zh-CN": "静默设置无效",
83
+ "en": "Invalid quiet-hours settings."
84
+ },
85
+ "invalidUrl": {
86
+ "zh-CN": "请提供有效 HTTP(S) RSS 地址,不接受 URL 用户名或密码",
87
+ "en": "Provide a valid HTTP(S) feed URL without a username or password."
88
+ },
89
+ "revision": {
90
+ "zh-CN": "订阅状态已变化,请刷新后重新确认",
91
+ "en": "Subscription state changed. Refresh it and confirm again."
92
+ },
93
+ "capacity": {
94
+ "zh-CN": "管理员 RSS 状态达到 8 条或 60 KiB 上限,请先取消并整理订阅",
95
+ "en": "Administrator RSS reached the 8-task or 60 KiB limit. Cancel and clean up subscriptions first."
96
+ },
97
+ "capacityReserved": {
98
+ "zh-CN": "管理员 RSS 状态空间不足以保留静默摘要及去重游标;请先取消并整理订阅,现有数据未改动",
99
+ "en": "There is not enough RSS storage for quiet-hour summaries and deduplication cursors. Cancel and clean up subscriptions first; existing data has not changed."
100
+ },
101
+ "corrupt": {
102
+ "zh-CN": "管理员 RSS 持久状态格式无效,已停止管理员轮询与投递;请保留原数据核查",
103
+ "en": "Administrator RSS storage is invalid. Polling and delivery have stopped; preserve the original data for investigation."
104
+ },
105
+ "waiting": {
106
+ "zh-CN": "等待首次抓取建立基线,不发送历史文章",
107
+ "en": "Waiting for the first fetch to establish a baseline; historical articles will not be sent."
108
+ },
109
+ "paused": {
110
+ "zh-CN": "管理员暂停并清空待发摘要;恢复时重新建立基线,不补发历史文章",
111
+ "en": "Paused by an administrator and pending summaries cleared. Resuming establishes a new baseline without backfilling articles."
112
+ },
113
+ "resumed": {
114
+ "zh-CN": "已恢复,等待重新建立基线,不补发历史文章",
115
+ "en": "Resumed; waiting for a new baseline without backfilling articles."
116
+ },
117
+ "edited": {
118
+ "zh-CN": "已编辑并清空待发摘要,保持暂停,恢复后首次抓取只建立基线",
119
+ "en": "Updated and pending summaries cleared; still paused. The first fetch after resuming only establishes a baseline."
120
+ },
121
+ "cancelled": {
122
+ "zh-CN": "管理员取消并清空待发摘要;已经开始的投递无法撤回",
123
+ "en": "Cancelled by an administrator and pending summaries cleared. A delivery already in progress cannot be recalled."
124
+ },
125
+ "fetchFailed": {
126
+ "zh-CN": "源站抓取失败;尚未尝试投递,下轮重查",
127
+ "en": "Feed fetch failed. No delivery was attempted; it will be checked on the next poll."
128
+ },
129
+ "baseline": {
130
+ "zh-CN": "已建立基线;从后续新文章开始推送",
131
+ "en": "Baseline established; only future articles will be delivered."
132
+ },
133
+ "noNew": {
134
+ "zh-CN": "抓取完成,暂无新文章",
135
+ "en": "Fetch completed; no new articles."
136
+ },
137
+ "quietPending": {
138
+ "zh-CN": "静默中,已积累更新;退出静默后的首轮轮询汇总",
139
+ "en": "Quiet hours are active; updates are queued for the first poll after quiet hours."
140
+ },
141
+ "summaryDelivering": {
142
+ "zh-CN": "摘要投递中或未确认;不会自动重试",
143
+ "en": "Summary delivery is in progress or unconfirmed; no automatic retry."
144
+ },
145
+ "unknown": {
146
+ "zh-CN": "平台未确认投递,请核实目标会话后取消并另建订阅;不会自动重试",
147
+ "en": "The platform did not confirm delivery. Check the target conversation, then cancel and create another subscription; no automatic retry."
148
+ },
149
+ "delivered": {
150
+ "zh-CN": "已确认投递;等待后续新文章",
151
+ "en": "Delivery confirmed; waiting for future articles."
152
+ },
153
+ "rejected": {
154
+ "zh-CN": "平台明确未投递或拒绝;订阅已停止",
155
+ "en": "The platform confirmed no delivery or rejected it. The subscription has stopped."
156
+ },
157
+ "quietDescription": {
158
+ "zh-CN": "静默 {timezone} {start}–{end}",
159
+ "en": "Quiet hours: {timezone} {start}–{end}"
160
+ },
161
+ "pendingDescription": {
162
+ "zh-CN": "待汇总{count} 条更新",
163
+ "en": "Queued summary: {count} updates"
164
+ },
165
+ "summaryDescription": {
166
+ "zh-CN": "本次摘要{count} 条更新",
167
+ "en": "Current summary: {count} updates"
168
+ },
169
+ "countCapped": {
170
+ "zh-CN": "至少{count}",
171
+ "en": "at least {count}"
172
+ },
173
+ "pollDescription": {
174
+ "zh-CN": "实例轮询 {cron}",
175
+ "en": "Instance polling: {cron}"
176
+ },
177
+ "checkedDescription": {
178
+ "zh-CN": "最近抓取 {at}",
179
+ "en": "Last fetched: {at}"
180
+ },
181
+ "stateNote": {
182
+ "zh-CN": "状态不代表平台连接健康",
183
+ "en": "State does not represent platform connection health."
184
+ },
185
+ "quietFields": {
186
+ "zh-CN": "静默时区、开始和结束时间须全部填写;三项全部留空关闭静默",
187
+ "en": "Provide the quiet timezone, start and end together. Leave all three empty to disable quiet hours."
188
+ },
189
+ "quietZoneOffset": {
190
+ "zh-CN": "请填写有效 IANA 时区,如 Asia/Shanghai;不能使用 +08:00 等偏移",
191
+ "en": "Use an IANA timezone such as Asia/Shanghai, not a fixed offset such as +08:00."
192
+ },
193
+ "quietZone": {
194
+ "zh-CN": "请填写有效 IANA 时区,如 Asia/Shanghai",
195
+ "en": "Use a valid IANA timezone such as Asia/Shanghai."
196
+ },
197
+ "quietClock": {
198
+ "zh-CN": "静默时间须为 HH:mm(24 小时制),开始和结束不能相同",
199
+ "en": "Use HH:mm in 24-hour format; quiet start and end must be different."
200
+ }
201
+ } as const;
202
+ export const adminText = createMessages(catalog);
203
+ export type AdminTextKey = keyof typeof catalog;
204
+ export function isAdminTextKey(value: unknown): value is AdminTextKey { return typeof value === 'string' && Object.hasOwn(catalog, value); }
205
+
206
+ /** Persist a stable presentation key; preserve legacy text for older readers. */
207
+ export function taskDetail(detailKey: AdminTextKey) { return { detailKey, detail: adminText('zh-CN', detailKey) }; }
208
+ export function formatTaskDetail(task: { detail: string; detailKey?: AdminTextKey }, locale?: Locale): string {
209
+ return task.detailKey ? adminText(locale, task.detailKey) : task.detail;
210
+ }
211
+
212
+ export class LocalizedTaskError extends PluginTaskError {
213
+ readonly messageKey: AdminTextKey;
214
+ constructor(code: ConstructorParameters<typeof PluginTaskError>[0], messageKey: AdminTextKey) {
215
+ super(code, adminText('zh-CN', messageKey)); this.messageKey = messageKey;
216
+ }
217
+ localize(locale?: Locale): PluginTaskError { return new PluginTaskError(this.code, adminText(locale, this.messageKey)); }
218
+ }