@nubisjs/core 0.1.0-next.0 → 0.1.0-next.2

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/dist/command.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { APIChatInputApplicationCommandInteraction } from "discord-api-type
2
2
  import type { NubisApi } from "./api";
3
3
  import type { ModalData } from "./component";
4
4
  import type { Guard } from "./guard";
5
+ import type { Localizable } from "./localization";
5
6
  import type { Middleware } from "./middleware";
6
7
  import type { OptionMap, ResolvedOptions } from "./option";
7
8
  import type { ReplyInput } from "./reply";
@@ -36,7 +37,7 @@ export interface CommandContext<T extends OptionMap> {
36
37
  /** Opens a modal. Must be the first response, so call it before reply(). */
37
38
  showModal(modal: ModalData): Promise<void>;
38
39
  }
39
- export interface CommandConfig<T extends OptionMap> {
40
+ export interface CommandConfig<T extends OptionMap> extends Localizable {
40
41
  readonly description: string;
41
42
  readonly options?: T;
42
43
  /** Makes replies (and the automatic "thinking…") visible only to the user. */
@@ -1,10 +1,10 @@
1
1
  import type { APIMessageComponentInteraction, APIModalSubmitInteraction } from "discord-api-types/v10";
2
- import { type ComponentDefinition, type ComponentRuntime } from "./component";
3
2
  import type { NubisApi } from "./api";
3
+ import { type ComponentDefinition, type ComponentRuntime } from "./component";
4
4
  import { type ErrorHandler } from "./errors";
5
5
  import { type Guard } from "./guard";
6
- import { type Middleware } from "./middleware";
7
6
  import { type Scheduler, type Transport } from "./lifecycle";
7
+ import { type Middleware } from "./middleware";
8
8
  export type ComponentRegistry = ReadonlyMap<string, ComponentRuntime>;
9
9
  export declare function createComponentRegistry(components: readonly ComponentDefinition[]): ComponentRegistry;
10
10
  export type ComponentDispatchResult = {
@@ -2,8 +2,8 @@ import { type APIApplicationCommandInteractionDataBasicOption, type APIApplicati
2
2
  import type { NubisApi } from "./api";
3
3
  import { type ErrorHandler } from "./errors";
4
4
  import { type Guard } from "./guard";
5
- import { type Middleware } from "./middleware";
6
5
  import { type Lifecycle, type Scheduler, type Transport } from "./lifecycle";
6
+ import { type Middleware } from "./middleware";
7
7
  import type { CommandLeaf, CommandParent, CommandTree } from "./tree";
8
8
  export interface DispatchOptions {
9
9
  /** See LifecycleOptions.autoDeferMs. */
package/dist/index.d.ts CHANGED
@@ -2,28 +2,26 @@ export type { NubisApi } from "./api";
2
2
  export { missingApi } from "./api";
3
3
  export type { AutocompleteOptions, AutocompleteResult } from "./autocomplete";
4
4
  export { dispatchAutocomplete } from "./autocomplete";
5
- export type { CommandConfig, CommandContextName, IntegrationTypeName, TopLevelSettings, CommandContext, CommandDefinition, } from "./command";
5
+ export type { CommandConfig, CommandContext, CommandContextName, CommandDefinition, IntegrationTypeName, TopLevelSettings, } from "./command";
6
6
  export { command } from "./command";
7
7
  export type { ButtonConfig, ButtonDefinition, ButtonOverrides, ButtonStyleName, ChannelSelectConfig, ChannelSelection, ChannelSelectOverrides, ComponentContext, ComponentDefinition, ComponentKind, ComponentState, EmojiInput, EntitySelectDefinition, LinkButtonConfig, MentionableSelectConfig, MentionableSelection, MentionableSelectOverrides, ModalData, RoleSelectConfig, RoleSelection, RoleSelectOverrides, SelectOption, StringSelectConfig, StringSelectDefinition, StringSelectOverrides, UserSelectConfig, UserSelection, UserSelectOverrides, } from "./component";
8
8
  export { button, channelSelect, linkButton, mentionableSelect, roleSelect, stringSelect, toEmoji, userSelect, } from "./component";
9
- export type { MemoryStoreOptions, StateStore } from "./store";
10
- export { memoryStore } from "./store";
11
9
  export type { ComponentDispatchOptions, ComponentDispatchResult, ComponentRegistry, } from "./component-dispatch";
12
10
  export { createComponentRegistry, dispatchComponent, dispatchModal, STALE_MESSAGE, } from "./component-dispatch";
13
11
  export type { DispatchOptions, DispatchResult } from "./dispatch";
14
12
  export { dispatch, ERROR_MESSAGE, UNKNOWN_COMMAND_MESSAGE } from "./dispatch";
15
- export type { InteractionKind, Lifecycle, LifecycleOptions, Scheduler, Transport, } from "./lifecycle";
16
- export { createLifecycle, DEFAULT_AUTO_DEFER_MS } from "./lifecycle";
17
13
  export type { ErrorHandler, ErrorInfo } from "./errors";
18
14
  export { NubisError } from "./errors";
19
15
  export type { AnyEventDefinition, CamelCase, EventContext, EventDefinition, EventDispatchResult, EventName, EventPayloads, EventRegistry, } from "./events";
20
16
  export { createEventRegistry, dispatchEvent, event, toEventName, } from "./events";
21
17
  export type { CooldownOptions, Denial, Guard, GuardContext, GuardedInteraction, GuardResult, } from "./guard";
22
18
  export { botHasPermission, cooldown, guard, guildOnly, hasPermission, hasRole, invokerOnly, } from "./guard";
23
- export type { Middleware, MiddlewareContext } from "./middleware";
24
- export { middleware } from "./middleware";
19
+ export type { InteractionKind, Lifecycle, LifecycleOptions, Scheduler, Transport, } from "./lifecycle";
20
+ export { createLifecycle, DEFAULT_AUTO_DEFER_MS } from "./lifecycle";
25
21
  export type { MenuDefinition, MenuDispatchResult, MenuPayload, MessageMenuConfig, MessageMenuContext, UserMenuConfig, UserMenuContext, } from "./menu";
26
22
  export { dispatchMenu, messageCommand, toMenuPayloads, userCommand, } from "./menu";
23
+ export type { Middleware, MiddlewareContext } from "./middleware";
24
+ export { middleware } from "./middleware";
27
25
  export type { FieldMap, ModalConfig, ModalContext, ModalDefinition, ModalOverrides, ModalValues, TextInputConfig, TextInputDefinition, } from "./modal";
28
26
  export { modal, textInput } from "./modal";
29
27
  export type { AnyOptionConfig, AutocompleteContext, AutocompleteHandler, BasicOptionConfig, ChannelOptionConfig, Choice, IsRequired, NumberOptionConfig, OptionDefinition, OptionKind, OptionMap, OptionValue, OptionValues, ResolvedOptions, StringOptionConfig, ValueOf, } from "./option";
@@ -34,8 +32,10 @@ export type { SchemaInput, SchemaOutput, StandardResult, StandardSchemaV1, } fro
34
32
  export { stateOf } from "./schema";
35
33
  export type { CommandPayload } from "./serialize";
36
34
  export { toCommandPayloads } from "./serialize";
35
+ export type { DiscordLocale, Localizable, Localizations } from "./localization";
36
+ export type { MemoryStoreOptions, StateStore } from "./store";
37
+ export { memoryStore } from "./store";
37
38
  export type { AnyCommandPayload, SyncPlan } from "./sync";
38
39
  export { planSync } from "./sync";
39
- export type { CommandFile, CommandFolder, CommandLeaf, CommandNode, CommandParent, CommandTree, } from "./tree";
40
- export type { FolderSettings } from "./tree";
40
+ export type { CommandFile, CommandFolder, CommandLeaf, CommandNode, CommandParent, CommandTree, FolderSettings, } from "./tree";
41
41
  export { buildCommandTree, folder } from "./tree";
package/dist/index.js CHANGED
@@ -114,94 +114,11 @@ function cooldown(options) {
114
114
  };
115
115
  }
116
116
 
117
- // src/middleware.ts
118
- function middleware(run) {
119
- return run;
120
- }
121
- function runWithMiddleware(stack, context, handler) {
122
- const call = (index) => {
123
- const current = stack[index];
124
- if (!current)
125
- return handler();
126
- let called = false;
127
- return current(context, () => {
128
- if (called)
129
- throw new Error("A middleware called next() twice.");
130
- called = true;
131
- return call(index + 1);
132
- });
133
- };
134
- return call(0);
135
- }
136
-
137
117
  // src/lifecycle.ts
138
118
  import {
139
119
  InteractionResponseType
140
120
  } from "discord-api-types/v10";
141
121
 
142
- // src/store.ts
143
- function memoryStore(options = {}) {
144
- const {
145
- ttlMs = 24 * 60 * 60 * 1000,
146
- maxEntries = 1e4,
147
- now = Date.now
148
- } = options;
149
- const entries = new Map;
150
- return {
151
- get size() {
152
- return entries.size;
153
- },
154
- set(key, value) {
155
- entries.set(key, { value, expires: now() + ttlMs });
156
- while (entries.size > maxEntries) {
157
- const oldest = entries.keys().next().value;
158
- if (oldest === undefined)
159
- break;
160
- entries.delete(oldest);
161
- }
162
- },
163
- get(key) {
164
- const entry = entries.get(key);
165
- if (!entry)
166
- return;
167
- if (entry.expires <= now()) {
168
- entries.delete(key);
169
- return;
170
- }
171
- return entry.value;
172
- }
173
- };
174
- }
175
- var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
176
- function newStateKey() {
177
- const bytes = crypto.getRandomValues(new Uint8Array(16));
178
- let key = "";
179
- for (const byte of bytes)
180
- key += ALPHABET[byte % 64];
181
- return key;
182
- }
183
- var pending = new Set;
184
- var failures = [];
185
- function trackStateWrite(write) {
186
- if (!(write instanceof Promise))
187
- return;
188
- const tracked = write.then(() => {
189
- pending.delete(tracked);
190
- }, (error) => {
191
- pending.delete(tracked);
192
- failures.push(error);
193
- });
194
- pending.add(tracked);
195
- }
196
- async function settleStateWrites() {
197
- if (pending.size > 0)
198
- await Promise.all([...pending]);
199
- if (failures.length > 0) {
200
- const [first] = failures.splice(0);
201
- throw new NubisError(`Saving component state failed: ${String(first)}`);
202
- }
203
- }
204
-
205
122
  // src/reply.ts
206
123
  import {
207
124
  AllowedMentionsTypes,
@@ -271,6 +188,69 @@ function toReplyMessage(input) {
271
188
  };
272
189
  }
273
190
 
191
+ // src/store.ts
192
+ function memoryStore(options = {}) {
193
+ const {
194
+ ttlMs = 24 * 60 * 60 * 1000,
195
+ maxEntries = 1e4,
196
+ now = Date.now
197
+ } = options;
198
+ const entries = new Map;
199
+ return {
200
+ get size() {
201
+ return entries.size;
202
+ },
203
+ set(key, value) {
204
+ entries.set(key, { value, expires: now() + ttlMs });
205
+ while (entries.size > maxEntries) {
206
+ const oldest = entries.keys().next().value;
207
+ if (oldest === undefined)
208
+ break;
209
+ entries.delete(oldest);
210
+ }
211
+ },
212
+ get(key) {
213
+ const entry = entries.get(key);
214
+ if (!entry)
215
+ return;
216
+ if (entry.expires <= now()) {
217
+ entries.delete(key);
218
+ return;
219
+ }
220
+ return entry.value;
221
+ }
222
+ };
223
+ }
224
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
225
+ function newStateKey() {
226
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
227
+ let key = "";
228
+ for (const byte of bytes)
229
+ key += ALPHABET[byte % 64];
230
+ return key;
231
+ }
232
+ var pending = new Set;
233
+ var failures = [];
234
+ function trackStateWrite(write) {
235
+ if (!(write instanceof Promise))
236
+ return;
237
+ const tracked = write.then(() => {
238
+ pending.delete(tracked);
239
+ }, (error) => {
240
+ pending.delete(tracked);
241
+ failures.push(error);
242
+ });
243
+ pending.add(tracked);
244
+ }
245
+ async function settleStateWrites() {
246
+ if (pending.size > 0)
247
+ await Promise.all([...pending]);
248
+ if (failures.length > 0) {
249
+ const [first] = failures.splice(0);
250
+ throw new NubisError(`Saving component state failed: ${String(first)}`);
251
+ }
252
+ }
253
+
274
254
  // src/lifecycle.ts
275
255
  var defaultScheduler = (run, ms) => {
276
256
  const timer = setTimeout(run, ms);
@@ -409,6 +389,26 @@ function createLifecycle(transport, options) {
409
389
  };
410
390
  }
411
391
 
392
+ // src/middleware.ts
393
+ function middleware(run) {
394
+ return run;
395
+ }
396
+ function runWithMiddleware(stack, context, handler) {
397
+ const call = (index) => {
398
+ const current = stack[index];
399
+ if (!current)
400
+ return handler();
401
+ let called = false;
402
+ return current(context, () => {
403
+ if (called)
404
+ throw new Error("A middleware called next() twice.");
405
+ called = true;
406
+ return call(index + 1);
407
+ });
408
+ };
409
+ return call(0);
410
+ }
411
+
412
412
  // src/dispatch.ts
413
413
  var ERROR_MESSAGE = "Something went wrong. Please try again.";
414
414
  var UNKNOWN_COMMAND_MESSAGE = "This command isn't available anymore. Discord may still be catching up.";
@@ -1046,13 +1046,8 @@ import {
1046
1046
  ApplicationCommandType as ApplicationCommandType2
1047
1047
  } from "discord-api-types/v10";
1048
1048
 
1049
- // src/serialize.ts
1050
- import {
1051
- ApplicationCommandOptionType as ApplicationCommandOptionType2,
1052
- ApplicationCommandType,
1053
- ApplicationIntegrationType,
1054
- InteractionContextType
1055
- } from "discord-api-types/v10";
1049
+ // src/localization.ts
1050
+ import { Locale } from "discord-api-types/v10";
1056
1051
 
1057
1052
  // src/names.ts
1058
1053
  var NAME = /^[-_'\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u;
@@ -1067,7 +1062,53 @@ function assertValidDescription(description, where) {
1067
1062
  }
1068
1063
  }
1069
1064
 
1065
+ // src/localization.ts
1066
+ var LOCALES = new Set(Object.values(Locale));
1067
+ function checkLocales(map, where) {
1068
+ for (const locale of Object.keys(map)) {
1069
+ if (!LOCALES.has(locale)) {
1070
+ throw new NubisError(`${where}: "${locale}" isn't a Discord locale. Use codes like "tr", "de", "en-US" or "pt-BR".`);
1071
+ }
1072
+ }
1073
+ }
1074
+ var entries = (map) => Object.entries(map);
1075
+ function nameLocalizations(map, where) {
1076
+ if (!map)
1077
+ return {};
1078
+ checkLocales(map, where);
1079
+ for (const [locale, name] of entries(map)) {
1080
+ assertValidName(name, `${where} (${locale})`);
1081
+ }
1082
+ return { name_localizations: map };
1083
+ }
1084
+ function descriptionLocalizations(map, where) {
1085
+ if (!map)
1086
+ return {};
1087
+ checkLocales(map, where);
1088
+ for (const [locale, description] of entries(map)) {
1089
+ assertValidDescription(description, `${where} (${locale})`);
1090
+ }
1091
+ return { description_localizations: map };
1092
+ }
1093
+ function labelLocalizations(map, where, max) {
1094
+ if (!map)
1095
+ return {};
1096
+ checkLocales(map, where);
1097
+ for (const [locale, label] of entries(map)) {
1098
+ if (label.trim().length === 0 || label.length > max) {
1099
+ throw new NubisError(`${where} (${locale}): must be 1-${max} characters and not blank (got ${label.length}).`);
1100
+ }
1101
+ }
1102
+ return { name_localizations: map };
1103
+ }
1104
+
1070
1105
  // src/serialize.ts
1106
+ import {
1107
+ ApplicationCommandOptionType as ApplicationCommandOptionType2,
1108
+ ApplicationCommandType,
1109
+ ApplicationIntegrationType,
1110
+ InteractionContextType
1111
+ } from "discord-api-types/v10";
1071
1112
  var OPTION_TYPES = {
1072
1113
  string: ApplicationCommandOptionType2.String,
1073
1114
  integer: ApplicationCommandOptionType2.Integer,
@@ -1114,9 +1155,10 @@ function optionDetails(name, option, where) {
1114
1155
  throw new NubisError(`${at}: integer choices must be whole numbers.`);
1115
1156
  }
1116
1157
  }
1117
- details.choices = choices.map(({ name: label, value }) => ({
1158
+ details.choices = choices.map(({ name: label, value, nameLocalizations: labels }) => ({
1118
1159
  name: label,
1119
- value
1160
+ value,
1161
+ ...labelLocalizations(labels, `${at} choice "${label}"`, MAX_CHOICE_NAME)
1120
1162
  }));
1121
1163
  }
1122
1164
  if (autocomplete)
@@ -1158,6 +1200,7 @@ function serializeOption(name, option, where) {
1158
1200
  type: OPTION_TYPES[option.kind],
1159
1201
  name,
1160
1202
  description,
1203
+ ...localized(option.config, `${where} option "${name}"`),
1161
1204
  required: option.required,
1162
1205
  ...optionDetails(name, option, where)
1163
1206
  };
@@ -1181,6 +1224,7 @@ function serializeChild(node) {
1181
1224
  type: ApplicationCommandOptionType2.Subcommand,
1182
1225
  name: node.name,
1183
1226
  description: node.definition.description,
1227
+ ...localized(node.definition, slash(node.path)),
1184
1228
  options: serializeLeafOptions(node)
1185
1229
  };
1186
1230
  }
@@ -1188,6 +1232,7 @@ function serializeChild(node) {
1188
1232
  type: ApplicationCommandOptionType2.SubcommandGroup,
1189
1233
  name: node.name,
1190
1234
  description: folderDescription(node),
1235
+ ...localized(node.folder ?? {}, `commands/${node.path.join("/")}`),
1191
1236
  options: [...node.children.values()].map((child) => {
1192
1237
  if (child.type !== "command") {
1193
1238
  throw new NubisError(`${slash(child.path)}: subcommand groups can't contain other groups.`);
@@ -1196,6 +1241,10 @@ function serializeChild(node) {
1196
1241
  })
1197
1242
  };
1198
1243
  }
1244
+ var localized = (settings, where) => ({
1245
+ ...nameLocalizations(settings.nameLocalizations, where),
1246
+ ...descriptionLocalizations(settings.descriptionLocalizations, where)
1247
+ });
1199
1248
  var CONTEXTS = {
1200
1249
  guild: InteractionContextType.Guild,
1201
1250
  "bot-dm": InteractionContextType.BotDM,
@@ -1250,6 +1299,7 @@ function toCommandPayloads(tree) {
1250
1299
  type: ApplicationCommandType.ChatInput,
1251
1300
  name: node.name,
1252
1301
  description: node.definition.description,
1302
+ ...localized(node.definition, slash(node.path)),
1253
1303
  options: serializeLeafOptions(node),
1254
1304
  ...topLevelFields(node.definition)
1255
1305
  };
@@ -1258,6 +1308,7 @@ function toCommandPayloads(tree) {
1258
1308
  type: ApplicationCommandType.ChatInput,
1259
1309
  name: node.name,
1260
1310
  description: folderDescription(node),
1311
+ ...localized(node.folder ?? {}, `commands/${node.path.join("/")}`),
1261
1312
  options: [...node.children.values()].map(serializeChild),
1262
1313
  ...topLevelFields(node.folder ?? {})
1263
1314
  };
@@ -1297,6 +1348,7 @@ function toMenuPayloads(menus) {
1297
1348
  return menus.map((menu) => ({
1298
1349
  type: TYPES[menu.kind],
1299
1350
  name: menu.name,
1351
+ ...labelLocalizations(menu.config.nameLocalizations, `Context menu "${menu.name}"`, MAX_NAME2),
1300
1352
  ...topLevelFields(menu.config)
1301
1353
  }));
1302
1354
  }
@@ -1542,13 +1594,23 @@ import {
1542
1594
  ApplicationCommandType as ApplicationCommandType3
1543
1595
  } from "discord-api-types/v10";
1544
1596
  var sorted = (values) => values ? [...values].sort((a, b) => a - b) : null;
1597
+ function localizations(map) {
1598
+ const entries = Object.entries(map ?? {}).filter((entry) => typeof entry[1] === "string");
1599
+ return entries.length === 0 ? null : Object.fromEntries(entries.sort(([a], [b]) => a.localeCompare(b)));
1600
+ }
1545
1601
  function normalizeOption(option) {
1546
1602
  return {
1547
1603
  type: option.type,
1548
1604
  name: option.name,
1605
+ name_localizations: localizations(option.name_localizations),
1549
1606
  description: option.description ?? "",
1607
+ description_localizations: localizations(option.description_localizations),
1550
1608
  required: option.required ?? false,
1551
- choices: option.choices?.map(({ name, value }) => ({ name, value })) ?? null,
1609
+ choices: option.choices?.map(({ name, name_localizations, value }) => ({
1610
+ name,
1611
+ name_localizations: localizations(name_localizations),
1612
+ value
1613
+ })) ?? null,
1552
1614
  autocomplete: option.autocomplete ?? false,
1553
1615
  min_value: option.min_value ?? null,
1554
1616
  max_value: option.max_value ?? null,
@@ -1563,7 +1625,9 @@ function normalize(command) {
1563
1625
  return {
1564
1626
  type: command.type ?? ApplicationCommandType3.ChatInput,
1565
1627
  name: command.name,
1628
+ name_localizations: localizations(command.name_localizations),
1566
1629
  description: command.description ?? "",
1630
+ description_localizations: localizations(command.description_localizations),
1567
1631
  default_member_permissions: command.default_member_permissions ?? null,
1568
1632
  contexts: sorted(command.contexts),
1569
1633
  integration_types: integrationTypes === null || integrationTypes.length === 1 && integrationTypes[0] === 0 ? null : integrationTypes,
@@ -0,0 +1,34 @@
1
+ import { Locale } from "discord-api-types/v10";
2
+ /** One of Discord's locales, as a string: "tr", "en-US", "pt-BR"… */
3
+ export type DiscordLocale = `${Locale}`;
4
+ /** Text in other languages, by Discord locale: `{ tr: "Bir zar at", de: "Würfle" }`. */
5
+ export type Localizations = Readonly<Partial<Record<DiscordLocale, string>>>;
6
+ /**
7
+ * What Discord's command picker shows in other languages. Discord picks the
8
+ * entry for the user's client language, and the plain `name`/`description`
9
+ * everywhere else.
10
+ */
11
+ export interface Localizable {
12
+ /**
13
+ * The name in other languages. Follows the same rules as the name:
14
+ * 1-32 lowercase letters, digits, "-" or "_".
15
+ */
16
+ readonly nameLocalizations?: Localizations;
17
+ /** The description in other languages, 1-100 characters each. */
18
+ readonly descriptionLocalizations?: Localizations;
19
+ }
20
+ /** `name_localizations` for a slash command, option or folder, checked. */
21
+ export declare function nameLocalizations(map: Localizations | undefined, where: string): {
22
+ name_localizations?: Localizations;
23
+ };
24
+ /** `description_localizations`, checked. */
25
+ export declare function descriptionLocalizations(map: Localizations | undefined, where: string): {
26
+ description_localizations?: Localizations;
27
+ };
28
+ /**
29
+ * `name_localizations` for something whose name is free text: a choice
30
+ * (1-100 characters) or a context menu command (1-32).
31
+ */
32
+ export declare function labelLocalizations(map: Localizations | undefined, where: string, max: number): {
33
+ name_localizations?: Localizations;
34
+ };
package/dist/menu.d.ts CHANGED
@@ -4,9 +4,10 @@ import type { TopLevelSettings } from "./command";
4
4
  import type { ModalData } from "./component";
5
5
  import { type DispatchOptions } from "./dispatch";
6
6
  import { type Guard } from "./guard";
7
+ import type { Transport } from "./lifecycle";
8
+ import { type Localizations } from "./localization";
7
9
  import { type Middleware } from "./middleware";
8
10
  import type { ReplyInput } from "./reply";
9
- import type { Transport } from "./lifecycle";
10
11
  interface MenuContextBase {
11
12
  /** Discord's API: act on the user or message. */
12
13
  readonly api: NubisApi;
@@ -32,6 +33,8 @@ export interface MessageMenuContext extends MenuContextBase {
32
33
  interface MenuConfigBase extends TopLevelSettings {
33
34
  /** Shown in the right-click menu: 1-32 characters, spaces and capitals allowed ("Report message"). */
34
35
  readonly name: string;
36
+ /** The name in other languages, by Discord locale; free text like `name`. */
37
+ readonly nameLocalizations?: Localizations;
35
38
  readonly ephemeral?: boolean;
36
39
  readonly guards?: readonly Guard[];
37
40
  readonly middleware?: readonly Middleware[];
package/dist/modal.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type APIModalSubmitInteraction } from "discord-api-types/v10";
2
- import { type AnySchema, type ComponentState, type ModalData, type RenderArgs, type WithRuntime } from "./component";
3
2
  import type { NubisApi } from "./api";
3
+ import { type AnySchema, type ComponentState, type ModalData, type RenderArgs, type WithRuntime } from "./component";
4
4
  import type { Guard } from "./guard";
5
5
  import type { Middleware } from "./middleware";
6
6
  import type { IsRequired } from "./option";
package/dist/option.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { APIApplicationCommandAutocompleteInteraction, APIInteractionDataResolvedChannel, APIRole, APIUser, ChannelType } from "discord-api-types/v10";
2
2
  import type { NubisApi } from "./api";
3
+ import type { Localizable, Localizations } from "./localization";
3
4
  /** What each option kind resolves to inside `run()`. */
4
5
  export interface OptionValues {
5
6
  string: string;
@@ -15,6 +16,8 @@ export type OptionKind = keyof OptionValues;
15
16
  export interface Choice<V extends string | number = string | number> {
16
17
  readonly name: string;
17
18
  readonly value: V;
19
+ /** The choice's name in other languages, by Discord locale. */
20
+ readonly nameLocalizations?: Localizations;
18
21
  }
19
22
  export interface AutocompleteContext {
20
23
  /** What the user has typed into this option so far. Always a string while typing. */
@@ -26,7 +29,7 @@ export interface AutocompleteContext {
26
29
  }
27
30
  /** Suggests values while the user types. Up to 25 are shown; Nubis drops the rest. */
28
31
  export type AutocompleteHandler<V extends string | number> = (context: AutocompleteContext) => readonly Choice<V>[] | Promise<readonly Choice<V>[]>;
29
- interface BaseOptionConfig {
32
+ interface BaseOptionConfig extends Localizable {
30
33
  readonly required?: boolean;
31
34
  /** Shown in Discord's command picker. Defaults to the option name. */
32
35
  readonly description?: string;
package/dist/reply.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type APIActionRowComponent, type APIAllowedMentions, type APIEmbed, type APIInteractionResponseCallbackData, type APIButtonComponent, type APIComponentInMessageActionRow, type APISelectMenuComponent } from "discord-api-types/v10";
1
+ import { type APIActionRowComponent, type APIAllowedMentions, type APIButtonComponent, type APIComponentInMessageActionRow, type APIEmbed, type APIInteractionResponseCallbackData, type APISelectMenuComponent } from "discord-api-types/v10";
2
2
  export type ActionRow = APIActionRowComponent<APIComponentInMessageActionRow>;
3
3
  /** A button or select menu, or a row you've laid out yourself. */
4
4
  export type ComponentInput = APIButtonComponent | APISelectMenuComponent | ActionRow;
package/dist/tree.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { CommandDefinition, TopLevelSettings } from "./command";
2
2
  import type { Guard } from "./guard";
3
+ import type { Localizable } from "./localization";
3
4
  import type { Middleware } from "./middleware";
4
5
  import type { OptionMap } from "./option";
5
6
  /** A command module found on disk: `commands/config/set.ts` has path `["config", "set"]`. */
@@ -17,7 +18,7 @@ export interface CommandLeaf {
17
18
  * Settings for a folder of commands (the loader will read them from a file in
18
19
  * the folder). Guards and middleware apply to every command inside it.
19
20
  */
20
- export interface CommandFolder {
21
+ export interface CommandFolder extends Localizable {
21
22
  readonly path: readonly string[];
22
23
  /** Shown in Discord's command picker. Defaults to the folder name. */
23
24
  readonly description?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubisjs/core",
3
- "version": "0.1.0-next.0",
3
+ "version": "0.1.0-next.2",
4
4
  "description": "Command and option definitions for Nubis, with types inferred from your definitions.",
5
5
  "license": "MIT",
6
6
  "author": "Eytak",