feat: more command locales

This commit is contained in:
TrèsAbhi committed 2024-03-08 20:29:35 -06:00
1 parent 38ca1499e2
commit b3a50f2b27
11 files changed
+185 -78

No files matched your search

+7 -5
View File
@@ -1,4 +1,3 @@
import { SlashCommandBuilder } from 'discord.js';
import CommandWrapper from '../components/CommandWrapper'; import CommandWrapper from '../components/CommandWrapper';
import NoData from '../components/NoData'; import NoData from '../components/NoData';
import * as Tanks from '../components/Tanks'; import * as Tanks from '../components/Tanks';
@@ -18,7 +17,9 @@ import { tankIcon } from '../core/blitzkrieg/tankIcon';
import addTierChoices from '../core/discord/addTierChoices'; import addTierChoices from '../core/discord/addTierChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { translator } from '../core/localization/translator';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => { export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
@@ -26,13 +27,12 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: createLocalizedCommand('owned-tanks')
.setName('owned-tanks')
.setDescription("Shows a player's owned tanks")
.addStringOption(addTierChoices) .addStringOption(addTierChoices)
.addStringOption(addUsernameChoices), .addStringOption(addUsernameChoices),
async handler(interaction) { async handler(interaction) {
const { translate } = translator(interaction.locale);
const tier = Number(interaction.options.getString('tier')); const tier = Number(interaction.options.getString('tier'));
const account = await resolvePlayerFromCommand(interaction); const account = await resolvePlayerFromCommand(interaction);
const { id, region: server } = account; const { id, region: server } = account;
@@ -70,7 +70,9 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
? emblemIdToURL(clanAccountInfo.clan.emblem_set_id) ? emblemIdToURL(clanAccountInfo.clan.emblem_set_id)
: undefined : undefined
} }
description={`Tier ${TIER_ROMAN_NUMERALS[tier as Tier]} tanks`} description={translate('bot.commands.owned_tanks.body.subtitle', [
TIER_ROMAN_NUMERALS[tier as Tier],
])}
/> />
{filteredTanks.length === 0 && ( {filteredTanks.length === 0 && (
+8 -8
View File
@@ -1,4 +1,5 @@
import { SlashCommandBuilder } from 'discord.js'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from '../core/localization/translator';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const permissionsCommand = new Promise<CommandRegistry>((resolve) => { export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
@@ -6,26 +7,25 @@ export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: createLocalizedCommand('permissions'),
.setName('permissions')
.setDescription('Checks wether the bot has the required permissions'),
async handler(interaction) { async handler(interaction) {
const { t, translate } = translator(interaction.locale);
// friday the 13th permissions easter egg // friday the 13th permissions easter egg
const isFridayThe13th = const isFridayThe13th =
new Date().getDay() === 5 && new Date().getDate() === 13; new Date().getDay() === 5 && new Date().getDate() === 13;
const permissions = [ const permissions = [
[ [
interaction.appPermissions?.has('ViewChannel'), interaction.appPermissions?.has('ViewChannel'),
'View channels: needed for the refresh button to work', t`bot.commands.permissions.body.view_channel`,
], ],
[ [
interaction.appPermissions?.has('ReadMessageHistory'), interaction.appPermissions?.has('ReadMessageHistory'),
'Read message history: also needed for the refresh button to work', t`bot.commands.permissions.body.read_message_history`,
], ],
[ [
interaction.appPermissions?.has('AttachFiles'), interaction.appPermissions?.has('AttachFiles'),
'Attach files: needed for image-based commands to work', t`bot.commands.permissions.body.attach_files`,
], ],
]; ];
const body = permissions const body = permissions
@@ -38,7 +38,7 @@ export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
}) })
.join('\n'); .join('\n');
return `# Permissions\n${body}`; return translate('bot.commands.permissions.body.title', [body]);
}, },
}); });
}); });
+13 -12
View File
@@ -1,4 +1,5 @@
import { SlashCommandBuilder } from 'discord.js'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from '../core/localization/translator';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const pingCommand = new Promise<CommandRegistry>((resolve) => { export const pingCommand = new Promise<CommandRegistry>((resolve) => {
@@ -7,28 +8,28 @@ export const pingCommand = new Promise<CommandRegistry>((resolve) => {
inPublic: true, inPublic: true,
handlesInteraction: true, handlesInteraction: true,
command: new SlashCommandBuilder() command: createLocalizedCommand('ping', [
.setName('ping') { subcommand: 'blitzkrieg' },
.setDescription('Check if the bot is alive') { subcommand: 'wotb' },
.addSubcommand((option) => ]),
option.setName('blitzkrieg').setDescription('Ping Blitzkrieg'),
)
.addSubcommand((option) =>
option.setName('blitz').setDescription('Ping Blitz'),
),
async handler(interaction) { async handler(interaction) {
const { t, translate } = translator(interaction.locale);
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
const executionStart = Date.now(); const executionStart = Date.now();
if (subcommand === 'blitzkrieg') { if (subcommand === 'blitzkrieg') {
await interaction.editReply('Pong 🏓'); await interaction.editReply(t`bot.commands.ping.body.pong`);
} else { } else {
await fetch('https://api.wotblitz.com/'); await fetch('https://api.wotblitz.com/');
} }
const executionTime = Date.now() - executionStart; const executionTime = Date.now() - executionStart;
interaction.editReply(`Pong 🏓 - ${executionTime}ms`); interaction.editReply(
translate('bot.commands.ping.body.pong_with_time', [
`${Math.round(executionTime)}`,
]),
);
}, },
}); });
}); });
+2
View File
@@ -256,6 +256,8 @@ export const ratingsCommand = new Promise<CommandRegistry>(async (resolve) => {
}, },
]), ]),
// BIG TODO: title seems to be in english
async handler(interaction) { async handler(interaction) {
const { t, translate } = translator(interaction.locale); const { t, translate } = translator(interaction.locale);
const subcommand = interaction.options.getSubcommand(true) as const subcommand = interaction.options.getSubcommand(true) as
+1
View File
@@ -51,6 +51,7 @@ export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
), ),
async handler(interaction) { async handler(interaction) {
const { translate } = translator(interaction.locale);
const server = interaction.options.getString('region') as Region; const server = interaction.options.getString('region') as Region;
const name = interaction.options.getString('username')!; const name = interaction.options.getString('username')!;
const limit = interaction.options.getInteger('limit') ?? 25; const limit = interaction.options.getInteger('limit') ?? 25;
+11 -10
View File
@@ -1,4 +1,4 @@
import { Locale, SlashCommandBuilder } from 'discord.js'; import { Locale } from 'discord.js';
import AllStatsOverview from '../components/AllStatsOverview'; import AllStatsOverview from '../components/AllStatsOverview';
import CommandWrapper from '../components/CommandWrapper'; import CommandWrapper from '../components/CommandWrapper';
import NoData from '../components/NoData'; import NoData from '../components/NoData';
@@ -15,6 +15,7 @@ import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import { buttonRefresh } from '../core/discord/buttonRefresh'; import { buttonRefresh } from '../core/discord/buttonRefresh';
import commandToURL from '../core/discord/commandToURL'; import commandToURL from '../core/discord/commandToURL';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import { getFiltersFromButton } from '../core/discord/getFiltersFromButton'; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand'; import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
@@ -52,25 +53,25 @@ async function render(
{!stats.battles && <NoData type="battles_in_period" locale={locale} />} {!stats.battles && <NoData type="battles_in_period" locale={locale} />}
{stats.battles > 0 && ( {stats.battles > 0 && (
<AllStatsOverview stats={stats} supplementaryStats={supplementary} /> <AllStatsOverview
locale={locale}
stats={stats}
supplementaryStats={supplementary}
/>
)} )}
</CommandWrapper> </CommandWrapper>
); );
} }
export const statsCommand = new Promise<CommandRegistry>(async (resolve) => { export const statsCommand = new Promise<CommandRegistry>(async (resolve) => {
const command = await addPeriodicFilterOptions(
new SlashCommandBuilder()
.setName('stats')
.setDescription('Regular battles statistics'),
(option) => option.addStringOption(addUsernameChoices),
);
resolve({ resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
command, command: await addPeriodicFilterOptions(
createLocalizedCommand('stats'),
(option) => option.addStringOption(addUsernameChoices),
),
async handler(interaction) { async handler(interaction) {
const player = await resolvePlayerFromCommand(interaction); const player = await resolvePlayerFromCommand(interaction);
+5 -9
View File
@@ -1,4 +1,3 @@
import { SlashCommandBuilder } from 'discord.js';
import { NATIONS } from '../core/blitzkrieg/tankDefinitions'; import { NATIONS } from '../core/blitzkrieg/tankDefinitions';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton'; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import { addFilterOptions } from '../core/discord/addFilterOptions'; import { addFilterOptions } from '../core/discord/addFilterOptions';
@@ -7,6 +6,7 @@ import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import { buttonRefresh } from '../core/discord/buttonRefresh'; import { buttonRefresh } from '../core/discord/buttonRefresh';
import commandToURL from '../core/discord/commandToURL'; import commandToURL from '../core/discord/commandToURL';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import { getFiltersFromButton } from '../core/discord/getFiltersFromButton'; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand'; import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
@@ -19,18 +19,14 @@ import { renderBreakdown } from './breakdown';
export const todayCommand = new Promise<CommandRegistry>(async (resolve) => { export const todayCommand = new Promise<CommandRegistry>(async (resolve) => {
const nations = await NATIONS; const nations = await NATIONS;
const command = addFilterOptions(
new SlashCommandBuilder()
.setName('today')
.setDescription("Today's played tanks and statistics"),
nations,
).addStringOption(addUsernameChoices);
resolve({ resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
command, command: addFilterOptions(
createLocalizedCommand('today'),
nations,
).addStringOption(addUsernameChoices),
async handler(interaction) { async handler(interaction) {
const player = await resolvePlayerFromCommand(interaction); const player = await resolvePlayerFromCommand(interaction);
@@ -1,30 +1,21 @@
import { Locale } from 'discord.js';
import { Percentile } from '../../../../constants/percentiles'; import { Percentile } from '../../../../constants/percentiles';
import { translator } from '../../../../core/localization/translator';
import getWN8Percentile from '../../../../core/statistics/getWN8Percentile'; import getWN8Percentile from '../../../../core/statistics/getWN8Percentile';
import { theme } from '../../../../stitches.config'; import { theme } from '../../../../stitches.config';
import { PERCENTILE_COLORS } from '../../../PercentileIndicator'; import { PERCENTILE_COLORS } from '../../../PercentileIndicator';
import { Glow } from './components/Glow'; import { Glow } from './components/Glow';
export interface WN8DisplayProps { export interface WN8DisplayProps {
WN8?: number; wn8?: number;
locale: Locale;
} }
export const PERCENTILE_NAMES = { export function WN8Display({ wn8, locale }: WN8DisplayProps) {
[Percentile.VeryBad]: 'Very Bad',
[Percentile.Bad]: 'Bad',
[Percentile.BelowAverage]: 'Below Average',
[Percentile.Average]: 'Average',
[Percentile.AboveAverage]: 'Above Average',
[Percentile.Good]: 'Good',
[Percentile.VeryGood]: 'Very Good',
[Percentile.Great]: 'Great',
[Percentile.Unicum]: 'Unicum',
[Percentile.SuperUnicum]: 'Super Unicum',
};
export function WN8Display({ WN8 }: WN8DisplayProps) {
const percentile = const percentile =
WN8 === undefined ? Percentile.VeryBad : getWN8Percentile(WN8); wn8 === undefined ? Percentile.VeryBad : getWN8Percentile(wn8);
const color = PERCENTILE_COLORS[percentile]; const color = PERCENTILE_COLORS[percentile];
const { translate } = translator(locale);
return ( return (
<div <div
@@ -59,7 +50,7 @@ export function WN8Display({ WN8 }: WN8DisplayProps) {
color: theme.colors.textHighContrast, color: theme.colors.textHighContrast,
}} }}
> >
{WN8 === undefined ? '--' : WN8.toFixed(0)} {wn8 === undefined ? '--' : wn8.toFixed(0)}
</span> </span>
<span <span
style={{ style={{
@@ -67,7 +58,7 @@ export function WN8Display({ WN8 }: WN8DisplayProps) {
color: theme.colors.textHighContrast, color: theme.colors.textHighContrast,
}} }}
> >
{PERCENTILE_NAMES[percentile]} {translate(`common.wn8_percentile.${percentile}`)}
</span> </span>
</div> </div>
+15 -9
View File
@@ -1,4 +1,6 @@
import { Locale } from 'discord.js';
import { AllStats, SupplementaryStats } from '../../core/blitz/getAccountInfo'; import { AllStats, SupplementaryStats } from '../../core/blitz/getAccountInfo';
import { translator } from '../../core/localization/translator';
import { WN8Display } from './components/WN8Display'; import { WN8Display } from './components/WN8Display';
import { import {
WN8SurroundingStat, WN8SurroundingStat,
@@ -8,12 +10,16 @@ import {
export interface AllStatsOverviewProps { export interface AllStatsOverviewProps {
stats: AllStats; stats: AllStats;
supplementaryStats: SupplementaryStats; supplementaryStats: SupplementaryStats;
locale: Locale;
} }
export default function AllStatsOverview({ export default function AllStatsOverview({
stats, stats,
locale,
supplementaryStats, supplementaryStats,
}: AllStatsOverviewProps) { }: AllStatsOverviewProps) {
const { t } = translator(locale);
return ( return (
<div <div
style={{ style={{
@@ -32,18 +38,18 @@ export default function AllStatsOverview({
}} }}
> >
<WN8SurroundingStat <WN8SurroundingStat
label="Battles" label={t`bot.commands.stats.body.battles`}
value={stats.battles.toLocaleString()} value={stats.battles.toLocaleString()}
align={WN8SurroundingStatAlign.Right} align={WN8SurroundingStatAlign.Right}
/> />
<WN8SurroundingStat <WN8SurroundingStat
label="Winrate" label={t`bot.commands.stats.body.winrate`}
value={`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`} value={`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`}
align={WN8SurroundingStatAlign.Right} align={WN8SurroundingStatAlign.Right}
padded padded
/> />
<WN8SurroundingStat <WN8SurroundingStat
label="Damage" label={t`bot.commands.stats.body.damage`}
value={Math.round( value={Math.round(
stats.damage_dealt / stats.battles, stats.damage_dealt / stats.battles,
).toLocaleString()} ).toLocaleString()}
@@ -51,13 +57,13 @@ export default function AllStatsOverview({
padded padded
/> />
<WN8SurroundingStat <WN8SurroundingStat
label="Dmg. ratio" label={t`bot.commands.stats.body.damage_ratio`}
value={(stats.damage_dealt / stats.damage_received).toFixed(2)} value={(stats.damage_dealt / stats.damage_received).toFixed(2)}
align={WN8SurroundingStatAlign.Right} align={WN8SurroundingStatAlign.Right}
/> />
</div> </div>
<WN8Display WN8={supplementaryStats.WN8} /> <WN8Display locale={locale} wn8={supplementaryStats.WN8} />
<div <div
style={{ style={{
@@ -68,25 +74,25 @@ export default function AllStatsOverview({
}} }}
> >
<WN8SurroundingStat <WN8SurroundingStat
label="Tier" label={t`bot.commands.stats.body.tier`}
value={ value={
supplementaryStats.tier ? supplementaryStats.tier.toFixed(2) : '--' supplementaryStats.tier ? supplementaryStats.tier.toFixed(2) : '--'
} }
/> />
<WN8SurroundingStat <WN8SurroundingStat
label="Survival" label={t`bot.commands.stats.body.survival`}
value={`${(100 * (stats.survived_battles / stats.battles)).toFixed( value={`${(100 * (stats.survived_battles / stats.battles)).toFixed(
2, 2,
)}%`} )}%`}
padded padded
/> />
<WN8SurroundingStat <WN8SurroundingStat
label="Accuracy" label={t`bot.commands.stats.body.accuracy`}
value={`${(100 * (stats.hits / stats.shots)).toFixed(2)}%`} value={`${(100 * (stats.hits / stats.shots)).toFixed(2)}%`}
padded padded
/> />
<WN8SurroundingStat <WN8SurroundingStat
label="Kill ratio" label={t`bot.commands.stats.body.kill_ratio`}
value={( value={(
stats.frags / stats.frags /
(stats.battles - stats.survived_battles) (stats.battles - stats.survived_battles)
+29 -7
View File
@@ -1,16 +1,38 @@
import { SlashCommandStringOption } from 'discord.js'; import {
APIApplicationCommandOptionChoice,
Locale,
SlashCommandStringOption,
} from 'discord.js';
import { range } from 'lodash'; import { range } from 'lodash';
import { translator } from '../localization/translator';
import numberToRomanNumeral from '../math/numberToRomanNumeral'; import numberToRomanNumeral from '../math/numberToRomanNumeral';
import { localizationObject } from './localizationObject';
export default function addTierChoices(option: SlashCommandStringOption) { export default function addTierChoices(option: SlashCommandStringOption) {
const { t, translate } = translator(Locale.EnglishUS);
return option return option
.setName('tier') .setName(t`bot.common.options.tier`)
.setDescription('The tier you want to see') .setNameLocalizations(localizationObject('bot.common.options.tier'))
.setDescription(t`bot.common.options.tier.description`)
.setDescriptionLocalizations(
localizationObject('bot.common.options.tier.description'),
)
.setChoices( .setChoices(
...range(10, 0).map((tier) => ({ ...range(10, 0).map(
name: `Tier ${tier} - ${numberToRomanNumeral(tier)}`, (tier) =>
value: `${tier}`, ({
})), name: translate('bot.common.options.tier.choices.tier', [
`${tier}`,
numberToRomanNumeral(tier),
]),
value: `${tier}`,
name_localizations: localizationObject(
'bot.common.options.tier.choices.tier',
[`${tier}`, numberToRomanNumeral(tier)],
),
}) satisfies APIApplicationCommandOptionChoice,
),
) )
.setRequired(true); .setRequired(true);
} }
+85
View File
@@ -1,5 +1,18 @@
{ {
"common": { "common": {
"wn8_percentile": {
"0": "Very bad",
"1": "Bad",
"2": "Below average",
"3": "Average",
"4": "Above average",
"5": "Good",
"6": "Very good",
"7": "Great",
"8": "Unicum",
"9": "Super unicum"
},
"nations": { "nations": {
"ussr": "U.S.S.R.", "ussr": "U.S.S.R.",
"germany": "Germany", "germany": "Germany",
@@ -135,6 +148,15 @@
"region": { "region": {
"$": "region", "$": "region",
"description": "Region" "description": "Region"
},
"tier": {
"$": "tier",
"description": "Tier",
"choices": {
"tier": "Tier %s - %s"
}
} }
}, },
@@ -386,6 +408,69 @@
"neighbors": "Ratings neighbors", "neighbors": "Ratings neighbors",
"more": "+%s more" "more": "+%s more"
} }
},
"today": {
"$": "today",
"description": "Today's played tanks and statistics"
},
"stats": {
"$": "stats",
"description": "Quick statistics for a period",
"body": {
"battles": "Battles",
"winrate": "Winrate",
"damage": "Damage",
"damage_ratio": "Damage ratio",
"tier": "Tier",
"survival": "Survival",
"accuracy": "Accuracy",
"kill_ratio": "Kill ratio"
}
},
"owned_tanks": {
"$": "owned-tanks",
"description": "Shows all own tanks in a tier",
"body": {
"subtitle": "Tier %s tanks"
}
},
"permissions": {
"$": "permissions",
"description": "Checks wether the bot has the required permissions to function",
"body": {
"title": "# Permissions\n\n%s",
"view_channels": "View channels: needed for the refresh button to work",
"read_message_history": "Read message history: needed for the refresh button to find the message to refresh",
"attach_files": "Attach files: needed for image-based commands to work"
}
},
"ping": {
"$": "ping",
"description": "Check wether the bot is alive.",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - %sms"
},
"subcommands": {
"blitzkrieg": {
"$": "blitzkrieg",
"description": "Ping Blitzkrieg"
},
"wotb": {
"$": "wotb",
"description": "Ping World of Tanks Blitz"
}
}
} }
} }
}, },