deploy #2
@@ -15,6 +15,7 @@ import { ensureRecurringJobs, startWorkers } from './jobs.js';
|
|||||||
import type { BotContext } from './types.js';
|
import type { BotContext } from './types.js';
|
||||||
import { startHealthServer } from './health.js';
|
import { startHealthServer } from './health.js';
|
||||||
import { getGuildLocale } from './i18n.js';
|
import { getGuildLocale } from './i18n.js';
|
||||||
|
import { ephemeral } from './interaction-reply.js';
|
||||||
import { t } from '@nexumi/shared';
|
import { t } from '@nexumi/shared';
|
||||||
import { handleAutoModMessage } from './modules/automod/actions.js';
|
import { handleAutoModMessage } from './modules/automod/actions.js';
|
||||||
import { registerLoggingEvents } from './modules/logging/events.js';
|
import { registerLoggingEvents } from './modules/logging/events.js';
|
||||||
@@ -227,9 +228,9 @@ if (!isShard) {
|
|||||||
const message = t(locale, 'generic.error');
|
const message = t(locale, 'generic.error');
|
||||||
if (interaction.isRepliable()) {
|
if (interaction.isRepliable()) {
|
||||||
if (interaction.replied || interaction.deferred) {
|
if (interaction.replied || interaction.deferred) {
|
||||||
await interaction.followUp({ content: message, ephemeral: true });
|
await interaction.followUp({ content: message, ...ephemeral() });
|
||||||
} else {
|
} else {
|
||||||
await interaction.reply({ content: message, ephemeral: true });
|
await interaction.reply({ content: message, ...ephemeral() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (replyError) {
|
} catch (replyError) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
import { decodeComponentCustomId, t, type DashboardModuleId } from '@nexumi/shared';
|
import { decodeComponentCustomId, t, type DashboardModuleId } from '@nexumi/shared';
|
||||||
import { env } from './env.js';
|
import { env } from './env.js';
|
||||||
import { getGuildLocale } from './i18n.js';
|
import { getGuildLocale } from './i18n.js';
|
||||||
|
import { ephemeral } from './interaction-reply.js';
|
||||||
import { isModuleEnabled, moduleForComponent } from './module-gates.js';
|
import { isModuleEnabled, moduleForComponent } from './module-gates.js';
|
||||||
import { isMaintenanceMode } from './presence.js';
|
import { isMaintenanceMode } from './presence.js';
|
||||||
import type { BotContext } from './types.js';
|
import type { BotContext } from './types.js';
|
||||||
@@ -176,7 +177,7 @@ export async function routeComponentInteraction(
|
|||||||
if (!interaction.replied && !interaction.deferred) {
|
if (!interaction.replied && !interaction.deferred) {
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: maintenance.message?.trim() || t(locale, 'generic.maintenance'),
|
content: maintenance.message?.trim() || t(locale, 'generic.maintenance'),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -198,7 +199,7 @@ export async function routeComponentInteraction(
|
|||||||
if (!interaction.replied && !interaction.deferred) {
|
if (!interaction.replied && !interaction.deferred) {
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: t(locale, 'generic.moduleDisabled'),
|
content: t(locale, 'generic.moduleDisabled'),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { t, tf } from '@nexumi/shared';
|
|||||||
import type { SlashCommand } from '../../types.js';
|
import type { SlashCommand } from '../../types.js';
|
||||||
import { getGuildLocale } from '../../i18n.js';
|
import { getGuildLocale } from '../../i18n.js';
|
||||||
import { requirePermission } from '../../permissions.js';
|
import { requirePermission } from '../../permissions.js';
|
||||||
|
import { ephemeral } from '../../interaction-reply.js';
|
||||||
import { ticketCommandData } from './command-definitions.js';
|
import { ticketCommandData } from './command-definitions.js';
|
||||||
import {
|
import {
|
||||||
addUserToTicket,
|
addUserToTicket,
|
||||||
@@ -26,7 +27,7 @@ async function ensureManageGuild(
|
|||||||
locale: 'de' | 'en'
|
locale: 'de' | 'en'
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (!interaction.inGuild()) {
|
if (!interaction.inGuild()) {
|
||||||
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ...ephemeral() });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale);
|
return requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale);
|
||||||
@@ -37,7 +38,7 @@ const ticketCommand: SlashCommand = {
|
|||||||
async execute(interaction, context) {
|
async execute(interaction, context) {
|
||||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||||
if (!interaction.inGuild()) {
|
if (!interaction.inGuild()) {
|
||||||
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ...ephemeral() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +55,10 @@ const ticketCommand: SlashCommand = {
|
|||||||
|
|
||||||
const categories = await getGuildCategories(context, interaction.guildId!);
|
const categories = await getGuildCategories(context, interaction.guildId!);
|
||||||
if (categories.length === 0) {
|
if (categories.length === 0) {
|
||||||
await interaction.reply({ content: t(locale, 'ticket.error.no_categories'), ephemeral: true });
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.no_categories'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +66,7 @@ const ticketCommand: SlashCommand = {
|
|||||||
if (!channel?.isTextBased() || channel.isDMBased()) {
|
if (!channel?.isTextBased() || channel.isDMBased()) {
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: t(locale, 'ticket.error.invalid_channel'),
|
content: t(locale, 'ticket.error.invalid_channel'),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -73,7 +77,7 @@ const ticketCommand: SlashCommand = {
|
|||||||
title: name,
|
title: name,
|
||||||
description
|
description
|
||||||
});
|
});
|
||||||
await interaction.reply({ content: t(locale, 'ticket.panel.created'), ephemeral: true });
|
await interaction.reply({ content: t(locale, 'ticket.panel.created'), ...ephemeral() });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof TicketPanelError) {
|
if (error instanceof TicketPanelError) {
|
||||||
const key =
|
const key =
|
||||||
@@ -82,7 +86,7 @@ const ticketCommand: SlashCommand = {
|
|||||||
: error.code === 'not_configured'
|
: error.code === 'not_configured'
|
||||||
? 'ticket.error.invalid_channel'
|
? 'ticket.error.invalid_channel'
|
||||||
: 'ticket.error.missing_permission';
|
: 'ticket.error.missing_permission';
|
||||||
await interaction.reply({ content: t(locale, key), ephemeral: true });
|
await interaction.reply({ content: t(locale, key), ...ephemeral() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -105,18 +109,28 @@ const ticketCommand: SlashCommand = {
|
|||||||
);
|
);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: tf(locale, 'ticket.category.created', { name: category.name }),
|
content: tf(locale, 'ticket.category.created', { name: category.name }),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ticket = await findTicketByChannel(context.prisma, interaction.channelId!);
|
const ticket = await findTicketByChannel(context.prisma, interaction.channelId!);
|
||||||
if (!ticket) {
|
if (!ticket) {
|
||||||
await interaction.reply({ content: t(locale, 'ticket.error.not_in_ticket'), ephemeral: true });
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.not_in_ticket'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = await interaction.guild!.members.fetch(interaction.user.id);
|
const member = await interaction.guild!.members.fetch(interaction.user.id).catch(() => null);
|
||||||
|
if (!member) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (sub === 'close') {
|
if (sub === 'close') {
|
||||||
@@ -131,40 +145,57 @@ const ticketCommand: SlashCommand = {
|
|||||||
const isOpener = ticket.openerId === interaction.user.id;
|
const isOpener = ticket.openerId === interaction.user.id;
|
||||||
|
|
||||||
if (!isStaff && !isOpener) {
|
if (!isStaff && !isOpener) {
|
||||||
await interaction.reply({ content: t(locale, 'ticket.error.not_staff'), ephemeral: true });
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.not_staff'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = interaction.options.getString('reason') ?? undefined;
|
const reason = interaction.options.getString('reason') ?? undefined;
|
||||||
await closeTicket(context, ticket, interaction.user.id, locale, reason);
|
await closeTicket(context, ticket, interaction.user.id, locale, reason);
|
||||||
await interaction.reply({ content: t(locale, 'ticket.close.success'), ephemeral: true });
|
await interaction.reply({ content: t(locale, 'ticket.close.success'), ...ephemeral() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sub === 'claim') {
|
if (sub === 'claim') {
|
||||||
await claimTicket(context, ticket, member, locale);
|
await claimTicket(context, ticket, member, locale);
|
||||||
await interaction.reply({ content: t(locale, 'ticket.claim.success'), ephemeral: true });
|
await interaction.reply({ content: t(locale, 'ticket.claim.success'), ...ephemeral() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sub === 'add') {
|
if (sub === 'add') {
|
||||||
const user = interaction.options.getUser('user', true);
|
const user = interaction.options.getUser('user', true);
|
||||||
const target = await interaction.guild!.members.fetch(user.id);
|
const target = await interaction.guild!.members.fetch(user.id).catch(() => null);
|
||||||
|
if (!target) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
await addUserToTicket(context, ticket, member, target, locale);
|
await addUserToTicket(context, ticket, member, target, locale);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: tf(locale, 'ticket.add.success', { user: `<@${user.id}>` }),
|
content: tf(locale, 'ticket.add.success', { user: `<@${user.id}>` }),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sub === 'remove') {
|
if (sub === 'remove') {
|
||||||
const user = interaction.options.getUser('user', true);
|
const user = interaction.options.getUser('user', true);
|
||||||
const target = await interaction.guild!.members.fetch(user.id);
|
const target = await interaction.guild!.members.fetch(user.id).catch(() => null);
|
||||||
|
if (!target) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
await removeUserFromTicket(context, ticket, member, target, locale);
|
await removeUserFromTicket(context, ticket, member, target, locale);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: tf(locale, 'ticket.remove.success', { user: `<@${user.id}>` }),
|
content: tf(locale, 'ticket.remove.success', { user: `<@${user.id}>` }),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -174,7 +205,7 @@ const ticketCommand: SlashCommand = {
|
|||||||
await renameTicket(context, ticket, member, name, locale);
|
await renameTicket(context, ticket, member, name, locale);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: tf(locale, 'ticket.rename.success', { name }),
|
content: tf(locale, 'ticket.rename.success', { name }),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -186,14 +217,14 @@ const ticketCommand: SlashCommand = {
|
|||||||
content: tf(locale, 'ticket.priority.success', {
|
content: tf(locale, 'ticket.priority.success', {
|
||||||
priority: t(locale, `ticket.priority.${level}`)
|
priority: t(locale, `ticket.priority.${level}`)
|
||||||
}),
|
}),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof TicketError) {
|
if (error instanceof TicketError) {
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: t(locale, `ticket.error.${error.code}`),
|
content: t(locale, `ticket.error.${error.code}`),
|
||||||
ephemeral: true
|
...ephemeral()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,14 @@ async function handleOpenTicketRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const member = await interaction.guild.members.fetch(interaction.user.id);
|
const member = await interaction.guild.members.fetch(interaction.user.id).catch(() => null);
|
||||||
|
if (!member) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ticket = await openTicketForCategory(
|
const ticket = await openTicketForCategory(
|
||||||
context,
|
context,
|
||||||
interaction.guild,
|
interaction.guild,
|
||||||
@@ -126,7 +133,14 @@ async function handleTicketControl(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = await interaction.guild.members.fetch(interaction.user.id);
|
const member = await interaction.guild.members.fetch(interaction.user.id).catch(() => null);
|
||||||
|
if (!member) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (interaction.isButton() && interaction.customId === TICKET_CTRL_CLOSE) {
|
if (interaction.isButton() && interaction.customId === TICKET_CTRL_CLOSE) {
|
||||||
@@ -177,7 +191,14 @@ async function handleTicketControl(
|
|||||||
await interaction.reply({ content: t(locale, 'ticket.error.not_found'), ...ephemeral() });
|
await interaction.reply({ content: t(locale, 'ticket.error.not_found'), ...ephemeral() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = await interaction.guild.members.fetch(userId);
|
const target = await interaction.guild.members.fetch(userId).catch(() => null);
|
||||||
|
if (!target) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
await addUserToTicket(context, ticket, member, target, locale);
|
await addUserToTicket(context, ticket, member, target, locale);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: tf(locale, 'ticket.add.success', { user: `<@${userId}>` }),
|
content: tf(locale, 'ticket.add.success', { user: `<@${userId}>` }),
|
||||||
@@ -192,7 +213,14 @@ async function handleTicketControl(
|
|||||||
await interaction.reply({ content: t(locale, 'ticket.error.not_found'), ...ephemeral() });
|
await interaction.reply({ content: t(locale, 'ticket.error.not_found'), ...ephemeral() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = await interaction.guild.members.fetch(userId);
|
const target = await interaction.guild.members.fetch(userId).catch(() => null);
|
||||||
|
if (!target) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
await removeUserFromTicket(context, ticket, member, target, locale);
|
await removeUserFromTicket(context, ticket, member, target, locale);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: tf(locale, 'ticket.remove.success', { user: `<@${userId}>` }),
|
content: tf(locale, 'ticket.remove.success', { user: `<@${userId}>` }),
|
||||||
@@ -298,7 +326,14 @@ export async function handleTicketInteraction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const member = await interaction.guild.members.fetch(interaction.user.id);
|
const member = await interaction.guild.members.fetch(interaction.user.id).catch(() => null);
|
||||||
|
if (!member) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: t(locale, 'ticket.error.member_not_found'),
|
||||||
|
...ephemeral()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ticket = await openTicketForCategory(
|
const ticket = await openTicketForCategory(
|
||||||
context,
|
context,
|
||||||
interaction.guild,
|
interaction.guild,
|
||||||
|
|||||||
@@ -71,6 +71,15 @@ export class TicketPanelError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUnknownChannelError(error: unknown): boolean {
|
||||||
|
return Boolean(
|
||||||
|
error &&
|
||||||
|
typeof error === 'object' &&
|
||||||
|
'code' in error &&
|
||||||
|
(error as { code: unknown }).code === 10003
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function botCanPostPanel(
|
function botCanPostPanel(
|
||||||
channel: GuildBasedChannel,
|
channel: GuildBasedChannel,
|
||||||
meId: string
|
meId: string
|
||||||
@@ -707,7 +716,11 @@ async function fetchTicketMessages(
|
|||||||
timestamp: message.createdAt.toISOString()
|
timestamp: message.createdAt.toISOString()
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (isUnknownChannelError(error)) {
|
||||||
|
logger.debug({ ticketId: ticket.id }, 'Ticket channel already gone while fetching messages');
|
||||||
|
} else {
|
||||||
logger.warn({ error, ticketId: ticket.id }, 'Failed to fetch ticket messages');
|
logger.warn({ error, ticketId: ticket.id }, 'Failed to fetch ticket messages');
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1123,10 +1136,14 @@ export async function closeTicket(
|
|||||||
await channel.delete(`Ticket ${ticket.id} closed`);
|
await channel.delete(`Ticket ${ticket.id} closed`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (isUnknownChannelError(error)) {
|
||||||
|
logger.debug({ ticketId: ticket.id }, 'Ticket channel already gone while deleting');
|
||||||
|
} else {
|
||||||
logger.warn({ error, ticketId: ticket.id }, 'Failed to delete ticket channel');
|
logger.warn({ error, ticketId: ticket.id }, 'Failed to delete ticket channel');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function rateTicket(
|
export async function rateTicket(
|
||||||
context: BotContext,
|
context: BotContext,
|
||||||
|
|||||||
@@ -509,6 +509,7 @@
|
|||||||
- Control-Panel im Ticket: Claim, Close, User-Select Add/Remove (wie TempVoice-Pattern)
|
- Control-Panel im Ticket: Claim, Close, User-Select Add/Remove (wie TempVoice-Pattern)
|
||||||
- Opener darf schließen; Claim/Add/Remove nur Support-Staff
|
- Opener darf schließen; Claim/Add/Remove nur Support-Staff
|
||||||
- Prioritäts-Dropdown im Discord-Control-Panel (`ticket:ctrl:priority`)
|
- Prioritäts-Dropdown im Discord-Control-Panel (`ticket:ctrl:priority`)
|
||||||
|
- Robustheit: Unknown Channel (10003) beim Close nur noch debug; Unknown Member (10007) bei Add/Remove/Interactions abgefangen; Ticket-Commands nutzen `ephemeral()` flags
|
||||||
|
|
||||||
### Manuell testen
|
### Manuell testen
|
||||||
|
|
||||||
|
|||||||
@@ -478,6 +478,7 @@ const de: Dictionary = {
|
|||||||
'ticket.error.no_parent_channel': 'Kein Elternkanal für Thread-Tickets konfiguriert.',
|
'ticket.error.no_parent_channel': 'Kein Elternkanal für Thread-Tickets konfiguriert.',
|
||||||
'ticket.error.invalid_parent_channel': 'Der konfigurierte Elternkanal ist ungültig.',
|
'ticket.error.invalid_parent_channel': 'Der konfigurierte Elternkanal ist ungültig.',
|
||||||
'ticket.error.not_found': 'Ticket nicht gefunden.',
|
'ticket.error.not_found': 'Ticket nicht gefunden.',
|
||||||
|
'ticket.error.member_not_found': 'Mitglied nicht auf dem Server gefunden.',
|
||||||
'ticket.error.already_rated': 'Du hast dieses Ticket bereits bewertet.',
|
'ticket.error.already_rated': 'Du hast dieses Ticket bereits bewertet.',
|
||||||
'birthdays.error.invalidDate': 'Ungültiges Datum. Prüfe Tag, Monat und optionales Jahr.',
|
'birthdays.error.invalidDate': 'Ungültiges Datum. Prüfe Tag, Monat und optionales Jahr.',
|
||||||
'birthdays.error.invalidTimezone': 'Ungültige Zeitzone. Nutze z. B. Europe/Berlin.',
|
'birthdays.error.invalidTimezone': 'Ungültige Zeitzone. Nutze z. B. Europe/Berlin.',
|
||||||
@@ -1090,6 +1091,7 @@ const en: Dictionary = {
|
|||||||
'ticket.error.no_parent_channel': 'No parent channel configured for thread tickets.',
|
'ticket.error.no_parent_channel': 'No parent channel configured for thread tickets.',
|
||||||
'ticket.error.invalid_parent_channel': 'The configured parent channel is invalid.',
|
'ticket.error.invalid_parent_channel': 'The configured parent channel is invalid.',
|
||||||
'ticket.error.not_found': 'Ticket not found.',
|
'ticket.error.not_found': 'Ticket not found.',
|
||||||
|
'ticket.error.member_not_found': 'Member is not on this server.',
|
||||||
'ticket.error.already_rated': 'You already rated this ticket.',
|
'ticket.error.already_rated': 'You already rated this ticket.',
|
||||||
'birthdays.error.invalidDate': 'Invalid date. Check day, month, and optional year.',
|
'birthdays.error.invalidDate': 'Invalid date. Check day, month, and optional year.',
|
||||||
'birthdays.error.invalidTimezone': 'Invalid timezone. Use e.g. Europe/Berlin.',
|
'birthdays.error.invalidTimezone': 'Invalid timezone. Use e.g. Europe/Berlin.',
|
||||||
|
|||||||
Reference in New Issue
Block a user