Update README and moderation commands for localization and migration support
- Revised README to include database migration steps and a new section for database management. - Enhanced moderation command responses to utilize localized messages for unknown commands. - Refactored moderation commands to use centralized command data definitions, improving maintainability and consistency. - Added localization support for command descriptions and options in both German and English. - Versioned Prisma migrations for better tracking of database changes.
This commit is contained in:
220
packages/shared/src/command-locales.ts
Normal file
220
packages/shared/src/command-locales.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
export const commandLocales = {
|
||||
'moderation.ban.description': {
|
||||
de: 'Bannt einen Nutzer',
|
||||
en: 'Ban a user'
|
||||
},
|
||||
'moderation.ban.options.user': {
|
||||
de: 'Zielnutzer',
|
||||
en: 'Target user'
|
||||
},
|
||||
'moderation.ban.options.reason': {
|
||||
de: 'Grund',
|
||||
en: 'Reason'
|
||||
},
|
||||
'moderation.ban.options.duration': {
|
||||
de: 'Optionale Temp-Ban-Dauer (z. B. 7d)',
|
||||
en: 'Optional temp-ban duration (e.g. 7d)'
|
||||
},
|
||||
'moderation.unban.description': {
|
||||
de: 'Entbannt einen Nutzer',
|
||||
en: 'Unban a user'
|
||||
},
|
||||
'moderation.unban.options.user_id': {
|
||||
de: 'Discord-Nutzer-ID',
|
||||
en: 'Discord user id'
|
||||
},
|
||||
'moderation.kick.description': {
|
||||
de: 'Kickt einen Nutzer',
|
||||
en: 'Kick a user'
|
||||
},
|
||||
'moderation.timeout.description': {
|
||||
de: 'Timeout für einen Nutzer',
|
||||
en: 'Timeout a user'
|
||||
},
|
||||
'moderation.timeout.options.duration': {
|
||||
de: 'Dauer (z. B. 15m)',
|
||||
en: 'Duration (e.g. 15m)'
|
||||
},
|
||||
'moderation.untimeout.description': {
|
||||
de: 'Entfernt den Timeout eines Nutzers',
|
||||
en: 'Remove timeout from a user'
|
||||
},
|
||||
'moderation.warn.description': {
|
||||
de: 'Verwarnungen verwalten',
|
||||
en: 'Manage user warnings'
|
||||
},
|
||||
'moderation.warn.add.description': {
|
||||
de: 'Verwarnung hinzufügen',
|
||||
en: 'Add warning'
|
||||
},
|
||||
'moderation.warn.list.description': {
|
||||
de: 'Verwarnungen anzeigen',
|
||||
en: 'List warnings'
|
||||
},
|
||||
'moderation.warn.remove.description': {
|
||||
de: 'Verwarnung entfernen',
|
||||
en: 'Remove warning'
|
||||
},
|
||||
'moderation.warn.clear.description': {
|
||||
de: 'Verwarnungen eines Nutzers löschen',
|
||||
en: 'Clear warnings for user'
|
||||
},
|
||||
'moderation.warn.escalation_set.description': {
|
||||
de: 'Eskalationsregel erstellen oder aktualisieren',
|
||||
en: 'Create or update escalation rule'
|
||||
},
|
||||
'moderation.warn.escalation_list.description': {
|
||||
de: 'Eskalationsregeln anzeigen',
|
||||
en: 'List escalation rules'
|
||||
},
|
||||
'moderation.warn.escalation_remove.description': {
|
||||
de: 'Eskalationsregel entfernen',
|
||||
en: 'Remove escalation rule'
|
||||
},
|
||||
'moderation.warn.options.count': {
|
||||
de: 'Schwellenwert für Verwarnungen',
|
||||
en: 'Warning count threshold'
|
||||
},
|
||||
'moderation.warn.options.action': {
|
||||
de: 'Eskalationsaktion',
|
||||
en: 'Escalation action'
|
||||
},
|
||||
'moderation.warn.options.escalation_duration': {
|
||||
de: 'Timeout-Dauer (für TIMEOUT erforderlich)',
|
||||
en: 'Timeout duration (required for TIMEOUT)'
|
||||
},
|
||||
'moderation.warn.options.escalation_reason': {
|
||||
de: 'Optionaler Eskalationsgrund',
|
||||
en: 'Optional escalation reason'
|
||||
},
|
||||
'moderation.warn.options.warning_id': {
|
||||
de: 'Verwarnungs-ID',
|
||||
en: 'Warning ID'
|
||||
},
|
||||
'moderation.choice.timeout': {
|
||||
de: 'Timeout',
|
||||
en: 'timeout'
|
||||
},
|
||||
'moderation.choice.kick': {
|
||||
de: 'Kick',
|
||||
en: 'kick'
|
||||
},
|
||||
'moderation.choice.ban': {
|
||||
de: 'Ban',
|
||||
en: 'ban'
|
||||
},
|
||||
'moderation.purge.description': {
|
||||
de: 'Nachrichten massenhaft löschen',
|
||||
en: 'Bulk delete messages'
|
||||
},
|
||||
'moderation.purge.options.amount': {
|
||||
de: '2-100',
|
||||
en: '2-100'
|
||||
},
|
||||
'moderation.purge.options.user': {
|
||||
de: 'Nur Nachrichten dieses Nutzers löschen',
|
||||
en: 'Only purge this user messages'
|
||||
},
|
||||
'moderation.purge.options.bots': {
|
||||
de: 'Nur Bot-Nachrichten löschen',
|
||||
en: 'Only purge bot messages'
|
||||
},
|
||||
'moderation.purge.options.links': {
|
||||
de: 'Nur Nachrichten mit Links löschen',
|
||||
en: 'Only purge messages containing links'
|
||||
},
|
||||
'moderation.purge.options.attachments': {
|
||||
de: 'Nur Nachrichten mit Anhängen löschen',
|
||||
en: 'Only purge messages with attachments'
|
||||
},
|
||||
'moderation.purge.options.regex': {
|
||||
de: 'Nur Nachrichten per Regex löschen',
|
||||
en: 'Only purge messages matching regex'
|
||||
},
|
||||
'moderation.slowmode.description': {
|
||||
de: 'Slowmode für den Kanal setzen',
|
||||
en: 'Set channel slowmode'
|
||||
},
|
||||
'moderation.slowmode.options.seconds': {
|
||||
de: '0-21600',
|
||||
en: '0-21600'
|
||||
},
|
||||
'moderation.lock.description': {
|
||||
de: 'Aktuellen Textkanal sperren',
|
||||
en: 'Lock current text channel'
|
||||
},
|
||||
'moderation.unlock.description': {
|
||||
de: 'Aktuellen Textkanal entsperren',
|
||||
en: 'Unlock current text channel'
|
||||
},
|
||||
'moderation.nick.description': {
|
||||
de: 'Nicknames verwalten',
|
||||
en: 'Manage nicknames'
|
||||
},
|
||||
'moderation.nick.set.description': {
|
||||
de: 'Nickname setzen',
|
||||
en: 'Set nickname'
|
||||
},
|
||||
'moderation.nick.reset.description': {
|
||||
de: 'Nickname zurücksetzen',
|
||||
en: 'Reset nickname'
|
||||
},
|
||||
'moderation.nick.options.nickname': {
|
||||
de: 'Neuer Nickname',
|
||||
en: 'New nickname'
|
||||
},
|
||||
'moderation.case.description': {
|
||||
de: 'Moderationsfälle verwalten',
|
||||
en: 'Manage moderation cases'
|
||||
},
|
||||
'moderation.case.view.description': {
|
||||
de: 'Fall anzeigen',
|
||||
en: 'View case'
|
||||
},
|
||||
'moderation.case.edit.description': {
|
||||
de: 'Fallgrund bearbeiten',
|
||||
en: 'Edit case reason'
|
||||
},
|
||||
'moderation.case.delete.description': {
|
||||
de: 'Fall soft-löschen',
|
||||
en: 'Soft-delete case'
|
||||
},
|
||||
'moderation.case.options.id': {
|
||||
de: 'Fallnummer',
|
||||
en: 'Case number'
|
||||
},
|
||||
'moderation.case.options.reason': {
|
||||
de: 'Neuer Grund',
|
||||
en: 'New reason'
|
||||
},
|
||||
'moderation.modnote.description': {
|
||||
de: 'Mod-Notizen verwalten',
|
||||
en: 'Manage mod notes'
|
||||
},
|
||||
'moderation.modnote.add.description': {
|
||||
de: 'Notiz hinzufügen',
|
||||
en: 'Add note'
|
||||
},
|
||||
'moderation.modnote.list.description': {
|
||||
de: 'Notizen anzeigen',
|
||||
en: 'List notes'
|
||||
},
|
||||
'moderation.modnote.options.note': {
|
||||
de: 'Notiz',
|
||||
en: 'Note'
|
||||
},
|
||||
'moderation.common.options.user': {
|
||||
de: 'Zielnutzer',
|
||||
en: 'Target user'
|
||||
},
|
||||
'moderation.common.options.reason': {
|
||||
de: 'Grund',
|
||||
en: 'Reason'
|
||||
},
|
||||
'moderation.common.options.target': {
|
||||
de: 'Ziel',
|
||||
en: 'Target'
|
||||
}
|
||||
} as const;
|
||||
|
||||
export type CommandLocaleKey = keyof typeof commandLocales;
|
||||
17
packages/shared/src/discord-localization.test.ts
Normal file
17
packages/shared/src/discord-localization.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { commandLocale, localizedChoice } from './discord-localization.js';
|
||||
|
||||
describe('discord localization helpers', () => {
|
||||
it('maps command locales to Discord localization keys', () => {
|
||||
const locale = commandLocale('moderation.ban.description');
|
||||
expect(locale.description).toBe('Ban a user');
|
||||
expect(locale.descriptionLocalizations.de).toBe('Bannt einen Nutzer');
|
||||
expect(locale.descriptionLocalizations['en-US']).toBe('Ban a user');
|
||||
});
|
||||
|
||||
it('maps choice labels with localizations', () => {
|
||||
const choice = localizedChoice('moderation.choice.ban', 'BAN');
|
||||
expect(choice.value).toBe('BAN');
|
||||
expect(choice.name_localizations?.de).toBe('Ban');
|
||||
});
|
||||
});
|
||||
48
packages/shared/src/discord-localization.ts
Normal file
48
packages/shared/src/discord-localization.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { commandLocales, type CommandLocaleKey } from './command-locales.js';
|
||||
|
||||
export function commandLocale(key: CommandLocaleKey): {
|
||||
description: string;
|
||||
descriptionLocalizations: Record<string, string>;
|
||||
} {
|
||||
const entry = commandLocales[key];
|
||||
return {
|
||||
description: entry.en,
|
||||
descriptionLocalizations: {
|
||||
de: entry.de,
|
||||
'en-US': entry.en
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function localizedChoice(key: CommandLocaleKey, value: string) {
|
||||
const entry = commandLocales[key];
|
||||
return {
|
||||
name: entry.en,
|
||||
name_localizations: {
|
||||
de: entry.de,
|
||||
'en-US': entry.en
|
||||
},
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
export function applyCommandDescription<
|
||||
T extends {
|
||||
setDescription: (description: string) => T;
|
||||
setDescriptionLocalizations: (localizations: Record<string, string>) => T;
|
||||
}
|
||||
>(builder: T, key: CommandLocaleKey): T {
|
||||
const locale = commandLocale(key);
|
||||
return builder
|
||||
.setDescription(locale.description)
|
||||
.setDescriptionLocalizations(locale.descriptionLocalizations);
|
||||
}
|
||||
|
||||
export function applyOptionDescription<
|
||||
T extends {
|
||||
setDescription: (description: string) => T;
|
||||
setDescriptionLocalizations: (localizations: Record<string, string>) => T;
|
||||
}
|
||||
>(option: T, key: CommandLocaleKey): T {
|
||||
return applyCommandDescription(option, key);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export * from './audit.js';
|
||||
export * from './command-locales.js';
|
||||
export * from './discord-localization.js';
|
||||
|
||||
export const LocaleSchema = z.enum(['de', 'en']);
|
||||
export type Locale = z.infer<typeof LocaleSchema>;
|
||||
@@ -40,6 +42,7 @@ const de: Dictionary = {
|
||||
'generic.botMissingPermission': 'Dem Bot fehlt die erforderliche Berechtigung ({permission}).',
|
||||
'generic.invalidRegex': 'Ungültiges Regex-Muster.',
|
||||
'generic.error': 'Es ist ein Fehler aufgetreten.',
|
||||
'generic.unknownCommand': 'Unbekannter Befehl.',
|
||||
'moderation.success': 'Aktion erfolgreich ausgeführt.',
|
||||
'moderation.reason.none': 'Kein Grund angegeben',
|
||||
'moderation.warning.created': 'Verwarnung erstellt: {warningId}',
|
||||
@@ -71,6 +74,7 @@ const en: Dictionary = {
|
||||
'generic.botMissingPermission': 'Bot lacks required permission ({permission}).',
|
||||
'generic.invalidRegex': 'Invalid regex pattern.',
|
||||
'generic.error': 'An error occurred.',
|
||||
'generic.unknownCommand': 'Unknown command.',
|
||||
'moderation.success': 'Action executed successfully.',
|
||||
'moderation.reason.none': 'No reason provided',
|
||||
'moderation.warning.created': 'Warning created: {warningId}',
|
||||
|
||||
Reference in New Issue
Block a user