mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-17 20:13:30 +01:00
feat: Slash command localization for builders (#7683)
* feat: add slash command localizations * chore: make requested changes * chore: make requested changes * fix: prevent unnecessary spread * chore: make requested changes * chore: don't allow maps
This commit is contained in:
@@ -420,5 +420,61 @@ describe('Slash Commands', () => {
|
|||||||
expect(() => getSubcommand().addBooleanOption(getBooleanOption()).toJSON()).not.toThrowError();
|
expect(() => getSubcommand().addBooleanOption(getBooleanOption()).toJSON()).not.toThrowError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Slash command localizations', () => {
|
||||||
|
const expectedSingleLocale = { 'en-US': 'foobar' };
|
||||||
|
const expectedMultipleLocales = {
|
||||||
|
...expectedSingleLocale,
|
||||||
|
bg: 'test',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('GIVEN valid name localizations THEN does not throw error', () => {
|
||||||
|
expect(() => getBuilder().setNameLocalization('en-US', 'foobar')).not.toThrowError();
|
||||||
|
expect(() => getBuilder().setNameLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid name localizations THEN does not throw error', () => {
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => getBuilder().setNameLocalization('en-U', 'foobar')).toThrowError();
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => getBuilder().setNameLocalizations({ 'en-U': 'foobar' })).toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid name localizations THEN valid data is stored', () => {
|
||||||
|
expect(getBuilder().setNameLocalization('en-US', 'foobar').name_localizations).toEqual(expectedSingleLocale);
|
||||||
|
expect(getBuilder().setNameLocalizations({ 'en-US': 'foobar', bg: 'test' }).name_localizations).toEqual(
|
||||||
|
expectedMultipleLocales,
|
||||||
|
);
|
||||||
|
expect(getBuilder().setNameLocalizations(null).name_localizations).toBeNull();
|
||||||
|
expect(getBuilder().setNameLocalization('en-US', null).name_localizations).toEqual({
|
||||||
|
'en-US': null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid description localizations THEN does not throw error', () => {
|
||||||
|
expect(() => getBuilder().setDescriptionLocalization('en-US', 'foobar')).not.toThrowError();
|
||||||
|
expect(() => getBuilder().setDescriptionLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid description localizations THEN does not throw error', () => {
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => getBuilder().setDescriptionLocalization('en-U', 'foobar')).toThrowError();
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => getBuilder().setDescriptionLocalizations({ 'en-U': 'foobar' })).toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GIVEN valid description localizations THEN valid data is stored', () => {
|
||||||
|
expect(getBuilder().setDescriptionLocalization('en-US', 'foobar').description_localizations).toEqual(
|
||||||
|
expectedSingleLocale,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
getBuilder().setDescriptionLocalizations({ 'en-US': 'foobar', bg: 'test' }).description_localizations,
|
||||||
|
).toEqual(expectedMultipleLocales);
|
||||||
|
expect(getBuilder().setDescriptionLocalizations(null).description_localizations).toBeNull();
|
||||||
|
expect(getBuilder().setDescriptionLocalization('en-US', null).description_localizations).toEqual({
|
||||||
|
'en-US': null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sapphire/shapeshift": "^2.0.0",
|
"@sapphire/shapeshift": "^2.0.0",
|
||||||
"@sindresorhus/is": "^4.4.0",
|
"@sindresorhus/is": "^4.4.0",
|
||||||
"discord-api-types": "^0.29.0",
|
"discord-api-types": "^0.31.1",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"ts-mixer": "^6.0.0",
|
"ts-mixer": "^6.0.0",
|
||||||
"tslib": "^2.3.1"
|
"tslib": "^2.3.1"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import is from '@sindresorhus/is';
|
import is from '@sindresorhus/is';
|
||||||
import type { APIApplicationCommandOptionChoice } from 'discord-api-types/v10';
|
import { type APIApplicationCommandOptionChoice, Locale } from 'discord-api-types/v10';
|
||||||
import { s } from '@sapphire/shapeshift';
|
import { s } from '@sapphire/shapeshift';
|
||||||
import type { ApplicationCommandOptionBase } from './mixins/ApplicationCommandOptionBase';
|
import type { ApplicationCommandOptionBase } from './mixins/ApplicationCommandOptionBase';
|
||||||
import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder';
|
import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder';
|
||||||
@@ -15,12 +15,16 @@ export function validateName(name: unknown): asserts name is string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const descriptionPredicate = s.string.lengthGe(1).lengthLe(100);
|
const descriptionPredicate = s.string.lengthGe(1).lengthLe(100);
|
||||||
|
const localePredicate = s.nativeEnum(Locale);
|
||||||
|
|
||||||
export function validateDescription(description: unknown): asserts description is string {
|
export function validateDescription(description: unknown): asserts description is string {
|
||||||
descriptionPredicate.parse(description);
|
descriptionPredicate.parse(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxArrayLengthPredicate = s.unknown.array.lengthLe(25);
|
const maxArrayLengthPredicate = s.unknown.array.lengthLe(25);
|
||||||
|
export function validateLocale(locale: unknown) {
|
||||||
|
return localePredicate.parse(locale);
|
||||||
|
}
|
||||||
|
|
||||||
export function validateMaxOptionsLength(options: unknown): asserts options is ToAPIApplicationCommandOptions[] {
|
export function validateMaxOptionsLength(options: unknown): asserts options is ToAPIApplicationCommandOptions[] {
|
||||||
maxArrayLengthPredicate.parse(options);
|
maxArrayLengthPredicate.parse(options);
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import type { APIApplicationCommandOption, RESTPostAPIApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
import type {
|
||||||
|
APIApplicationCommandOption,
|
||||||
|
LocalizationMap,
|
||||||
|
RESTPostAPIApplicationCommandsJSONBody,
|
||||||
|
} from 'discord-api-types/v10';
|
||||||
import { mix } from 'ts-mixer';
|
import { mix } from 'ts-mixer';
|
||||||
import {
|
import {
|
||||||
assertReturnOfBuilder,
|
assertReturnOfBuilder,
|
||||||
@@ -17,11 +21,21 @@ export class SlashCommandBuilder {
|
|||||||
*/
|
*/
|
||||||
public readonly name: string = undefined!;
|
public readonly name: string = undefined!;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The localized names for this command
|
||||||
|
*/
|
||||||
|
public readonly name_localizations?: LocalizationMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The description of this slash command
|
* The description of this slash command
|
||||||
*/
|
*/
|
||||||
public readonly description: string = undefined!;
|
public readonly description: string = undefined!;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The localized descriptions for this command
|
||||||
|
*/
|
||||||
|
public readonly description_localizations?: LocalizationMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options of this slash command
|
* The options of this slash command
|
||||||
*/
|
*/
|
||||||
@@ -44,7 +58,9 @@ export class SlashCommandBuilder {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
name_localizations: this.name_localizations,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
|
description_localizations: this.description_localizations,
|
||||||
options: this.options.map((option) => option.toJSON()),
|
options: this.options.map((option) => option.toJSON()),
|
||||||
default_permission: this.defaultPermission,
|
default_permission: this.defaultPermission,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { validateDescription, validateName } from '../Assertions';
|
import type { LocaleString, LocalizationMap } from 'discord-api-types/v10';
|
||||||
|
import { validateDescription, validateLocale, validateName } from '../Assertions';
|
||||||
|
|
||||||
export class SharedNameAndDescription {
|
export class SharedNameAndDescription {
|
||||||
public readonly name!: string;
|
public readonly name!: string;
|
||||||
|
public readonly name_localizations?: LocalizationMap;
|
||||||
public readonly description!: string;
|
public readonly description!: string;
|
||||||
|
public readonly description_localizations?: LocalizationMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the name
|
* Sets the name
|
||||||
@@ -31,4 +34,85 @@ export class SharedNameAndDescription {
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a name localization
|
||||||
|
*
|
||||||
|
* @param locale The locale to set a description for
|
||||||
|
* @param localizedName The localized description for the given locale
|
||||||
|
*/
|
||||||
|
public setNameLocalization(locale: LocaleString, localizedName: string | null) {
|
||||||
|
if (!this.name_localizations) {
|
||||||
|
Reflect.set(this, 'name_localizations', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localizedName === null) {
|
||||||
|
this.name_localizations![locale] = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateName(localizedName);
|
||||||
|
|
||||||
|
this.name_localizations![validateLocale(locale)] = localizedName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name localizations
|
||||||
|
*
|
||||||
|
* @param localizedNames The dictionary of localized descriptions to set
|
||||||
|
*/
|
||||||
|
public setNameLocalizations(localizedNames: LocalizationMap | null) {
|
||||||
|
if (localizedNames === null) {
|
||||||
|
Reflect.set(this, 'name_localizations', null);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect.set(this, 'name_localizations', {});
|
||||||
|
|
||||||
|
Object.entries(localizedNames).forEach((args) =>
|
||||||
|
this.setNameLocalization(...(args as [LocaleString, string | null])),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a description localization
|
||||||
|
*
|
||||||
|
* @param locale The locale to set a description for
|
||||||
|
* @param localizedDescription The localized description for the given locale
|
||||||
|
*/
|
||||||
|
public setDescriptionLocalization(locale: LocaleString, localizedDescription: string | null) {
|
||||||
|
if (!this.description_localizations) {
|
||||||
|
Reflect.set(this, 'description_localizations', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localizedDescription === null) {
|
||||||
|
this.description_localizations![locale] = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDescription(localizedDescription);
|
||||||
|
|
||||||
|
this.description_localizations![validateLocale(locale)] = localizedDescription;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description localizations
|
||||||
|
*
|
||||||
|
* @param localizedDescriptions The dictionary of localized descriptions to set
|
||||||
|
*/
|
||||||
|
public setDescriptionLocalizations(localizedDescriptions: LocalizationMap | null) {
|
||||||
|
if (localizedDescriptions === null) {
|
||||||
|
Reflect.set(this, 'description_localizations', null);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect.set(this, 'description_localizations', {});
|
||||||
|
Object.entries(localizedDescriptions).forEach((args) =>
|
||||||
|
this.setDescriptionLocalization(...(args as [LocaleString, string | null])),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1765,7 +1765,7 @@ __metadata:
|
|||||||
"@typescript-eslint/eslint-plugin": ^5.11.0
|
"@typescript-eslint/eslint-plugin": ^5.11.0
|
||||||
"@typescript-eslint/parser": ^5.11.0
|
"@typescript-eslint/parser": ^5.11.0
|
||||||
babel-plugin-transform-typescript-metadata: ^0.3.2
|
babel-plugin-transform-typescript-metadata: ^0.3.2
|
||||||
discord-api-types: ^0.29.0
|
discord-api-types: ^0.31.1
|
||||||
eslint: ^8.9.0
|
eslint: ^8.9.0
|
||||||
eslint-config-marine: ^9.3.2
|
eslint-config-marine: ^9.3.2
|
||||||
eslint-config-prettier: ^8.3.0
|
eslint-config-prettier: ^8.3.0
|
||||||
@@ -4436,6 +4436,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"discord-api-types@npm:^0.31.1":
|
||||||
|
version: 0.31.1
|
||||||
|
resolution: "discord-api-types@npm:0.31.1"
|
||||||
|
checksum: 5a18e512b549b75d55892b0dbc2dc7c46526bee0d001b73d41d144f4653de847c96b9c57342a32479af738f46acd80ee21686dced9fe0184dcac86b669b31f18
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"discord.js@workspace:packages/discord.js":
|
"discord.js@workspace:packages/discord.js":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "discord.js@workspace:packages/discord.js"
|
resolution: "discord.js@workspace:packages/discord.js"
|
||||||
|
|||||||
Reference in New Issue
Block a user