((resolve) => {
height: 16,
}}
/>
- Accounts linked
+ {t`bot.commands.link.body.accounts_linked`}
((resolve) => {
{clanAccountInfo?.clan
? `[${clanAccountInfo?.clan?.tag}] • `
: ''}
- {REGION_NAMES[region]}
+ {translate(`common.regions.normal.${region}`)}
@@ -202,8 +209,8 @@ export const verifyCommand = new Promise((resolve) => {
,
embedInfo(
- 'Incorrect account?',
- 'Use the command again and select a username from the search results.',
+ t`bot.commands.link.embed.title`,
+ t`bot.commands.link.embed.description`,
),
];
},
diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx
index 5fbe47c79..fcfbf4ac2 100644
--- a/src/commands/ratings.tsx
+++ b/src/commands/ratings.tsx
@@ -1,6 +1,6 @@
import {
APIApplicationCommandOptionChoice,
- SlashCommandBuilder,
+ Locale,
SlashCommandSubcommandBuilder,
} from 'discord.js';
import { range } from 'lodash';
@@ -11,7 +11,7 @@ import * as Leaderboard from '../components/Leaderboard';
import TitleBar from '../components/TitleBar';
import { LEAGUES } from '../constants/leagues';
import { FIRST_ARCHIVED_RATINGS_SEASON } from '../constants/ratings';
-import { REGION_NAMES_SHORT, Region } from '../constants/regions';
+import { Region } from '../constants/regions';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getRatingsInfo from '../core/blitz/getRatingsInfo';
@@ -25,8 +25,10 @@ import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchive
import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
-import embedNegative from '../core/discord/embedNegative';
+import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
+import { localizationObject } from '../core/discord/localizationObject';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
+import { translator } from '../core/localization/translator';
import { CommandRegistry } from '../events/interactionCreate';
import { UserError } from '../hooks/userError';
@@ -117,10 +119,15 @@ export interface RatingsNeighbors {
neighbors: RatingsPlayer[];
}
-const noOngoingSeason = embedNegative(
- 'No ongoing season',
- "Wargaming didn't provide any data for this season.",
-);
+interface SimplifiedPlayer {
+ id: number;
+ score: number;
+ position: number;
+ clan: string | undefined;
+ nickname: string;
+}
+
+const DEFAULT_LIMIT = 10;
export const ratingsCommand = new Promise(async (resolve) => {
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
@@ -130,89 +137,131 @@ export const ratingsCommand = new Promise(async (resolve) => {
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
);
- function addSubcommands(option: SlashCommandSubcommandBuilder) {
+ function addOptions(option: SlashCommandSubcommandBuilder) {
+ const { t, translate } = translator(Locale.EnglishUS);
+
return option
- .addStringOption(addUsernameChoices)
.addIntegerOption((option) =>
option
- .setName('limit')
- .setDescription('How many neighbors to display (default: 10)')
+ .setName(t`bot.commands.ratings.options.limit`)
+ .setNameLocalizations(
+ localizationObject('bot.commands.ratings.options.limit'),
+ )
+ .setDescription(
+ translate('bot.commands.ratings.options.limit.description', [
+ `${DEFAULT_LIMIT}`,
+ ]),
+ )
+ .setDescriptionLocalizations(
+ localizationObject(
+ 'bot.commands.ratings.options.limit.description',
+ [`${DEFAULT_LIMIT}`],
+ ),
+ )
.setRequired(false)
.setMinValue(5)
.setMaxValue(30),
)
.addStringOption((option) =>
option
- .setName('season')
- .setDescription('Season number')
+ .setName(t`bot.commands.ratings.options.season`)
+ .setNameLocalizations(
+ localizationObject('bot.commands.ratings.options.season'),
+ )
+ .setDescription(
+ translate('bot.commands.ratings.options.season.description'),
+ )
+ .setDescriptionLocalizations(
+ localizationObject(
+ 'bot.commands.ratings.options.season.description',
+ ),
+ )
.setRequired(false)
.addChoices(
...seasonNumbers
- .map(
- (number, index) =>
- ({
- name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`,
- value: `${number}`,
- }) satisfies APIApplicationCommandOptionChoice,
- )
+ .map((number, index) => {
+ const current =
+ index === seasonNumbers.length - 1 && onGoingSeason;
+
+ return {
+ name: current
+ ? translate(
+ 'bot.commands.ratings.options.season.choices.current',
+ [`${number}`],
+ )
+ : `${number}`,
+ value: `${number}`,
+ name_localizations: current
+ ? localizationObject(
+ 'bot.commands.ratings.options.season.choices.current',
+ [`${number}`],
+ )
+ : undefined,
+ } satisfies APIApplicationCommandOptionChoice;
+ })
.reverse(),
),
);
}
- const command = new SlashCommandBuilder()
- .setName('ratings')
- .setDescription('Ratings battles statistics')
- .addSubcommand((option) =>
- addSubcommands(
- option
- .setName('neighbors')
- .setDescription('Shows neighboring players on the leaderboard'),
- ),
- )
- .addSubcommand((option) =>
- addSubcommands(
- option
- .setName('league')
- .setDescription('Shows top players from a league')
- .addStringOption(addRegionChoices)
- .addStringOption((option) =>
- option
- .setName('league')
- .setDescription('The league to display')
- .addChoices(
- ...LEAGUES.map(
- ({ name }, value) =>
- ({
- name,
- value: `${value}`,
- }) satisfies APIApplicationCommandOptionChoice,
- ),
- )
- .setRequired(true),
- ),
- ),
- );
-
resolve({
inProduction: true,
inPublic: true,
- command,
+ command: createLocalizedCommand('ratings', [
+ {
+ subcommand: 'neighbors',
+ modify(option: SlashCommandSubcommandBuilder) {
+ option.addStringOption(addUsernameChoices);
+ addOptions(option);
+ },
+ },
+ {
+ subcommand: 'league',
+ modify(option: SlashCommandSubcommandBuilder) {
+ option
+ .addStringOption((option) => {
+ const { t, translate } = translator(Locale.EnglishUS);
+
+ return option
+ .setName(t`bot.commands.ratings.options.league`)
+ .setNameLocalizations(
+ localizationObject('bot.commands.ratings.options.league'),
+ )
+ .setDescription(
+ translate('bot.commands.ratings.options.league.description'),
+ )
+ .setDescriptionLocalizations(
+ localizationObject(
+ 'bot.commands.ratings.options.league.description',
+ ),
+ )
+ .addChoices(
+ ...LEAGUES.map(
+ ({ name }, value) =>
+ ({
+ name: translate(`common.leagues.${name}`),
+ value: `${value}`,
+ name_localizations: localizationObject(
+ `common.leagues.${name}`,
+ ),
+ }) satisfies APIApplicationCommandOptionChoice,
+ ),
+ )
+ .setRequired(true);
+ })
+ .addStringOption(addRegionChoices);
+ addOptions(option);
+ },
+ },
+ ]),
async handler(interaction) {
- interface SimplifiedPlayer {
- id: number;
- score: number;
- position: number;
- clan: string | undefined;
- nickname: string;
- }
-
+ const { t, translate } = translator(interaction.locale);
const subcommand = interaction.options.getSubcommand(true) as
| 'league'
| 'neighbors';
- const limit = interaction.options.getInteger('limit') ?? 10;
+ const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
const seasonOption = interaction.options.getString('season');
const season = seasonOption
? parseInt(seasonOption)
@@ -223,7 +272,6 @@ export const ratingsCommand = new Promise(async (resolve) => {
let titleName: string;
let titleImage: string | undefined;
let titleDescription: string;
- let playersBefore: number;
let playersAfter: number;
let players: SimplifiedPlayer[] | undefined;
let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined;
@@ -237,7 +285,8 @@ export const ratingsCommand = new Promise(async (resolve) => {
? getRatingsInfo(region)
: getArchivedRatingsInfo(region, season!));
- if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
+ if (regionRatingsInfo.detail !== undefined)
+ return t`bot.commands.ratings.body.no_ongoing_season`;
const result = isCurrentSeason
? await getRatingsLeague(region, leagueIndex).then(async (result) =>
@@ -280,7 +329,10 @@ export const ratingsCommand = new Promise(async (resolve) => {
position: firstPlayerIndex + index + 1,
clan: clanData[player.id]?.clan?.tag,
nickname:
- clanData[index]?.account_name ?? `Deleted ${player.id}`,
+ clanData[index]?.account_name ??
+ translate('bot.commands.ratings.body.deleted_player', [
+ `${player.id}`,
+ ]),
}) satisfies SimplifiedPlayer,
);
},
@@ -294,23 +346,25 @@ export const ratingsCommand = new Promise(async (resolve) => {
: await getArchivedRatingsMidnightLeaderboard(region, season!);
players = result;
- playersBefore = result ? result[0].position - 1 : 0;
playersAfter =
regionRatingsInfo.count - result[result.length - 1].position;
titleName = `${
- leagueInfo ? leagueInfo.title : 'No Ongoing Season'
- } - ${REGION_NAMES_SHORT[region]}`;
+ leagueInfo ? leagueInfo.title : ''
+ } - ${translate(`common.regions.short.${region}`)}`;
titleImage = leagueInfo.big_icon.startsWith('http')
? leagueInfo.big_icon
: `https:${leagueInfo.big_icon}`;
- titleDescription = `Top ${limit} players`;
+ titleDescription = translate('bot.commands.ratings.body.top_players', [
+ `${limit}`,
+ ]);
} else {
const { region, id } = await resolvePlayerFromCommand(interaction);
regionRatingsInfo = isCurrentSeason
? await getRatingsInfo(region)
: await getArchivedRatingsInfo(region, season!);
- if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
+ if (regionRatingsInfo.detail !== undefined)
+ return t`bot.commands.ratings.body.no_ongoing_season`;
const accountInfo = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
@@ -336,7 +390,10 @@ export const ratingsCommand = new Promise(async (resolve) => {
if (playerIndex === -1) {
throw new UserError(
- `# ${markdownEscape(accountInfo.nickname)} didn't play ratings in season ${season}\nThis player did not participate in this season or did not get past calibration.`,
+ translate('bot.commands.ratings.body.no_participation', [
+ markdownEscape(accountInfo.nickname),
+ `${season}`,
+ ]),
);
}
@@ -373,11 +430,8 @@ export const ratingsCommand = new Promise(async (resolve) => {
titleImage = clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
: undefined;
- titleName = neighbors
- ? accountInfo.nickname
- : `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`;
- titleDescription = 'Ratings neighbors';
- playersBefore = neighbors ? neighbors[0].position - 1 : 0;
+ titleName = neighbors ? accountInfo.nickname : '';
+ titleDescription = t`bot.commands.ratings.body.neighbors`;
playersAfter =
regionRatingsInfo.count - neighbors[neighbors.length - 1].position;
playerId = id;
@@ -428,15 +482,12 @@ export const ratingsCommand = new Promise(async (resolve) => {
{items && (
- {playersBefore > 0 && (
-
- )}
{items}
{playersAfter > 0 && (
)}
diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts
index 4b70afae5..3fe09224a 100644
--- a/src/commands/searchClans.ts
+++ b/src/commands/searchClans.ts
@@ -1,46 +1,68 @@
-import { SlashCommandBuilder } from 'discord.js';
+import { Locale } from 'discord.js';
import markdownEscape from 'markdown-escape';
-import { REGION_NAMES, Region } from '../constants/regions';
+import { Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import fetchBlitz from '../core/blitz/fetchBlitz';
import addClanChoices from '../core/discord/addClanChoices';
import addRegionChoices from '../core/discord/addRegionChoices';
+import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import embedInfo from '../core/discord/embedInfo';
+import { localizationObject } from '../core/discord/localizationObject';
+import { translator } from '../core/localization/translator';
import { CommandRegistry } from '../events/interactionCreate';
import { ClanList } from '../types/clanList';
+const DEFAULT_LIMIT = 25;
+
export const searchClansCommand = new Promise((resolve) => {
resolve({
inProduction: true,
inPublic: true,
- command: new SlashCommandBuilder()
- .setName('search-clans')
- .setDescription('Search clans')
+ command: createLocalizedCommand('search-clans')
.addStringOption(addRegionChoices)
.addStringOption((option) =>
addClanChoices(option).setAutocomplete(false),
)
- .addIntegerOption((option) =>
- option
- .setName('limit')
- .setDescription('The size of the search result (default: 25)')
+ .addIntegerOption((option) => {
+ const { t, translate } = translator(Locale.EnglishUS);
+
+ return option
+ .setName(t`bot.commands.search_clans.options.limit`)
+ .setNameLocalizations(
+ localizationObject('bot.commands.search_clans.options.limit'),
+ )
+ .setDescription(
+ translate('bot.commands.search_clans.options.limit.description', [
+ `${DEFAULT_LIMIT}`,
+ ]),
+ )
+ .setDescriptionLocalizations(
+ localizationObject(
+ 'bot.commands.search_clans.options.limit.description',
+ [`${DEFAULT_LIMIT}`],
+ ),
+ )
.setMinValue(1)
- .setMaxValue(100),
- ),
+ .setMaxValue(100);
+ }),
async handler(interaction) {
+ const { translate } = translator(interaction.locale);
const server = interaction.options.getString('region') as Region;
const clan = interaction.options.getString('clan')!;
- const limit = interaction.options.getInteger('limit') ?? 25;
+ const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
const clanList = await fetchBlitz(
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`,
);
return embedInfo(
- `Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`,
+ translate('bot.commands.search_clans.body.title', [
+ markdownEscape(clan),
+ translate(`common.regions.normal.${server}`),
+ ]),
clanList.length === 0
- ? 'No clans found.'
+ ? translate('bot.commands.search_clans.body.no_results')
: `\`\`\`\n${clanList
.map((clan) => `${clan.name} [${clan.tag}]`)
.join('\n')}\n\`\`\``,
diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts
index 11f642735..aa93d115b 100644
--- a/src/commands/searchPlayers.ts
+++ b/src/commands/searchPlayers.ts
@@ -1,6 +1,6 @@
-import { SlashCommandBuilder } from 'discord.js';
+import { Locale } from 'discord.js';
import markdownEscape from 'markdown-escape';
-import { REGION_NAMES, Region } from '../constants/regions';
+import { Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import fetchBlitz from '../core/blitz/fetchBlitz';
import {
@@ -9,25 +9,43 @@ import {
} from '../core/blitz/searchPlayersAcrossRegions';
import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
+import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import embedInfo from '../core/discord/embedInfo';
+import { localizationObject } from '../core/discord/localizationObject';
+import { translator } from '../core/localization/translator';
import { CommandRegistry } from '../events/interactionCreate';
+const DEFAULT_LIMIT = 25;
+
export const searchPlayersCommand = new Promise((resolve) => {
+ const { t, translate } = translator(Locale.EnglishUS);
+
resolve({
inProduction: true,
inPublic: true,
- command: new SlashCommandBuilder()
- .setName('search-players')
- .setDescription('Search players in a Blitz server')
+ command: createLocalizedCommand('search-players')
.addStringOption(addRegionChoices)
.addStringOption((option) =>
addUsernameChoices(option).setAutocomplete(false).setRequired(true),
)
.addIntegerOption((option) =>
option
- .setName('limit')
- .setDescription('The size of the search result (default: 25)')
+ .setName(t`bot.commands.search_players.options.limit`)
+ .setNameLocalizations(
+ localizationObject('bot.commands.search_players.options.limit'),
+ )
+ .setDescription(
+ translate('bot.commands.search_players.options.limit.description', [
+ `${DEFAULT_LIMIT}`,
+ ]),
+ )
+ .setDescriptionLocalizations(
+ localizationObject(
+ 'bot.commands.search_players.options.limit.description',
+ [`${DEFAULT_LIMIT}`],
+ ),
+ )
.setMinValue(1)
.setMaxValue(100),
),
@@ -46,10 +64,13 @@ export const searchPlayersCommand = new Promise((resolve) => {
: [];
return embedInfo(
- `Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`,
+ translate('bot.commands.search_players.body.title', [
+ markdownEscape(trimmedSearch),
+ translate(`common.regions.normal.${server}`),
+ ]),
`\`\`\`${
players.length === 0
- ? 'No players found.'
+ ? translate('bot.commands.search_players.body.no_results')
: players.map((player) => player.nickname).join('\n')
}\`\`\``,
);
diff --git a/src/constants/leagues.ts b/src/constants/leagues.ts
index 082886490..ee77accb4 100644
--- a/src/constants/leagues.ts
+++ b/src/constants/leagues.ts
@@ -1,7 +1,7 @@
export const LEAGUES = [
- { name: 'Diamond', minScore: 5000 },
- { name: 'Platinum', minScore: 4000 },
- { name: 'Gold', minScore: 3000 },
- { name: 'Silver', minScore: 2000 },
- { name: 'Bronze', minScore: 0 },
+ { name: 'diamond', minScore: 5000 },
+ { name: 'platinum', minScore: 4000 },
+ { name: 'gold', minScore: 3000 },
+ { name: 'silver', minScore: 2000 },
+ { name: 'bronze', minScore: 0 },
];
diff --git a/src/constants/regions.ts b/src/constants/regions.ts
index d6e088235..85e84a7d0 100644
--- a/src/constants/regions.ts
+++ b/src/constants/regions.ts
@@ -3,21 +3,11 @@ export const REGIONS = ['com', 'eu', 'asia'] as const;
export type Region = (typeof REGIONS)[number];
export type RegionSubdomain = 'na' | 'eu' | 'asia';
-export const REGION_NAMES: Record = {
+/**
+ * @deprecated
+ */
+export const UNLOCALIZED_REGION_NAMES: Record = {
com: 'North America',
- eu: 'Europe',
asia: 'Asia',
-};
-
-// https://planetarynames.wr.usgs.gov/Abbreviations
-export const REGION_NAMES_SHORT: Record = {
- com: 'NA',
- eu: 'EU',
- asia: 'ASIA',
-};
-
-export const REGION_SUBDOMAIN_NAMES: Record = {
- na: 'North America',
eu: 'Europe',
- asia: 'Asia',
};
diff --git a/src/core/discord/addRegionChoices.ts b/src/core/discord/addRegionChoices.ts
index eb0042a5d..9e53ca323 100644
--- a/src/core/discord/addRegionChoices.ts
+++ b/src/core/discord/addRegionChoices.ts
@@ -1,14 +1,33 @@
-import { SlashCommandStringOption } from 'discord.js';
-import { REGION_NAMES } from '../../constants/regions';
+import {
+ APIApplicationCommandOptionChoice,
+ Locale,
+ SlashCommandStringOption,
+} from 'discord.js';
+import { REGIONS } from '../../constants/regions';
+import { translator } from '../localization/translator';
+import { localizationObject } from './localizationObject';
export default function addRegionChoices(option: SlashCommandStringOption) {
+ const { t, translate } = translator(Locale.EnglishUS);
+
return option
- .setName('region')
- .setDescription('The Blitz region you are in')
+ .setName(t`bot.common.options.region`)
+ .setNameLocalizations(localizationObject('bot.common.options.region'))
+ .setDescription(t`bot.common.options.region.description`)
+ .setDescriptionLocalizations(
+ localizationObject('bot.common.options.region.description'),
+ )
.addChoices(
- { name: REGION_NAMES.com, value: 'com' },
- { name: REGION_NAMES.eu, value: 'eu' },
- { name: REGION_NAMES.asia, value: 'asia' },
+ ...REGIONS.map(
+ (region) =>
+ ({
+ value: region,
+ name: translate(`common.regions.normal.${region}`),
+ name_localizations: localizationObject(
+ `common.regions.normal.${region}`,
+ ),
+ }) satisfies APIApplicationCommandOptionChoice,
+ ),
)
.setRequired(true);
}
diff --git a/src/core/discord/autocompleteClan.ts b/src/core/discord/autocompleteClan.ts
index bbbd9a2de..142282ad2 100644
--- a/src/core/discord/autocompleteClan.ts
+++ b/src/core/discord/autocompleteClan.ts
@@ -3,8 +3,8 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
-import { REGION_NAMES_SHORT } from '../../constants/regions';
import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions';
+import { translator } from '../localization/translator';
export const DISCORD_CHOICES_MAX_NAME_SIZE = 25;
export const OVERFLOW_SUFFIX = '…';
@@ -12,6 +12,7 @@ export const OVERFLOW_SUFFIX = '…';
export default async function autocompleteClan(
interaction: AutocompleteInteraction,
) {
+ const { translate } = translator(interaction.locale);
const focusedOption = interaction.options.getFocused(true);
if (focusedOption.name !== 'clan') return;
if (focusedOption.value.length < 2) return interaction.respond([]);
@@ -20,7 +21,9 @@ export default async function autocompleteClan(
await interaction.respond(
clans
? clans.map((clan) => {
- let name = `${clan.name} (${REGION_NAMES_SHORT[clan.region]})`;
+ let name = `${clan.name} (${translate(
+ `common.regions.short.${clan.region}`,
+ )})`;
if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
@@ -28,7 +31,9 @@ export default async function autocompleteClan(
name = `${clan.name.slice(
0,
clan.name.length - overSize - OVERFLOW_SUFFIX.length,
- )}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[clan.region]})`;
+ )}${OVERFLOW_SUFFIX} (${translate(
+ `common.regions.short.${clan.region}`,
+ )})`;
}
return {
diff --git a/src/core/discord/autocompleteUsername.ts b/src/core/discord/autocompleteUsername.ts
index 333b51624..a41b3860c 100644
--- a/src/core/discord/autocompleteUsername.ts
+++ b/src/core/discord/autocompleteUsername.ts
@@ -3,8 +3,8 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
-import { REGION_NAMES_SHORT } from '../../constants/regions';
import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions';
+import { translator } from '../localization/translator';
import {
DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX,
@@ -13,6 +13,7 @@ import {
export default async function autocompleteUsername(
interaction: AutocompleteInteraction,
) {
+ const { translate } = translator(interaction.locale);
const focusedOption = interaction.options.getFocused(true);
if (focusedOption.name !== 'username') return;
const players = await searchPlayersAcrossRegions(focusedOption.value);
@@ -21,9 +22,9 @@ export default async function autocompleteUsername(
await interaction.respond(
players
? players.map((player) => {
- let name = `${player.nickname} (${
- REGION_NAMES_SHORT[player.region]
- })`;
+ let name = `${player.nickname} (${translate(
+ `common.regions.short.${player.region}`,
+ )})`;
if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
@@ -31,7 +32,9 @@ export default async function autocompleteUsername(
name = `${player.nickname.slice(
0,
player.nickname.length - overSize - OVERFLOW_SUFFIX.length,
- )}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[player.region]})`;
+ )}${OVERFLOW_SUFFIX} (${translate(
+ `common.regions.short.${player.region}`,
+ )})`;
}
return {
diff --git a/src/core/discord/localizationObject.ts b/src/core/discord/localizationObject.ts
index 28f2f0e90..2a41e7963 100644
--- a/src/core/discord/localizationObject.ts
+++ b/src/core/discord/localizationObject.ts
@@ -2,13 +2,13 @@ import { Locale } from 'discord.js';
import { locales } from '../localization/strings';
import { translator } from '../localization/translator';
-export function localizationObject(path: string) {
+export function localizationObject(path: string, literals?: string[]) {
return locales.reduce>>(
(localizations, locale) => {
const { translate } = translator(locale);
return {
...localizations,
- [locale]: translate(path),
+ [locale]: translate(path, literals),
};
},
{},
diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts
index 3008bf026..3f21ef942 100644
--- a/src/events/interactionCreate/index.ts
+++ b/src/events/interactionCreate/index.ts
@@ -101,8 +101,15 @@ export const COMMANDS_RAW: Promise[] = [
export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
return rawCommands.reduce>(
- (commands, registry) => {
- if (registry.status === 'rejected') return commands;
+ (commands, registry, index) => {
+ if (registry.status === 'rejected') {
+ console.warn(
+ `Command ${index} failed to load; skipping...`,
+ registry.reason,
+ );
+
+ return commands;
+ }
if (isDev()) registry.value.command.setDefaultMemberPermissions(0);
return { ...commands, [registry.value.command.name]: registry.value };
},
diff --git a/src/lang/en-US.json b/src/lang/en-US.json
index 9f5868b84..fcc60b649 100644
--- a/src/lang/en-US.json
+++ b/src/lang/en-US.json
@@ -23,6 +23,27 @@
"researchable": "Tech tree",
"premium": "Premium",
"collector": "Collector"
+ },
+
+ "regions": {
+ "normal": {
+ "com": "North America",
+ "eu": "Europe",
+ "asia": "Asia"
+ },
+ "short": {
+ "com": "NA",
+ "eu": "EU",
+ "asia": "ASIA"
+ }
+ },
+
+ "leagues": {
+ "diamond": "Diamond",
+ "platinum": "Platinum",
+ "gold": "Gold",
+ "silver": "Silver",
+ "bronze": "Bronze"
}
},
@@ -109,6 +130,11 @@
"$": "tree-type",
"description": "Tree type"
}
+ },
+
+ "region": {
+ "$": "region",
+ "description": "Region"
}
},
@@ -261,6 +287,105 @@
}
}
}
+ },
+
+ "link": {
+ "$": "link",
+ "description": "Links your Blitz and Discord account",
+
+ "body": {
+ "accounts_linked": "Accounts linked",
+ "no_role_permissions": {
+ "$": "%s failed to verify",
+ "description": "I don't have the permission to change your manage roles."
+ }
+ },
+
+ "embed": {
+ "title": "Incorrect account?",
+ "description": "Use the command again and select a username from the search results."
+ }
+ },
+
+ "search_clans": {
+ "$": "search-clans",
+ "description": "Search clans",
+
+ "options": {
+ "limit": {
+ "$": "limit",
+ "description": "The max number of clan results (default: %s)"
+ }
+ },
+
+ "body": {
+ "title": "Clan search for %s in region %s",
+ "no_results": "No clans found"
+ }
+ },
+
+ "search_players": {
+ "$": "search-players",
+ "description": "Search players",
+
+ "options": {
+ "limit": {
+ "$": "limit",
+ "description": "The max number of player results (default: %s)"
+ }
+ },
+
+ "body": {
+ "title": "Player search for %s in region %s",
+ "no_results": "No players found"
+ }
+ },
+
+ "ratings": {
+ "$": "ratings",
+ "description": "Ratings battles statistics",
+
+ "subcommands": {
+ "neighbors": {
+ "$": "neighbors",
+ "description": "Neighboring players on the leaderboard"
+ },
+
+ "league": {
+ "$": "league",
+ "description": "Top players of a league"
+ }
+ },
+
+ "options": {
+ "limit": {
+ "$": "limit",
+ "description": "How many neighbors to display (default: %s)"
+ },
+
+ "season": {
+ "$": "season",
+ "description": "Season number (default: current)",
+
+ "choices": {
+ "current": "%s (current)"
+ }
+ },
+
+ "league": {
+ "$": "league",
+ "description": "The league to display"
+ }
+ },
+
+ "body": {
+ "top_players": "Top %s players",
+ "no_participation": "# %s didn't play ratings in season %s\n\nThis player did not participate in this season or did not get past calibration.",
+ "no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running ratings season.",
+ "deleted_player": "Deleted %s",
+ "neighbors": "Ratings neighbors",
+ "more": "+%s more"
+ }
}
}
},