diff --git a/apps/dashboard/src/client/features/welcome/components/WelcomeImageEditor.tsx b/apps/dashboard/src/client/features/welcome/components/WelcomeImageEditor.tsx index 39a27e2..ebf5194 100644 --- a/apps/dashboard/src/client/features/welcome/components/WelcomeImageEditor.tsx +++ b/apps/dashboard/src/client/features/welcome/components/WelcomeImageEditor.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, useRef } from "react"; +import { useState, useEffect, useRef } from "react"; import { Image, Palette, @@ -10,6 +10,8 @@ import { Sparkles, Eye, Check, + Wifi, + WifiOff, } from "lucide-react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -36,6 +38,7 @@ import { useDeleteBackground, type WelcomeImageSettings, } from "../hooks/useWelcome"; +import { useAuth } from "../../../shared/hooks/useAuth"; const PRESET_GRADIENT_COLORS: Record = { midnight: "from-[#0f0c29] via-[#302b63] to-[#24243e]", @@ -63,46 +66,120 @@ export function WelcomeImageEditor({ const { data: templateData } = useWelcomeTemplates(); const { data: fontData } = useWelcomeFonts(); const { data: presetData } = useWelcomePresets(); - const preview = useWelcomeImagePreview(guildId); + const { data: user } = useAuth(); + const serverPreview = useWelcomeImagePreview(guildId); + const uploadBg = useUploadBackground(guildId); const deleteBg = useDeleteBackground(guildId); const [previewUrl, setPreviewUrl] = useState(null); + const [previewMode, setPreviewMode] = useState<"client" | "server">("client"); + const [previewError, setPreviewError] = useState(false); + const [isPending, setIsPending] = useState(false); + const [refreshKey, setRefreshKey] = useState(0); const fileInputRef = useRef(null); - const debounceRef = useRef | undefined>(undefined); + const rafRef = useRef(0); + const canvasRef = useRef(null); + const prevUrlRef = useRef(null); + const fontsLoadedRef = useRef(false); + const serverUrlRef = useRef(null); const templates = templateData?.templates ?? []; const fonts = fontData?.fonts ?? []; const presets = presetData?.backgrounds ?? []; - // Auto-generate preview on settings change (debounced) - const generatePreview = useCallback(() => { - if (debounceRef.current) clearTimeout(debounceRef.current); - debounceRef.current = setTimeout(() => { - preview.mutate( + const avatarUrl = user?.avatar + ? `https://cdn.discordapp.com/avatars/${user.userId}/${user.avatar}.png?size=256` + : "https://cdn.discordapp.com/embed/avatars/0.png"; + + // Lazy-init offscreen canvas + if (!canvasRef.current) { + canvasRef.current = document.createElement("canvas"); + } + + // Load fonts once + useEffect(() => { + if (fontsLoadedRef.current) return; + fontsLoadedRef.current = true; + import("../image/renderer").then((m) => m.loadPreviewFonts()); + }, []); + + // Client-side render — instant, no debounce + useEffect(() => { + if (previewMode !== "client") return; + + setIsPending(true); + setPreviewError(false); + + // Cancel any pending RAF + if (rafRef.current) cancelAnimationFrame(rafRef.current); + + rafRef.current = requestAnimationFrame(async () => { + try { + const { renderWelcomeImagePreview } = await import("../image/renderer"); + const result = await renderWelcomeImagePreview({ + settings: settings as never, + member: { + username: user?.username ?? "User", + displayName: user?.username ?? "User", + avatarUrl, + }, + guild: { + name: "Your Server", + memberCount: 1234, + }, + }); + + if (prevUrlRef.current) URL.revokeObjectURL(prevUrlRef.current); + prevUrlRef.current = result.url; + setPreviewUrl(result.url); + setIsPending(false); + } catch { + setIsPending(false); + setPreviewError(true); + } + }); + + return () => { + if (rafRef.current) cancelAnimationFrame(rafRef.current); + }; + }, [settings, previewMode, user, avatarUrl, refreshKey]); + + // Server-side preview — debounced since it's a network request + useEffect(() => { + if (previewMode !== "server") return; + + const timer = setTimeout(() => { + setIsPending(true); + setPreviewError(false); + + serverPreview.mutate( { settings, type }, { onSuccess: (url) => { - if (previewUrl) URL.revokeObjectURL(previewUrl); + if (serverUrlRef.current) URL.revokeObjectURL(serverUrlRef.current); + serverUrlRef.current = url; setPreviewUrl(url); + setIsPending(false); + }, + onError: () => { + setIsPending(false); + setPreviewError(true); + toast.error(t("imageEditor.toast.previewFailed")); }, - onError: () => toast.error(t("imageEditor.toast.previewFailed")), }, ); - }, 600); - }, [settings, type, t]); + }, 400); - useEffect(() => { - generatePreview(); - return () => { - if (debounceRef.current) clearTimeout(debounceRef.current); - }; - }, [generatePreview]); + return () => clearTimeout(timer); + }, [settings, type, previewMode, t, refreshKey]); - // Cleanup object URLs + // Cleanup useEffect(() => { return () => { - if (previewUrl) URL.revokeObjectURL(previewUrl); + if (rafRef.current) cancelAnimationFrame(rafRef.current); + if (prevUrlRef.current) URL.revokeObjectURL(prevUrlRef.current); + if (serverUrlRef.current) URL.revokeObjectURL(serverUrlRef.current); }; }, []); @@ -163,19 +240,52 @@ export function WelcomeImageEditor({
- +
+ + +
{previewUrl ? ( @@ -186,14 +296,19 @@ export function WelcomeImageEditor({ /> ) : (
- {preview.isPending + {isPending ? t("imageEditor.generatingPreview") : t("imageEditor.previewPlaceholder")}
)}
+ {previewError && ( +

+ {t("imageEditor.previewFailedHint")} +

+ )}
- {preview.isPending ? t("imageEditor.generatingPreview") : ""} + {isPending ? t("imageEditor.generatingPreview") : ""}
diff --git a/apps/dashboard/src/client/features/welcome/hooks/useWelcome.ts b/apps/dashboard/src/client/features/welcome/hooks/useWelcome.ts index 36890c2..ec3760f 100644 --- a/apps/dashboard/src/client/features/welcome/hooks/useWelcome.ts +++ b/apps/dashboard/src/client/features/welcome/hooks/useWelcome.ts @@ -170,6 +170,8 @@ export function useWelcomeImagePreview(guildId: string) { }); } + + export function useUploadBackground(guildId: string) { return useMutation({ mutationFn: async (file: File) => { diff --git a/apps/dashboard/src/client/features/welcome/image/renderer.ts b/apps/dashboard/src/client/features/welcome/image/renderer.ts new file mode 100644 index 0000000..a671f8e --- /dev/null +++ b/apps/dashboard/src/client/features/welcome/image/renderer.ts @@ -0,0 +1,463 @@ +import { getTemplate, PRESET_GRADIENTS } from "./templates"; + +export interface ClientImageSettings { + template: string; + background: { + type: "color" | "image" | "preset"; + color: string; + imageKey?: string; + preset?: string; + }; + overlay: { + enabled: boolean; + color: string; + opacity: number; + }; + avatar: { + shape: "circle" | "rounded" | "square"; + borderColor: string; + borderWidth: number; + glowEnabled: boolean; + glowColor: string; + }; + title: { + font: string; + color: string; + size: number; + }; + subtitle: { + font: string; + color: string; + size: number; + text: string; + }; + accentColor: string; + sendMode: "with" | "before" | "only"; +} + +export interface RenderInput { + settings: ClientImageSettings; + member: { + username: string; + displayName: string; + avatarUrl: string; + }; + guild: { + name: string; + memberCount: number; + }; +} + +type FontCategory = "sans-serif" | "serif" | "monospace" | "display" | "rounded"; + +const FONT_FAMILIES: Record = { + Inter: { family: "Inter", category: "sans-serif" }, + SpaceGrotesk: { family: "Space Grotesk", category: "sans-serif" }, + JetBrainsMono: { family: "JetBrains Mono", category: "monospace" }, + Poppins: { family: "Poppins", category: "rounded" }, + PlayfairDisplay: { family: "Playfair Display", category: "serif" }, + Outfit: { family: "Outfit", category: "sans-serif" }, + Orbitron: { family: "Orbitron", category: "display" }, + BebasNeue: { family: "Bebas Neue", category: "display" }, +}; + +export function getFontFamily(fontName: string): string { + return FONT_FAMILIES[fontName]?.family ?? "Inter"; +} + +const FONT_CSS_URLS: Record = { + Inter: "https://fonts.googleapis.com/css2?family=Inter:wght@600&display=swap", + "Space Grotesk": "https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@700&display=swap", + "JetBrains Mono": "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700&display=swap", + Poppins: "https://fonts.googleapis.com/css2?family=Poppins:wght@600&display=swap", + "Playfair Display": "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&display=swap", + Outfit: "https://fonts.googleapis.com/css2?family=Outfit:wght@600&display=swap", + Orbitron: "https://fonts.googleapis.com/css2?family=Orbitron:wght@700&display=swap", + "Bebas Neue": "https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap", +}; + +let fontsLoaded = false; +let fontLoadPromise: Promise | null = null; + +export function loadPreviewFonts(): Promise { + if (fontsLoaded) return Promise.resolve(); + if (fontLoadPromise) return fontLoadPromise; + + const uniqueUrls = new Set(Object.values(FONT_CSS_URLS)); + fontLoadPromise = new Promise((resolve) => { + let loaded = 0; + const total = uniqueUrls.size; + + if (total === 0) { + fontsLoaded = true; + resolve(); + return; + } + + for (const url of uniqueUrls) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = url; + link.onload = () => { + loaded++; + if (loaded >= total) { + document.fonts.ready.then(() => { + fontsLoaded = true; + resolve(); + }); + } + }; + link.onerror = () => { + loaded++; + if (loaded >= total) { + document.fonts.ready.then(() => { + fontsLoaded = true; + resolve(); + }); + } + }; + document.head.appendChild(link); + } + }); + + return fontLoadPromise; +} + +function hexToRgba(hex: string, alpha: number): string { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +} + +function resolveColor(colorSource: string, settings: ClientImageSettings): string { + if (colorSource === "accent") return settings.accentColor; + if (colorSource === "title") return settings.title.color; + if (colorSource === "subtitle") return settings.subtitle.color; + return colorSource; +} + +async function drawBackground( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + settings: ClientImageSettings, +): Promise { + const { background } = settings; + + if (background.type === "image" && background.imageKey) { + try { + const imgUrl = `/uploads/welcome/${background.imageKey}`; + const img = await loadImageElement(imgUrl); + const scale = Math.max(width / img.naturalWidth, height / img.naturalHeight); + const drawWidth = img.naturalWidth * scale; + const drawHeight = img.naturalHeight * scale; + const offsetX = (width - drawWidth) / 2; + const offsetY = (height - drawHeight) / 2; + ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight); + return; + } catch { + // Fall through + } + } + + if (background.type === "preset" && background.preset) { + const colors = PRESET_GRADIENTS[background.preset]; + if (colors) { + const gradient = ctx.createLinearGradient(0, 0, width, height); + gradient.addColorStop(0, colors[0]); + gradient.addColorStop(colors[2] ? 0.5 : 1, colors[1]); + if (colors[2]) gradient.addColorStop(1, colors[2]); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, width, height); + return; + } + } + + ctx.fillStyle = background.color; + ctx.fillRect(0, 0, width, height); +} + +function drawOverlay( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + settings: ClientImageSettings, +): void { + if (!settings.overlay.enabled) return; + ctx.fillStyle = hexToRgba(settings.overlay.color, settings.overlay.opacity); + ctx.fillRect(0, 0, width, height); +} + +async function drawAvatar( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + size: number, + settings: ClientImageSettings, + avatarUrl: string, +): Promise { + const { avatar } = settings; + const radius = size / 2; + + if (avatar.glowEnabled) { + const glowRadius = radius + 20; + const gradient = ctx.createRadialGradient(x, y, radius, x, y, glowRadius); + gradient.addColorStop(0, hexToRgba(avatar.glowColor, 0.3)); + gradient.addColorStop(1, hexToRgba(avatar.glowColor, 0)); + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.arc(x, y, glowRadius, 0, Math.PI * 2); + ctx.fill(); + } + + if (avatar.borderWidth > 0) { + ctx.beginPath(); + drawAvatarShape(ctx, x, y, radius + avatar.borderWidth, avatar.shape); + ctx.fillStyle = avatar.borderColor; + ctx.fill(); + } + + ctx.save(); + ctx.beginPath(); + drawAvatarShape(ctx, x, y, radius, avatar.shape); + ctx.clip(); + + try { + const img = await loadImageElement(avatarUrl); + ctx.drawImage(img, x - radius, y - radius, size, size); + } catch { + ctx.fillStyle = "#374151"; + ctx.fill(); + } + + ctx.restore(); +} + +function drawAvatarShape( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + radius: number, + shape: "circle" | "rounded" | "square", +): void { + if (shape === "circle") { + ctx.arc(x, y, radius, 0, Math.PI * 2); + } else if (shape === "rounded") { + const w = radius * 2; + const r = radius * 0.3; + ctx.roundRect(x - radius, y - radius, w, w, r); + } else { + ctx.rect(x - radius, y - radius, radius * 2, radius * 2); + } +} + +function drawText( + ctx: CanvasRenderingContext2D, + text: string, + x: number, + y: number, + options: { + font: string; + size: number; + color: string; + align: "left" | "center" | "right"; + maxWidth: number; + }, +): void { + const family = getFontFamily(options.font); + ctx.font = `${options.size}px "${family}", sans-serif`; + ctx.fillStyle = options.color; + ctx.textAlign = options.align; + ctx.textBaseline = "middle"; + + let displayText = text; + const measured = ctx.measureText(displayText); + if (measured.width > options.maxWidth && displayText.length > 3) { + while ( + ctx.measureText(displayText + "...").width > options.maxWidth && + displayText.length > 1 + ) { + displayText = displayText.slice(0, -1); + } + displayText += "..."; + } + + ctx.fillText(displayText, x, y, options.maxWidth); +} + +function drawDecorations( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + decorations: { type: string; props: Record }[], + settings: ClientImageSettings, +): void { + for (const dec of decorations) { + const color = resolveColor(dec.props.colorSource as string, settings); + const opacity = (dec.props.opacity as number) ?? 1; + + switch (dec.type) { + case "line": + ctx.strokeStyle = hexToRgba(color, opacity); + ctx.lineWidth = (dec.props.width as number) ?? 1; + ctx.beginPath(); + ctx.moveTo(dec.props.x1 as number, dec.props.y1 as number); + ctx.lineTo(dec.props.x2 as number, dec.props.y2 as number); + ctx.stroke(); + break; + + case "border": { + const inset = (dec.props.inset as number) ?? 20; + ctx.strokeStyle = hexToRgba(color, opacity); + ctx.lineWidth = (dec.props.width as number) ?? 1; + ctx.strokeRect(inset, inset, width - inset * 2, height - inset * 2); + break; + } + + case "corner-accents": { + const inset = (dec.props.inset as number) ?? 15; + const len = (dec.props.length as number) ?? 40; + const lw = (dec.props.width as number) ?? 3; + ctx.strokeStyle = hexToRgba(color, opacity); + ctx.lineWidth = lw; + ctx.lineCap = "round"; + + const corners = [ + [inset, inset + len, inset, inset, inset + len, inset], + [width - inset - len, inset, width - inset, inset, width - inset, inset + len], + [inset, height - inset - len, inset, height - inset, inset + len, height - inset], + [width - inset - len, height - inset, width - inset, height - inset, width - inset, height - inset - len], + ]; + + for (const [x1, y1, x2, y2, x3, y3] of corners) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.stroke(); + } + ctx.lineCap = "butt"; + break; + } + + case "gradient-bar": { + const gx = dec.props.x as number; + const gy = dec.props.y as number; + const gw = (dec.props.width as number) ?? 100; + const gh = (dec.props.height as number) ?? 3; + + const isVertical = gh > gw; + const gradient = isVertical + ? ctx.createLinearGradient(gx, gy, gx, gy + gh) + : ctx.createLinearGradient(gx, gy, gx + gw, gy); + + gradient.addColorStop(0, hexToRgba(color, 0)); + gradient.addColorStop(0.5, hexToRgba(color, opacity)); + gradient.addColorStop(1, hexToRgba(color, 0)); + + ctx.fillStyle = gradient; + ctx.fillRect(gx, gy, gw, gh); + break; + } + + case "glow": { + const glx = dec.props.x as number; + const gly = dec.props.y as number; + const gRadius = (dec.props.radius as number) ?? 80; + + const gradient = ctx.createRadialGradient(glx, gly, 0, glx, gly, gRadius); + gradient.addColorStop(0, hexToRgba(color, opacity)); + gradient.addColorStop(1, hexToRgba(color, 0)); + + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.arc(glx, gly, gRadius, 0, Math.PI * 2); + ctx.fill(); + break; + } + + case "rect": { + const rx = dec.props.x as number; + const ry = dec.props.y as number; + const rw = dec.props.width as number; + const rh = dec.props.height as number; + ctx.fillStyle = hexToRgba(color, opacity); + ctx.fillRect(rx, ry, rw, rh); + break; + } + } + } +} + +function loadImageElement(src: string): Promise { + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = "anonymous"; + img.onload = () => resolve(img); + img.onerror = reject; + img.src = src; + }); +} + +function replaceVariables(text: string, member: RenderInput["member"], guild: RenderInput["guild"]): string { + return text + .replaceAll("{user}", member.username) + .replaceAll("{user.name}", member.username) + .replaceAll("{user.displayname}", member.displayName) + .replaceAll("{server}", guild.name) + .replaceAll("{membercount}", guild.memberCount.toLocaleString()); +} + +export interface GenerateResult { + blob: Blob; + url: string; +} + +export async function renderWelcomeImagePreview(input: RenderInput): Promise { + await loadPreviewFonts(); + + const { settings, member, guild } = input; + const template = getTemplate(settings.template); + const { width, height } = template.canvas; + + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext("2d")!; + + await drawBackground(ctx, width, height, settings); + + drawOverlay(ctx, width, height, settings); + + drawDecorations(ctx, width, height, template.decorations, settings); + + await drawAvatar(ctx, template.avatar.x, template.avatar.y, template.avatar.size, settings, member.avatarUrl); + + const titleText = member.displayName; + drawText(ctx, titleText, template.title.x, template.title.y, { + font: settings.title.font, + size: settings.title.size || template.title.defaultSize, + color: settings.title.color, + align: template.title.align, + maxWidth: template.title.maxWidth, + }); + + const subtitleRaw = settings.subtitle.text; + const subtitleText = replaceVariables(subtitleRaw, member, guild); + drawText(ctx, subtitleText, template.subtitle.x, template.subtitle.y, { + font: settings.subtitle.font, + size: settings.subtitle.size || template.subtitle.defaultSize, + color: settings.subtitle.color, + align: template.subtitle.align, + maxWidth: template.subtitle.maxWidth, + }); + + const blob = await new Promise((resolve) => { + canvas.toBlob((b) => resolve(b!), "image/png"); + }); + + const url = URL.createObjectURL(blob); + + return { blob, url }; +} diff --git a/apps/dashboard/src/client/features/welcome/image/templates.ts b/apps/dashboard/src/client/features/welcome/image/templates.ts new file mode 100644 index 0000000..a4ea642 --- /dev/null +++ b/apps/dashboard/src/client/features/welcome/image/templates.ts @@ -0,0 +1,137 @@ +export interface TemplateDecoration { + type: "line" | "rect" | "gradient-bar" | "border" | "corner-accents" | "glow"; + props: Record; +} + +export interface TemplateLayout { + name: string; + displayName: string; + description: string; + canvas: { width: number; height: number }; + avatar: { x: number; y: number; size: number }; + title: { + x: number; + y: number; + align: "left" | "center" | "right"; + maxWidth: number; + defaultSize: number; + }; + subtitle: { + x: number; + y: number; + align: "left" | "center" | "right"; + maxWidth: number; + defaultSize: number; + }; + decorations: TemplateDecoration[]; +} + +const starterTemplate: TemplateLayout = { + name: "starter", + displayName: "Starter", + description: "Classic centered layout. Clean and universal — works for any server.", + canvas: { width: 1024, height: 450 }, + avatar: { x: 512, y: 150, size: 128 }, + title: { x: 512, y: 290, align: "center", maxWidth: 800, defaultSize: 36 }, + subtitle: { x: 512, y: 335, align: "center", maxWidth: 700, defaultSize: 20 }, + decorations: [ + { + type: "line", + props: { x1: 312, y1: 258, x2: 712, y2: 258, width: 2, colorSource: "accent", opacity: 0.4 }, + }, + ], +}; + +const neonTemplate: TemplateLayout = { + name: "neon", + displayName: "Neon", + description: "Cyberpunk-inspired with neon accents and glow effects. Built for gaming servers.", + canvas: { width: 1024, height: 450 }, + avatar: { x: 512, y: 155, size: 120 }, + title: { x: 512, y: 300, align: "center", maxWidth: 800, defaultSize: 40 }, + subtitle: { x: 512, y: 350, align: "center", maxWidth: 700, defaultSize: 18 }, + decorations: [ + { type: "border", props: { inset: 20, width: 2, colorSource: "accent", opacity: 0.5 } }, + { type: "corner-accents", props: { inset: 15, length: 40, width: 3, colorSource: "accent", opacity: 0.8 } }, + { type: "glow", props: { x: 512, y: 155, radius: 80, colorSource: "accent", opacity: 0.15 } }, + ], +}; + +const horizonTemplate: TemplateLayout = { + name: "horizon", + displayName: "Horizon", + description: "Landscape split with avatar on left and text on right. Modern and balanced.", + canvas: { width: 1024, height: 450 }, + avatar: { x: 180, y: 225, size: 140 }, + title: { x: 620, y: 195, align: "left", maxWidth: 380, defaultSize: 34 }, + subtitle: { x: 620, y: 240, align: "left", maxWidth: 380, defaultSize: 18 }, + decorations: [ + { type: "gradient-bar", props: { x: 380, y: 140, width: 3, height: 170, colorSource: "accent", opacity: 0.6 } }, + { type: "line", props: { x1: 620, y1: 270, x2: 950, y2: 270, width: 1, colorSource: "accent", opacity: 0.3 } }, + ], +}; + +const elegantTemplate: TemplateLayout = { + name: "elegant", + displayName: "Elegant", + description: "Refined layout with thin borders and subtle dividers. Premium and sophisticated.", + canvas: { width: 1024, height: 400 }, + avatar: { x: 512, y: 130, size: 96 }, + title: { x: 512, y: 240, align: "center", maxWidth: 700, defaultSize: 32 }, + subtitle: { x: 512, y: 280, align: "center", maxWidth: 600, defaultSize: 16 }, + decorations: [ + { type: "border", props: { inset: 30, width: 1, colorSource: "accent", opacity: 0.25 } }, + { type: "line", props: { x1: 412, y1: 205, x2: 612, y2: 205, width: 1, colorSource: "accent", opacity: 0.3 } }, + { type: "line", props: { x1: 362, y1: 310, x2: 662, y2: 310, width: 1, colorSource: "accent", opacity: 0.2 } }, + ], +}; + +const minimalTemplate: TemplateLayout = { + name: "minimal", + displayName: "Minimal", + description: "Ultra-clean with small avatar and compact text. Lets the background shine.", + canvas: { width: 1024, height: 350 }, + avatar: { x: 120, y: 175, size: 80 }, + title: { x: 210, y: 155, align: "left", maxWidth: 700, defaultSize: 28 }, + subtitle: { x: 210, y: 195, align: "left", maxWidth: 700, defaultSize: 16 }, + decorations: [ + { type: "line", props: { x1: 210, y1: 225, x2: 900, y2: 225, width: 1, colorSource: "accent", opacity: 0.15 } }, + ], +}; + +const auroraTemplate: TemplateLayout = { + name: "aurora", + displayName: "Aurora", + description: "Dreamy gradient effects with soft glow. Ethereal and atmospheric.", + canvas: { width: 1024, height: 450 }, + avatar: { x: 512, y: 145, size: 130 }, + title: { x: 512, y: 295, align: "center", maxWidth: 800, defaultSize: 36 }, + subtitle: { x: 512, y: 340, align: "center", maxWidth: 700, defaultSize: 18 }, + decorations: [ + { type: "glow", props: { x: 512, y: 145, radius: 100, colorSource: "accent", opacity: 0.12 } }, + { type: "gradient-bar", props: { x: 262, y: 260, width: 500, height: 3, colorSource: "accent", opacity: 0.4 } }, + { type: "glow", props: { x: 512, y: 262, radius: 250, colorSource: "accent", opacity: 0.06 } }, + ], +}; + +const templates = new Map([ + ["starter", starterTemplate], + ["neon", neonTemplate], + ["horizon", horizonTemplate], + ["elegant", elegantTemplate], + ["minimal", minimalTemplate], + ["aurora", auroraTemplate], +]); + +export function getTemplate(name: string): TemplateLayout { + return templates.get(name) ?? starterTemplate; +} + +export const PRESET_GRADIENTS: Record = { + midnight: ["#0f0c29", "#302b63", "#24243e"], + ocean: ["#141e30", "#243b55"], + sunset: ["#1a0a2e", "#6b2fa0", "#d4418e"], + forest: ["#0a1a0a", "#1b4332", "#2d6a4f"], + nebula: ["#16082b", "#4a1a7a", "#1a0a3e"], + ember: ["#1a0a0a", "#7a1a1a", "#3e1a0a"], +}; diff --git a/apps/dashboard/src/server/index.ts b/apps/dashboard/src/server/index.ts index a452f3d..725aaf3 100644 --- a/apps/dashboard/src/server/index.ts +++ b/apps/dashboard/src/server/index.ts @@ -3,9 +3,9 @@ import fastifyCookie from "@fastify/cookie"; import fastifyHelmet from "@fastify/helmet"; import fastifyRateLimit from "@fastify/rate-limit"; import fastifyStatic from "@fastify/static"; +import { fileURLToPath } from "node:url"; import { join, dirname } from "node:path"; import { readFile } from "node:fs/promises"; -import { fileURLToPath } from "node:url"; import { config } from "@fluxcore/config"; import { logger } from "@fluxcore/utils"; import { @@ -104,6 +104,13 @@ async function main(): Promise { }); }); + // Serve uploaded images (welcome backgrounds, etc.) + app.register(fastifyStatic, { + root: "/data/uploads", + prefix: "/uploads/", + decorateReply: false, + }); + registerAuthRoutes(app); registerGuildRoutes(app); registerTempVoiceRoutes(app); diff --git a/docs/module-guide.md b/docs/module-guide.md new file mode 100644 index 0000000..eff342b --- /dev/null +++ b/docs/module-guide.md @@ -0,0 +1,1112 @@ +# FluxCore — Module Guide + +> A comprehensive deep-dive into every module, package, and system in the FluxCore monorepo. +> **Version:** 1.0.0 | **Last Updated:** 2026-07-08 + +--- + +## Table of Contents + +1. [Project Architecture](#1-project-architecture) +2. [Infrastructure Packages](#2-infrastructure-packages) + - [@fluxcore/config](#21-fluxcoreconfig) + - [@fluxcore/types](#22-fluxcoretypes) + - [@fluxcore/utils](#23-fluxcoreutils) + - [@fluxcore/database](#24-fluxcoredatabase) + - [@fluxcore/i18n](#25-fluxcorei18n) +3. [Systems Package](#3-systems-package) + - [Actions / Automation](#31-actions--automation) + - [Warnings & Escalation](#32-warnings--escalation) + - [Moderation](#33-moderation) + - [Logging](#34-logging) + - [TempVoice](#35-tempvoice) + - [Music](#36-music) + - [Welcome & Farewell](#37-welcome--farewell) + - [Role Panels](#38-role-panels) + - [Leveling / XP](#39-leveling--xp) + - [Tickets](#310-tickets) + - [Suggestions](#311-suggestions) + - [Starboard](#312-starboard) + - [Giveaways](#313-giveaways) + - [Anti-Raid](#314-anti-raid) + - [Custom Commands](#315-custom-commands) + - [Scheduled Messages](#316-scheduled-messages) +4. [Bot Application](#4-bot-application) + - [Commands](#42-commands) + - [Events](#43-events) + - [Feature Systems](#44-feature-systems) + - [Shared Infrastructure](#45-shared-infrastructure) +5. [Dashboard Application](#5-dashboard-application) + - [Server API Routes](#52-server-api-routes) + - [Client Pages](#53-client-pages) + - [Auth & Security](#54-auth--security) + - [Dashboard Permissions](#55-dashboard-permissions) +6. [Design System](#6-design-system) +7. [Infrastructure & Tooling](#7-infrastructure--tooling) + +--- + +## 1. Project Architecture + +FluxCore is a modular Discord bot framework with an integrated admin dashboard. It follows a monorepo structure managed by pnpm and Turborepo. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ FluxCore │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────┐ ┌───────────────────────────┐ │ +│ │ apps/bot │ │ apps/dashboard │ │ +│ │ Discord Bot │ │ Admin Dashboard │ │ +│ │ (discord.js v14) │ │ (Fastify 5 + React 19) │ │ +│ │ Shoukaku (Lavalink)│ │ Vite 6, TanStack Router │ │ +│ └────────┬────────────┘ └───────────┬───────────────┘ │ +│ │ │ │ +│ └──────────┬───────────────────┘ │ +│ │ │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ Shared Packages │ │ +│ │ │ │ +│ │ @fluxcore/systems → Business logic (16 modules) │ │ +│ │ @fluxcore/database → Prisma ORM + PostgreSQL │ │ +│ │ @fluxcore/config → Environment & configuration │ │ +│ │ @fluxcore/types → Shared TypeScript interfaces │ │ +│ │ @fluxcore/utils → Logger, embeds, permissions │ │ +│ │ @fluxcore/i18n → Internationalization (50+ langs)│ │ +│ └────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Key Design Principles:** + +- **Single Source of Truth:** PostgreSQL is canonical; in-memory caches sync back via a cache invalidation pipeline. +- **Command vs Dashboard Split:** Slash commands are for fast in-context actions only. All configuration is dashboard-only to conserve Discord's 100 global command limit. +- **3+ Command Rule:** If a task needs more than 3 commands to complete, it belongs in the dashboard. +- **Feature Toggles:** All features are per-guild configurable via database models. +- **Docker-First:** All development runs inside Docker containers using `docker-compose`. + +--- + +## 2. Infrastructure Packages + +### 2.1 `@fluxcore/config` + +**Location:** `packages/config/` + +**Purpose:** Centralized environment variable loading and typed configuration access for the entire project. + +**How it works:** + +1. `loadConfig()` reads environment variables using `dotenv` +2. Resolves Docker secret files (`*_FILE` convention — if `FOO_FILE` is set, reads the file at that path for the value) +3. Validates required variables and returns a typed `Config` object +4. The singleton `config` object is exported for use across all packages + +**Configuration categories:** + +| Category | Variables | Example | +|----------|-----------|---------| +| Discord | `DISCORD_TOKEN`, `DISCORD_CLIENT_ID`, `DISCORD_GUILD_ID` | Auth + deployment | +| Dashboard | `DASHBOARD_PORT`, `DASHBOARD_CLIENT_SECRET`, `DASHBOARD_SESSION_SECRET`, `DASHBOARD_CALLBACK_URL`, `DASHBOARD_PUBLIC_URL` | OAuth, sessions, CSRF | +| Bot Sync | `BOT_SYNC_PORT`, `BOT_SYNC_SECRET`, `BOT_SYNC_URL` | Dashboard→bot cache invalidation | +| Database | `DATABASE_USER`, `DATABASE_PASSWORD`, `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME` | PostgreSQL connection | +| Lavalink | `LAVALINK_HOST`, `LAVALINK_PORT`, `LAVALINK_PASSWORD` | Music system | +| General | `LOG_LEVEL`, `NODE_ENV` | Runtime behavior | + +**Key files:** +- `src/index.ts` — `loadConfig()`, `Config` interface, exported `config` singleton +- `src/secret-files.ts` — Docker secret file resolution (`readSecretFile()`) + +--- + +### 2.2 `@fluxcore/types` + +**Location:** `packages/types/` + +**Purpose:** Shared TypeScript interfaces and type definitions consumed by bot, dashboard, and systems packages. + +**Modules defined:** + +| File | Contents | +|------|----------| +| `src/Command.ts` | Slash command type definition (`Command` interface with data, category, cooldown, execute) | +| `src/Event.ts` | Gateway event type definition (`Event` interface with name, once, execute) | +| `src/anti-raid.ts` | Anti-raid configuration types (join rate, account age, nuke thresholds) | +| `src/custom-commands.ts` | Custom command trigger types (command/keyword/startsWith/regex) and response types | +| `src/dashboard-permissions.ts` | Permission registry (49 permissions across 15 modules), `matchPermission()`, `expandWildcard()`, `ROLE_PRESETS` | +| `src/giveaways.ts` | Giveaway types (prize, winners, requirements) | +| `src/scheduled-messages.ts` | Scheduled message types + `CRON_PRESETS` | +| `src/suggestions.ts` | Suggestion types (status, votes) | +| `src/tickets.ts` | Ticket category, form field, panel types | + +--- + +### 2.3 `@fluxcore/utils` + +**Location:** `packages/utils/` + +**Purpose:** Shared utility functions used across all packages. + +**Modules:** + +| File | Export | Purpose | +|------|--------|---------| +| `src/logger.ts` | `logger`, `redactSensitive` | Structured pino-based logger with automatic redaction of tokens/secrets | +| `src/embeds.ts` | `successEmbed`, `errorEmbed`, `infoEmbed`, `warnEmbed` | Discord.js embed builders with consistent styling (color, icon, footer) | +| `src/permissions.ts` | `checkPermissions()`, `checkBotPermissions()`, `isAboveTarget()` | Discord permission checks for command handlers | +| `src/time.ts` | `parseDuration()`, `formatDuration()` | Human-readable duration parsing ("7d", "2h30m") and formatting | +| `src/files.ts` | `getFiles()` | Recursive file discovery for dynamic command/event loading | + +--- + +### 2.4 `@fluxcore/database` + +**Location:** `packages/database/` + +**Purpose:** Database layer — Prisma 7 ORM client generation, connection management, and the canonical schema for the entire project. + +**Key files:** + +- `prisma/schema.prisma` — The canonical database schema (30 models) +- `prisma/migrations/` — Migration history (11 applied migrations) +- `src/index.ts` — Public API exports +- `src/client.ts` — Prisma client singleton with connection lifecycle + +**Connection flow:** +1. `connectDatabase()` builds the `DATABASE_URL` from config values +2. Creates a Prisma client instance with the PostgreSQL adapter +3. Tests the connection (throws if unreachable) +4. `getPrisma()` returns the singleton +5. `disconnectDatabase()` cleanly shuts down on app exit + +**Data conventions:** +- `Int @id @default(autoincrement())` for all model IDs +- Discord IDs stored as `String` (snowflakes) +- JSON data stored as `String @default("[]")` or `String @default("{}")` +- Compound indexes: `@@index([guildId, ...])` on every guild-scoped model +- Compound uniques: `@@unique([guildId, name])` where applicable +- Timestamps: `createdAt @default(now())`, `updatedAt @updatedAt` + +**Database models by domain** (see individual module sections for details): + +| Domain | Models | +|--------|--------| +| TempVoice | `TempVoiceGuildConfig`, `TempVoiceUserSettings` | +| Automation | `ActionRule`, `ActionLog`, `ActionGuildSettings`, `ActionCacheInvalidation` | +| Reminders | `Reminder` | +| Auth | `DashboardSession` | +| Music | `MusicGuildSettings`, `MusicLibraryAlbum`, `MusicLibraryTrack` | +| Logging | `LogGuildConfig`, `LogEntry` | +| Warnings | `Warning`, `WarnPunishment`, `WarnGuildSettings` | +| Moderation | `ModCase`, `ModGuildSettings` | +| Welcome | `WelcomeConfig` | +| Role Panels | `RolePanel` | +| Leveling | `UserLevel`, `LevelReward`, `LevelGuildSettings` | +| Scheduled Messages | `ScheduledMessage` | +| Custom Commands | `CustomCommand` | +| Anti-Raid | `AntiRaidConfig`, `RaidEvent` | +| Tickets | `TicketPanel`, `Ticket`, `TicketGuildSettings` | +| Giveaways | `Giveaway`, `GiveawayCacheInvalidation` | +| Suggestions | `Suggestion`, `SuggestionGuildSettings` | +| Starboard | `StarboardEntry`, `StarboardGuildSettings` | +| Dashboard Permissions | `DashboardGuildSettings`, `DashboardRole`, `DashboardRoleAssignment`, `DashboardUserPermission`, `DashboardAuditLog` | + +--- + +### 2.5 `@fluxcore/i18n` + +**Location:** `packages/i18n/` + +**Purpose:** Full internationalization for the dashboard (client + server) supporting 50+ languages including RTL (Arabic, Hebrew, Urdu, Persian). + +**Architecture:** + +``` +packages/i18n/ + src/ + index.ts → Re-exports types and language registry + types.ts → i18n type definitions + languages.ts → Supported languages list, RTL detection + server.ts → Server-side i18next init (FsBackend) + client.ts → Client-side react-i18next init (HttpBackend) + locales/en/ → Translation files (common.json, errors.json) + scripts/ + translate.ts → Translation automation script +``` + +**Namespaces:** +- `common` — Navigation, buttons, shared UI text +- `errors` — All error messages + +**Server usage:** `Accept-Language` header parsing → `getTranslation(lng)` → translated error responses +**Client usage:** `react-i18next` hooks → language switcher in nav → localStorage persistence + +**Supported languages (initial):** 48 languages covering European, Asian, Middle Eastern, and African language families. RTL languages get automatic layout switching via Tailwind CSS logical properties. + +--- + +## 3. Systems Package + +**Package:** `@fluxcore/systems` | **Location:** `packages/systems/` + +This is the core business logic layer — 16 modules of shared stateful logic consumed by both the bot and the dashboard. Each module follows a consistent structure: + +``` +{module}/ + types.ts → TypeScript interfaces and enums + constants.ts → Default values, limits, configuration constants + config.ts → Guild-specific settings with in-memory cache + persistence.ts → Database CRUD operations + cacheSync.ts → Cache invalidation polling (for cachable modules) +``` + +--- + +### 3.1 Actions / Automation + +**Status:** Done | **Spec:** `docs/features/actions.md` + +A rule engine that triggers automated actions when events occur. This is the most architecturally complex module in the codebase. + +**Core concept:** An Action Rule consists of: +- **Event** (what happens — 19 event types) +- **Conditions** (when to fire — optional filters) +- **Actions** (what to do — 10 action types) + +**Event types:** +- `memberJoin`, `memberLeave`, `memberUpdate` (username/nickname/roles) +- `messageSent`, `messageEdited`, `messageDeleted` +- `channelCreated`, `channelDeleted`, `channelUpdated` +- `roleCreated`, `roleDeleted`, `roleUpdated` +- `voiceJoin`, `voiceLeave`, `voiceMove`, `voiceStream` +- `modWarn`, `modTimeout`, `modKick`, `modBan` + +**Action types:** +- `sendMessage` — Post a message (text or embed) to a channel +- `addRole` / `removeRole` — Modify member roles +- `timeout` / `removeTimeout` — Manage timed-out state +- `kick` / `ban` — Punitive actions +- `warn` — Issue a warning (feeds into warning system) +- `log` — Write to log entries +- `webhook` — Send to external webhook + +**Template engine:** Messages support dynamic variables: `{user.name}`, `{user.mention}`, `{server.name}`, `{membercount}`, `{action.reason}`, etc. + +**Architecture:** +``` +packages/systems/src/actions/ + types.ts → ActionRule, ActionEvent, ActionCondition, ActionResult + constants.ts → Default limits (50 rules max per guild) + config.ts → ActionGuildSettings with cache (5s TTL) + persistence.ts → ActionRule CRUD, reorder, log queries + cacheSync.ts → Polls ActionCacheInvalidation, reloads rules + templateEngine.ts → `{variable}` substitution engine +``` + +**Cache sync flow:** +1. Dashboard writes to database → creates `ActionCacheInvalidation` record +2. Bot's sync server polls for invalidations (every 3 seconds) +3. Bot reloads caches → acknowledges invalidation + +--- + +### 3.2 Warnings & Escalation + +**Status:** Done | **Spec:** `docs/features/warn-system.md` + +A progressive discipline system — warnings are tracked per-user, and configurable thresholds automatically escalate to timeouts, kicks, or bans. + +**Database models:** +- `Warning` — guildId, userId, moderatorId, reason, createdAt +- `WarnPunishment` — guildId, threshold (number of warnings), action (timeout/kick/ban), duration +- `WarnGuildSettings` — guildId, dmOnWarn, reasonRequired, maxWarnings + +**Escalation flow:** +1. Moderator issues `/warn` (or action rule triggers a warn action) +2. Warning stored in database +3. Bot checks the user's total active warnings against configured thresholds +4. If a threshold is met, the punishment action fires automatically +5. DM sent to user (if `dmOnWarn` enabled) + +**Key constants:** `MAX_REASON_LENGTH = 1000`, `MAX_WARNINGS_PER_USER = 500` + +--- + +### 3.3 Moderation + +**Status:** Done | **Spec:** `docs/features/moderation.md` + +Enhanced moderation commands with a unified case tracking system. Every punishment creates a `ModCase` entry for auditing. + +**Database models:** +- `ModCase` — guildId, targetId, moderatorId, action, reason, duration, expiresAt, active +- `ModGuildSettings` — guildId, dmOnPunishment, modLogChannelId + +**Commands that create ModCase entries:** +- `/ban`, `/kick`, `/timeout` — Extended with ModCase creation + DM +- `/tempban` — Temporary ban with automatic unban via scheduler +- `/softban` — Ban + immediate unban (clears messages) +- `/lock` / `/unlock` — Channel lockdown (deny `SendMessages`) +- `/case` / `/cases` — View individual case or all cases for a user +- `/note` — Create a case with action type "NOTE" (no actual punishment) +- `/purge` — Bulk delete with filters (by user, contains text, before message, after time) +- `/clear` / `delete` — Message deletion by count + +**Scheduler:** The moderation module runs a 60-second interval that checks for expired tempbans and auto-unbans users. + +--- + +### 3.4 Logging + +**Status:** Done | **Spec:** `docs/features/logging.md` + +Real-time event logging to configurable Discord channels. 7 categories with per-event toggles, ignored channels/roles, and a dashboard log viewer. + +**Categories:** +- `message` — Deletes, edits, bulk deletes +- `member` — Joins, leaves, nickname/role updates +- `voice` — Join, leave, move, stream start/stop +- `channel` — Create, delete, update +- `role` — Create, delete, update +- `server` — Guild settings changes +- `moderation` — All punitative actions + +**Database models:** +- `LogGuildConfig` — guildId, category, channelId, enabled, ignoredChannels, ignoredRoles, enabledEvents (all JSON) +- `LogEntry` — guildId, category, eventType, targetId, executorId, content (JSON blob), createdAt + +**Key features:** +- Per-event toggles (you can log message deletes but not edits) +- Ignored channels/roles (staff channels don't get logged) +- Message content captures (edited before/after) +- Bulk delete exports (file attachment) +- 90-day retention with automatic cleanup +- Partials support (for uncached message events) + +--- + +### 3.5 TempVoice + +**Status:** Done | **Spec:** `docs/features/tempvoice.md` + +Temporary voice channels — users join a hub channel and get a private channel created for them, which is automatically deleted when empty. + +**Database models:** +- `TempVoiceGuildConfig` — guildId, hubChannelId, categoryId, nameFormat, userLimit, bitrate +- `TempVoiceUserSettings` — guildId, userId, channelId, name, limit, locked, hidden, bitrate, bannedUsers + +**Flow:** +1. User joins the designated hub voice channel +2. Bot creates a new voice channel in the configured category +3. User is moved to the new channel +4. User can rename, limit, lock, hide via `/tempvoice` or buttons +5. When the channel is empty, it's automatically deleted + +**User commands (via `/tempvoice`):** +- `name ` — Rename channel +- `limit ` — Set user limit +- `lock` / `unlock` — Toggle join permission +- `hide` / `reveal` — Toggle channel visibility +- `ban ` / `unban ` — Per-channel user bans +- `claim` — Transfer ownership (if owner left) + +--- + +### 3.6 Music + +**Status:** Done | **Spec:** `docs/features/music.md` + +Music playback system using Lavalink (via Shoukaku). Supports guild-specific settings and a library system for organizing saved tracks. + +**Database models:** +- `MusicGuildSettings` — guildId, mode (normal/dj-only), djRoleId, defaultVolume, twentyFourSeven (stay in VC when empty) +- `MusicLibraryAlbum` — guildId, name, coverUrl, createdBy +- `MusicLibraryTrack` — guildId, albumId, title, url, addedBy + +**Bot integration:** +- `/play ` — Search and play from YouTube (via Lavalink) +- `/queue` — View current queue +- Buttons: skip, stop, pause/resume, loop, shuffle, volume +- Panel embed that auto-updates as tracks change + +**Dj-only mode:** Only users with the DJ role can control playback. + +**24/7 mode:** Bot stays in voice channel even when the queue is empty. + +--- + +### 3.7 Welcome & Farewell + +**Status:** Not Started | **Spec:** `docs/features/welcome-farewell.md` + +Customizable welcome messages (channel + DM) and farewell messages when members join/leave. + +**Database model:** `WelcomeConfig` — guildId, welcome/farewell toggles, channel IDs, message templates (JSON embeds), DM settings, auto-role IDs + +**Features:** +- Rich embeds with variable substitution (`{user}`, `{server}`, `{membercount}`, `{joindate}`, etc.) +- Welcome message sent to a channel AND optionally as a DM +- Farewell message sent when member leaves +- Auto-role assignment (add roles on join) +- Visual embed builder in the dashboard with live preview + +**Bot commands:** `/welcome test` — sends test welcome message using current config + +--- + +### 3.8 Role Panels + +**Status:** Not Started | **Spec:** `docs/features/reaction-roles.md` + +Self-assignable roles via reactions, buttons, or dropdown menus on messages. + +**Database model:** `RolePanel` — guildId, channelId, messageId, name, type (reaction/button/dropdown), mode (toggle/unique/verify), embed config, roles array, maxRoles, minRoles + +**Modes:** +- `toggle` — Click to gain, click again to lose +- `unique` — Only one role from the panel at a time +- `verify` — One-time add (cannot remove) + +**Constraints:** +- Max 25 roles per panel +- Bot must have role hierarchy position above assigned roles +- Panel creation/editing is entirely dashboard-based + +**Bot interaction:** `/rolepanel send [channel]` — sends a panel to a channel. Button clicks and dropdown selections handled via `interactionCreate` with prefix-based custom IDs (`rp_`, `rpd_`). + +--- + +### 3.9 Leveling / XP + +**Status:** Not Started | **Spec:** `docs/features/leveling.md` + +Gamification system — members earn XP for messages and voice activity, gain levels, unlock role rewards. + +**Database models:** +- `UserLevel` — guildId, userId, xp, level, messageCount, voiceMinutes, lastMessageXp +- `LevelReward` — guildId, level, roleId +- `LevelGuildSettings` — guildId, enabled, xpPerMessage, xpCooldown, voiceXpPerMinute, announcement config, exclusions, multipliers + +**XP formula:** `5 * level² + 50 * level + 100` XP required per level + +**XP sources:** +- **Message XP:** 15-25 XP per message (random range), 60-second cooldown per user. Configurable per-guild. +- **Voice XP:** 1-3 XP per minute in voice, only when not muted/deafened with ≥2 non-bot members. + +**Bot commands:** +- `/rank [user]` — Show XP card (everyone) +- `/leaderboard [page]` — Guild leaderboard (everyone) +- `/xp set/add/remove ` — Manage XP (ManageGuild) + +**Role rewards:** Checked on level-up AND when reward config changes (retroactive). + +--- + +### 3.10 Tickets + +**Status:** Not Started | **Spec:** `docs/features/tickets.md` + +Private support channel system — users click a panel button to open a private ticket channel. + +**Database models:** +- `TicketPanel` — guildId, channelId, messageId, name, embed config, categories (form fields), createdBy +- `Ticket` — guildId, channelId, userId, category, panelId, status (open/closed/claimed), claimedBy, form responses, transcript +- `TicketGuildSettings` — guildId, staff roles, transcript channel, maxOpenPerUser, autoCloseHours + +**Flow:** +1. User clicks a panel button +2. If the panel has form fields, a Discord modal appears (max 5 fields) +3. Private channel created in configured category +4. Opening embed displays user info and category +5. Staff can claim, close, add/remove users +6. On close, HTML transcript generated and saved + +**Bot commands:** `/ticket close`, `/ticket add `, `/ticket remove `, `/ticket claim`, `/ticket transcript` + +**Auto-close:** Tickets auto-close after configurable hours of inactivity with a 1-hour warning. + +--- + +### 3.11 Suggestions + +**Status:** Not Started | **Spec:** `docs/features/suggestions.md` + +Community suggestion submission system with voting and status management. + +**Database models:** +- `Suggestion` — guildId, userId, content, status (pending/approved/denied/implemented), upvotes, downvotes +- `SuggestionGuildSettings` — guildId, channelId, reviewChannelId, dmOnStatusChange, autoThread, anonymousMode + +**Flow:** +1. User submits `/suggest ` +2. Bot posts an embed in the suggestions channel with upvote/downvote reactions +3. Optional auto-created discussion thread +4. Moderator approves/denies/implement with reason +5. Embed updates with new status + DM sent to submitter + +**Bot commands:** `/suggest ` (everyone), `/suggestion approve/deny/implement [reason]` (ManageGuild) + +--- + +### 3.12 Starboard + +**Status:** Not Started | **Spec:** `docs/features/starboard.md` + +Auto-reposts popular messages to a highlights channel when they reach a configurable star reaction threshold. + +**Database models:** +- `StarboardEntry` — guildId, originalMessageId, originalChannelId, starboardMessageId, authorId, starCount +- `StarboardGuildSettings` — guildId, channelId, emoji, threshold, selfStar, ignoredChannels, nsfwHandling + +**Flow:** +1. Users react to a message with the star emoji +2. Bot tracks reaction counts (via `messageReactionAdd` / `messageReactionRemove`) +3. When count reaches threshold, message is reposted to starboard channel +4. If count drops below threshold, message is removed from starboard +5. Self-starring optionally allowed + +**Bot commands:** None — fully dashboard-configured. The starboard is entirely event-driven. + +--- + +### 3.13 Giveaways + +**Status:** Not Started | **Spec:** `docs/features/giveaways.md` + +Timed giveaway system with reaction entry, role requirements, multiple winners, and reroll capability. + +**Database model:** `Giveaway` — guildId, channelId, messageId, hostId, prize, winner count, endsAt, winnerIds, requiredRoleIds + +**Flow:** +1. `/giveaway start [winners] [role]` +2. Bot posts an embed with the 🎉 reaction +3. Users react to enter +4. Scheduler checks every 30 seconds for expired giveaways +5. Winners selected randomly, embed updated, DMs sent +6. `/giveaway reroll` picks new winners if needed + +--- + +### 3.14 Anti-Raid + +**Status:** Not Started | **Spec:** `docs/features/anti-raid.md` + +Protection against raids (mass joins), nuking (mass deletions/changes), and suspicious accounts. + +**Database models:** +- `AntiRaidConfig` — guildId, join threshold/window/action, accountAgeMinDays/action, anti-nuke config, whitelisted roles, lockdown settings +- `RaidEvent` — guildId, eventType, details, timestamp + +**Detection mechanisms:** +- **Join rate:** In-memory timestamp tracker per guild. If N joins in M seconds exceeded, configured action fires (kick/ban/lockdown). +- **Account age:** Check on join — if account is younger than threshold, action fires. +- **Anti-nuke:** In-memory tracker for mass channel/role deletions and bans. Tracks by executor, not per-guild. +- **Lockdown:** Temporarily prevents all joins (via channel permission changes). + +**Bot commands:** `/lockdown`, `/lockdown lift` — only two slash commands for this entire module. + +--- + +### 3.15 Custom Commands + +**Status:** Not Started | **Spec:** `docs/features/custom-commands.md` + +User-created text commands and auto-responders. Entirely managed via dashboard — no slash commands. + +**Database model:** `CustomCommand` — guildId, name, triggerType, response (JSON), actions (JSON), cooldown, allowedRoles, allowedChannels, deletesTrigger, dmResponse + +**Trigger types:** +- `command` — `!name` prefix-triggered +- `keyword` — Message contains the keyword +- `startsWith` — Message starts with the text +- `regex` — Regular expression match + +**Response types:** +- Text message +- Rich embed (configured via dashboard builder) +- DM response +- Role actions (add/remove) + +**Limit:** 50 custom commands per guild. + +--- + +### 3.16 Scheduled Messages + +**Status:** Not Started | **Spec:** `docs/features/scheduled-messages.md` + +Recurring auto-posted messages on cron schedules. Dashboard-only management. + +**Database model:** `ScheduledMessage` — guildId, channelId, name, message (JSON), cronExpr, timezone, enabled, lastRunAt, nextRunAt + +**Cron presets:** Every hour, every 6 hours, daily at 9am/midnight, weekly (Monday 9am), monthly (1st at 9am) + +**Scheduler:** Polls every 60 seconds for messages where `nextRunAt <= now AND enabled`, sends them, then computes the next run time. + +**No slash commands** — all CRUD is dashboard-only. + +--- + +## 4. Bot Application + +**Package:** `@fluxcore/bot` | **Location:** `apps/bot/` + +**Entry point:** `src/index.ts` + +**Startup flow:** +1. Loads configuration (`@fluxcore/config`) +2. Connects database (`@fluxcore/database`) +3. Creates `ExtendedClient` (custom Discord.js Client) +4. Loads commands dynamically from per-module directories +5. Loads events dynamically from per-module feature directories +6. Initializes Shoukaku (Lavalink client for music) +7. Starts the sync HTTP server (for dashboard→bot cache invalidation) +8. Logs in to Discord +9. Registers graceful shutdown handlers + +--- + +### 4.1 Command Architecture + +Commands are organized by module in `src/commands/`: + +``` +commands/ + general/ → General purpose (8 commands) + moderation/ → Punitive & case management (15 commands) + utility/ → Misc utilities (3 commands) + music/ → Music playback (2 commands) + tempvoice/ → Temp voice management (1 command) + tickets/ → Ticket management (5 commands) + suggestions/ → Suggestion management (2 commands) + giveaways/ → Giveaway management (1 command) + leveling/ → XP/Rank/Leaderboard (3 commands) +``` + +**Each command exports:** +```typescript +export const command: Command = { + data: SlashCommandBuilder, // Discord slash command definition + category: string, // Module category + cooldown: number, // Cooldown in seconds + execute: Function, // Command handler +}; +``` + +**Command guard flow:** +1. `interactionCreate` event fires +2. Command handler resolves the command file +3. `checkPermissions()` — user has required Discord permissions +4. `checkBotPermissions()` — bot has required Discord permissions +5. `isAboveTarget()` — moderator is above target in role hierarchy +6. Cooldown check (per-user, per-command) +7. Feature toggle check (is this feature enabled for this guild?) +8. Execute command → success embed or error embed + +--- + +### 4.2 Events + +Events are organized in `src/events/`: + +``` +events/ + ready.ts → Bot startup complete + interactionCreate.ts → Slash commands, buttons, modals, selects + messageCreate.ts → Message XP, custom commands, swear filter + messageDelete.ts → Logging + messageUpdate.ts → Logging + messageBulkDelete.ts → Logging + messageReactionAdd.ts → Starboard, role panels + messageReactionRemove.ts → Starboard, role panels + guildMemberAdd.ts → Welcome, anti-raid, auto-role + guildMemberRemove.ts → Farewell, logging + guildMemberUpdate.ts → Logging + guildBanAdd.ts → Logging, anti-nuke + guildBanRemove.ts → Logging + voiceStateUpdate.ts → TempVoice, music, voice XP, logging + channelCreate.ts → Logging, anti-nuke + channelDelete.ts → Logging, anti-nuke + channelUpdate.ts → Logging + roleCreate.ts → Logging, anti-nuke + roleDelete.ts → Logging, anti-nuke + roleUpdate.ts → Logging + guildUpdate.ts → Logging +``` + +--- + +### 4.3 Feature Systems (Bot-Side) + +These are bot-specific implementations that consume the shared `@fluxcore/systems` package: + +| System | Directory | Purpose | +|--------|-----------|---------| +| **Automation** | `features/automation/system/` | Event bridge — listens to all 19 event types, matches against action rules, executes matched actions. Includes sync server (HTTP endpoint for dashboard→bot cache invalidation). | +| **Music** | `features/music/system/` | Shoukaku node manager, queue controller, track event handlers, now-playing panel (auto-updating embed), settings reactor (applies config changes in real-time). | +| **TempVoice** | `features/tempvoice/system/` | Voice channel lifecycle manager — creates channels on join, deletes on empty, handles button interactions (rename, limit, lock, hide, claim, ban). | +| **Tickets** | `features/tickets/system/` | Panel button handlers, ticket modal forms, channel permission management, auto-close scheduler. | +| **Giveaways** | `features/giveaways/system/` | Reaction entry tracking, winner selection, embed updates. | + +--- + +### 4.4 Shared Bot Infrastructure + +| File | Purpose | +|------|---------| +| `shared/client/ExtendedClient.ts` | Custom `Client` subclass with database, Shoukaku, sync server references | +| `shared/handlers/commandHandler.ts` | Dynamic command loading from `commands//` directories | +| `shared/handlers/eventHandler.ts` | Dynamic event registration from `events/` and `features/*/events/` | +| `shared/systems/reminders.ts` | Polling-based reminder delivery system | +| `shared/systems/customCommandHandler.ts` | `messageCreate` listener for custom commands (trigger matching + execution) | +| `shared/systems/permissionAudit.ts` | Permission check logging for audit trail | + +--- + +## 5. Dashboard Application + +**Package:** `@fluxcore/dashboard` | **Location:** `apps/dashboard/` + +**Stack:** Fastify 5 (API server) + React 19 SPA (client), Vite 6, TanStack Router, TanStack Query, Tailwind CSS 4, Radix UI, shadcn/ui, Recharts, @xyflow/react + +**Entry point:** `src/server/index.ts` + +**Startup flow:** +1. Loads configuration +2. Creates Fastify instance with cookie parser, helmet, rate limiter +3. Sets up CSRF double-submit pattern +4. Initializes server-side i18n +5. Registers 19+ API route files +6. In production, serves the built React SPA (from `dist/client/`) +7. Health check endpoint (`/api/health`) +8. Scheduled session cleanup (every hour) +9. Starts listening on configured port + +--- + +### 5.1 Client-Server Split + +``` +apps/dashboard/src/ + server/ → Fastify API server (TypeScript) + index.ts → Server bootstrap + features/ → Per-module route groups + shared/ → Auth, crypto, middleware, Discord API wrappers + client/ → React 19 SPA (TypeScript + TSX) + main.tsx → React entry point + routes/ → TanStack Router route tree + components/ → UI components (shadcn/ui, custom) + features/ → Page-level components, landing page + hooks/ → TanStack Query hooks + lib/ → API client, i18n client, utils + styles/ → Tailwind CSS entry +``` + +--- + +### 5.2 Server API Routes + +| Route File | Endpoints | Purpose | +|------------|-----------|---------| +| `features/auth/routes.ts` | `GET /auth/login`, `/auth/callback`, `/auth/csrf`, `/auth/logout`, `/auth/me` | Discord OAuth2 login flow | +| `features/guilds/routes.ts` | `GET /api/guilds` | List user's guilds (where admin) | +| `features/discord/routes.ts` | `GET /api/guilds/:id/channels`, `/roles`, `/members` | Proxy to Discord API | +| `features/moderation/routes.ts` | CRUD `/api/guilds/:id/moderation/` | ModCases, mod settings | +| `features/moderation/warnings-routes.ts` | CRUD `/api/guilds/:id/warnings/` | Warnings, punishments, settings | +| `features/logging/routes.ts` | `GET /api/guilds/:id/logs` | Log browser, log config | +| `features/actions/routes.ts` | CRUD `/api/guilds/:id/actions/` | Action rules, reorder, analytics | +| `features/music/routes.ts` | CRUD `/api/guilds/:id/music/` | Music settings, library | +| `features/tempvoice/routes.ts` | CRUD `/api/guilds/:id/tempvoice/` | Temp voice config | +| `features/welcome/routes.ts` | `GET/PUT /api/guilds/:id/welcome` | Welcome/farewell config | +| `features/roles/routes.ts` | CRUD `/api/guilds/:id/role-panels/` | Role panels | +| `features/leveling/routes.ts` | CRUD `/api/guilds/:id/leveling/` | Levels, leaderboard, rewards, settings | +| `features/tickets/routes.ts` | CRUD `/api/guilds/:id/tickets/` | Tickets, panels, settings | +| `features/suggestions/routes.ts` | CRUD `/api/guilds/:id/suggestions/` | Suggestions, settings | +| `features/starboard/routes.ts` | `GET/PUT /api/guilds/:id/starboard/` | Starboard entries, settings | +| `features/giveaways/routes.ts` | CRUD `/api/guilds/:id/giveaways/` | Giveaways, end/reroll | +| `features/scheduled/routes.ts` | CRUD `/api/guilds/:id/scheduled/` | Scheduled messages | +| `features/commands/routes.ts` | CRUD `/api/guilds/:id/custom-commands/` | Custom commands | +| `features/security/routes.ts` | CRUD `/api/guilds/:id/antiraid/` | Anti-raid config, events | +| `features/permissions/routes.ts` | `GET /api/guilds/:id/my-permissions` | Permission resolution | +| `features/permissions/roles-routes.ts` | CRUD roles, assignments, presets | Dashboard role management | + +**Route conventions:** +- `GET /api/guilds/:guildId/{module}` — List resources +- `POST /api/guilds/:guildId/{module}` — Create resource +- `GET /api/guilds/:guildId/{module}/:id` — Get single resource +- `PUT /api/guilds/:guildId/{module}/:id` — Update resource +- `DELETE /api/guilds/:guildId/{module}/:id` — Delete resource +- All guild routes protected by `requireAuth` + `requireGuildAdmin` +- Validation: helper function returns `string | null`, early return with `reply.code(400)` + +--- + +### 5.3 Client Pages + +The dashboard has 19 main pages under `/guild/$guildId/`: + +| Route | Page | Features | +|-------|------|----------| +| `/` | Landing | Hero, features, CTA — public, no auth | +| `/overview` | Guild Dashboard | Charts (member growth, message activity), stats cards, recent cases, system health | +| `/moderation` | Moderation | Cases table (filterable, searchable), settings panel (DM toggle, mod log channel) | +| `/warnings` | Warnings | Stats bar, warning history table, escalation config (threshold → action), settings | +| `/logs` | Logs | Per-category config (channel, event toggles), log browser with filters, stats | +| `/music` | Music | Guild settings (mode, DJ role, volume, 24/7), library management (albums, tracks) | +| `/tempvoice` | TempVoice | Hub channel, category, name format, bitrate, user limit config | +| `/welcome` | Welcome | Welcome/farewell toggle + embed builder + auto-role, live preview, test button | +| `/roles` | Role Panels | Panel list, builder (name, type, mode, embed, role entries with emoji picker) | +| `/leveling` | Leveling | Leaderboard, settings (XP rate, cooldown, voice), rewards table, exclusions | +| `/tickets` | Tickets | Active tickets, panel builder (categories, form fields, embed), settings | +| `/suggestions` | Suggestions | List with filters, status management, settings | +| `/starboard` | Starboard | Settings (channel, threshold, emoji), starred messages gallery | +| `/giveaways` | Giveaways | Active/past giveaways, create form | +| `/rules` | Automation | Workflow editor (visual node-based), rule list, analytics | +| `/commands` | Custom Commands | Command list, response editor (text/embed), permissions config | +| `/scheduled` | Scheduled Messages | Message list, create/edit form (cron picker, embed builder), test send | +| `/security` | Anti-Raid | Join rate thresholds, account age filter, anti-nuke, lockdown toggle, raid timeline | +| `/permissions` | Permissions | Enable/disable toggle, role list + permission grid, user overrides, audit log | +| `/settings` | Guild Settings | General guild configuration | + +--- + +### 5.4 Auth & Security + +**Authentication flow:** +1. User clicks "Login with Discord" → redirected to `/auth/login` +2. Server generates a random `state` value, stores it as a signed `oauth_state` cookie (`SameSite=Lax`, 5-minute expiry), and redirects to Discord's OAuth authorization URL +3. Discord redirects to `/auth/callback?code=...&state=...` +4. Server validates the `state` parameter against the cookie, then burns the cookie (prevents replay) +5. Server exchanges the `code` for an access token via Discord's token endpoint +6. Server fetches user info and guild list via Discord API +7. Server creates a session in PostgreSQL (access token encrypted with AES-256-GCM) +8. Old sessions for the same user are deleted (session fixation protection) +9. Server sets a `session` cookie (`SameSite=Lax`, 24-hour TTL) and redirects to `/` + +**Security measures:** +- **CSRF protection:** Double-submit cookie pattern — JS-readable `csrf_token` cookie must match `x-csrf-token` header on all mutating API requests +- **Rate limiting:** 10 requests/minute on auth endpoints +- **OAuth state busting:** Cookie is cleared immediately after validation, before the token exchange — prevents replay attacks even if the exchange fails +- **Encrypted tokens:** OAuth access tokens encrypted with AES-256-GCM before database storage +- **Session cleanup:** Expired sessions deleted every hour +- **CSP:** Content Security Policy via `@fastify/helmet` +- **Middleware stack:** `requireAuth` → `requireGuildAdmin` → (optional) `requirePermission` on all guild routes + +--- + +### 5.5 Dashboard Permissions + +**Status:** Not Started | **Spec:** `docs/features/dashboard-permissions.md` + +Granular RBAC system for the dashboard. `MANAGE_GUILD` permission is the entry gate; this adds fine-grained control within that gate. + +**Permission format:** `..` +Example: `moderation.cases.view`, `music.settings.manage` + +**49 permissions across 15 modules**, covering: +- View vs Manage access separation +- Module-level wildcards (`moderation.*`) +- Resource-level wildcards (`*.settings.manage`) +- Global wildcard (`*`) + +**Built-in presets:** +- Full Admin — `*` +- Moderator — moderation, warnings, logs +- Content Manager — welcome, roles, suggestions, starboard +- Music DJ — music only +- Viewer (Read-Only) — all `.view` permissions + +**Resolution order:** +1. Guild owner → `*` (full access) +2. If `requirePermissions` is off → full access (legacy mode) +3. Merge role permissions (from assigned `DashboardRole`) +4. Apply per-user overrides (`DashboardUserPermission`) +5. First wildcard match wins + +--- + +## 6. Design System + +**Name:** "The Obsidian Engine" +**File:** `design.md` + +**Aesthetic:** Dark, technical, premium — like carved obsidian. + +### Colors + +| Token | Hex | Usage | +|-------|-----|-------| +| `background` | `#0e0e10` | Absolute base | +| `primary` | `#a3a6ff` | Critical actions, brand moments | +| `primary-dim` | `#6063ee` | Gradient endpoints, secondary emphasis | +| `secondary` | `#ac8aff` | Technical accents, syntax highlighting | +| `on-primary` | `#0f00a4` | Text on primary surfaces | +| `surface-lowest` | `#000000` | Recessed areas, code blocks | +| `surface-low` | `#131315` | Main content areas | +| `surface-container` | `#19191c` | Container default | +| `surface-high` | `#1f1f22` | Elevated cards | +| `surface-hover` | `#262528` | Hover states, active tabs | +| `surface-bright` | `#2c2c2f` | Brightest surface | +| `danger` | `#ff6e84` | Errors, destructive actions | +| `success` | `#57f287` | Success states | +| `warning` | `#fee75c` | Warnings | +| `info` | `#60a5fa` | Informational | +| `text` | `#f9f5f8` | Primary text (never pure white) | +| `text-muted` | `#adaaad` | Secondary/muted text | +| `border` | `#1f1f22` | Default borders | +| `outline` | `#767577` | Visible outlines | +| `ring` | `#a3a6ff` | Focus rings | + +### Typography + +| Face | Usage | +|------|-------| +| **Inter** | Body — all UI controls, headers, descriptions | +| **Space Grotesk** | Labels — technical metadata, small tags | +| **JetBrains Mono** | Code — bot tokens, JSON, logs, metrics | + +### Elevation (Surface Shift, No Shadows) + +| Level | Token | Usage | +|-------|-------|-------| +| 0 | `background` | Base page | +| 1 | `surface-low` | Large content blocks | +| 2 | `surface-high` | Interactive cards | +| 3 | `surface-hover` | Tooltips, dropdowns, modals | + +### Rules + +1. **No-Line Rule:** No 1px borders for sectioning — use surface shifts +2. **No Pure White:** Use `#f9f5f8` for text +3. **Max 3 Depth Levels:** If you need a 4th, simplify the IA +4. **No Dividers in Lists:** Use spacing + hover backgrounds +5. **Technical Data in Mono:** Auto-switch inputs with technical content + +--- + +## 7. Infrastructure & Tooling + +### Docker Compose + +Profiles for different development scenarios: + +| Profile | Services | Usage | +|---------|----------|-------| +| `bot` | bot + postgres + lavalink | `pnpm dev:bot` | +| `dashboard` | dashboard + postgres | `pnpm dev:dashboard` | +| `full` | all services | `pnpm dev` | +| `preview` | prebuilt production images | `pnpm preview` | +| `tools` | pgAdmin | `pnpm dev --profile tools` | + +### Key Ports + +| Port | Service | +|------|---------| +| 3000 | Dashboard API (Fastify) | +| 5173 | Vite HMR (development) | +| 5432 | PostgreSQL | +| 2333 | Lavalink | +| 5050 | pgAdmin | + +### Root Scripts + +| Script | Command | Purpose | +|--------|---------|---------| +| `pnpm dev` | `docker compose --profile full up` | Full stack | +| `pnpm dev:bot` | `docker compose --profile bot up` | Bot only | +| `pnpm dev:dashboard` | `docker compose --profile dashboard up` | Dashboard only | +| `pnpm test` | `vitest run` | Unit tests | +| `pnpm test:integration` | Integration tests (real PG) | Cross-boundary tests | +| `pnpm db:migrate` | `prisma migrate dev` | Create/apply migrations | +| `pnpm db:generate` | `prisma generate` | Regenerate Prisma client | +| `pnpm db:studio` | `prisma studio` | Database GUI | +| `pnpm deploy:commands` | Register slash commands | Discord API | +| `pnpm typecheck` | TypeScript checks | All packages | + +### Testing + +**Categories:** + +| Category | Location | Database | When to use | +|----------|----------|----------|-------------| +| Unit | `apps/*/tests/`, `packages/*/tests/` | Mocked | Logic in isolation | +| Integration | `packages/systems/tests/integration/` | Real PostgreSQL | Cross-boundary flows | +| E2E | `apps/dashboard/tests/e2e/` | Real PG + Discord mock | Full user flows | + +**Test infrastructure:** +- Vitest for unit/integration tests +- Playwright for E2E tests +- Docker-based test PostgreSQL (tmpfs for speed) +- Shared factories at `packages/systems/tests/helpers/factories.ts` +- Discord.js object mocks (`createMockInteraction()`, `createMockMember()`) +- Silent logger mocking (`vi.mock("@fluxcore/utils")`) + +--- + +## Module Dependency Map + +``` + ┌──────────────┐ + │ Anti-Raid │ + └──────┬───────┘ + │ +┌──────────┐ ┌────────▼────────┐ ┌──────────────┐ +│ Welcome │ │ Logging │ │ Tickets │ +│ Farewell │ │ │ │ │ +└────┬─────┘ └────▲───▲───▲───┘ └──────┬───────┘ + │ │ │ │ │ + │ ┌──────────┘ │ └──────────┐ │ + │ │ ┌────────┴────────┐ │ │ + │ │ │ Moderation │ │ │ + │ │ │ (cases, tempban)│ │ │ + │ │ └────▲────────▲───┘ │ │ + │ │ │ │ │ │ + │ │ ┌────┴────────┴───┐ │ │ + │ │ │ Warn System │ │ │ + │ │ │ + Escalation │ │ │ + │ │ └────────▲───────┘ │ │ + │ │ │ │ │ + │ │ ┌────────┴────────┐ │ │ + │ │ │ Actions / │ │ │ + │ │ │ Automation │ │ │ + │ │ └─────────────────┘ │ │ + │ │ │ │ + │ ┌──┴────────────────────────────┴──┐ │ + │ │ Role Panels │ │ + │ └──────────────────────────────────┘ │ + │ │ +┌────▼────────────────────────────────────────▼──┐ +│ Leveling / XP │ +└───────────────────▲─────────────────────────────┘ + │ + ┌─────────┴──────────┐ + │ Music │ + └────────────────────┘ +``` + +--- + +## Status Overview + +| Module | Phase | Priority | Status | +|--------|-------|----------|--------| +| Actions / Automation | 1 | P0 | Done | +| Warnings & Escalation | 1 | P0 | Done | +| Moderation (Enhanced) | 1 | P0 | Done | +| Logging | 1 | P0 | Done | +| Music | 2 | P0 | Done | +| TempVoice | 2 | P0 | Done | +| Welcome & Farewell | 2 | P0 | Not Started | +| Role Panels | 2 | P0 | Not Started | +| Leveling / XP | 2 | P1 | Not Started | +| Tickets | 3 | P1 | Not Started | +| Anti-Raid | 4 | P1 | Not Started | +| Suggestions | 3 | P2 | Not Started | +| Starboard | 3 | P2 | Not Started | +| Giveaways | 3 | P2 | Not Started | +| Custom Commands | 4 | P2 | Not Started | +| Scheduled Messages | 4 | P2 | Not Started | +| Dashboard Permissions | Cross | — | Not Started | + +--- + +*This document is auto-generated from the codebase and feature specs. Last updated: 2026-07-08.* diff --git a/packages/i18n/src/locales/af/common.json b/packages/i18n/src/locales/af/common.json index b51bc6f..de02a4b 100644 --- a/packages/i18n/src/locales/af/common.json +++ b/packages/i18n/src/locales/af/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/af/guilds.json b/packages/i18n/src/locales/af/guilds.json index ac2627e..5a6e8b4 100644 --- a/packages/i18n/src/locales/af/guilds.json +++ b/packages/i18n/src/locales/af/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Geen bedieners gevind nie", "description": "Maak seker die bot is bygevoeg by 'n bediener waar jy die Bestuur Bediener-toestemming het." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/af/welcome.json b/packages/i18n/src/locales/af/welcome.json index ba44fab..c5a68d1 100644 --- a/packages/i18n/src/locales/af/welcome.json +++ b/packages/i18n/src/locales/af/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/ar/common.json b/packages/i18n/src/locales/ar/common.json index 917d695..a8c3a29 100644 --- a/packages/i18n/src/locales/ar/common.json +++ b/packages/i18n/src/locales/ar/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "ابحث عن اللغات...", "noResults": "لم يتم العثور على أي لغة" + }, + "variableField": { + "insert": "إدراج متغيّر", + "search": "البحث عن المتغيّرات…", + "noMatches": "لا توجد نتائج", + "unknown": "متغيّر غير معروف {{token}}", + "didYouMean": "هل تقصد {{suggestion}}؟", + "preview": "معاينة", + "notAvailable": "{{token}} غير متاح لـ {{event}}" + }, + "variables": { + "user": "يُشير إلى المستخدم", + "userTag": "وسم المستخدم (مثال: name#0001)", + "userName": "اسم المستخدم", + "userId": "معرّف المستخدم", + "userAvatar": "رابط الصورة الرمزية للمستخدم", + "server": "اسم الخادم", + "serverId": "معرّف الخادم", + "memberCount": "عدد أعضاء الخادم", + "serverIcon": "رابط أيقونة الخادم", + "level": "المستوى الذي وصل إليه العضو", + "entries": { + "user": "يُشير إلى المستخدم الذي شغّل الأمر", + "username": "الاسم المعروض للمستخدم", + "userId": "معرّف المستخدم", + "server": "اسم الخادم", + "channel": "الإشارة إلى القناة", + "channelName": "اسم القناة", + "memberCount": "عدد أعضاء الخادم" + } } } diff --git a/packages/i18n/src/locales/ar/guilds.json b/packages/i18n/src/locales/ar/guilds.json index 2e678fc..00b22fb 100644 --- a/packages/i18n/src/locales/ar/guilds.json +++ b/packages/i18n/src/locales/ar/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "لم يتم العثور على سيرفرات", "description": "تأكد من إضافة البوت إلى سيرفر لديك فيه صلاحية إدارة السيرفر." - } + }, + "refresh": "تحديث الخوادم" } diff --git a/packages/i18n/src/locales/ar/welcome.json b/packages/i18n/src/locales/ar/welcome.json index c7d31c9..b60484b 100644 --- a/packages/i18n/src/locales/ar/welcome.json +++ b/packages/i18n/src/locales/ar/welcome.json @@ -138,6 +138,11 @@ "uploaded": "تم رفع الخلفية", "uploadFailed": "فشل رفع الخلفية", "previewFailed": "فشل إنشاء المعاينة" - } + }, + "previewFailedHint": "فشل عرض المعاينة. حاول التحديث أو تبديل الوضع.", + "clientMode": "فورية", + "serverMode": "الخادم", + "switchToServer": "التبديل إلى معاينة الخادم", + "switchToClient": "التبديل إلى المعاينة الفورية" } } diff --git a/packages/i18n/src/locales/bg/common.json b/packages/i18n/src/locales/bg/common.json index f826ac7..218e71c 100644 --- a/packages/i18n/src/locales/bg/common.json +++ b/packages/i18n/src/locales/bg/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/bg/guilds.json b/packages/i18n/src/locales/bg/guilds.json index eb420a1..a42a814 100644 --- a/packages/i18n/src/locales/bg/guilds.json +++ b/packages/i18n/src/locales/bg/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Не са намерени сървъри", "description": "Уверете се, че ботът е добавен към сървър, в който имате разрешение за управление на сървъра." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/bg/welcome.json b/packages/i18n/src/locales/bg/welcome.json index eb0057f..aa2506f 100644 --- a/packages/i18n/src/locales/bg/welcome.json +++ b/packages/i18n/src/locales/bg/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/bn/common.json b/packages/i18n/src/locales/bn/common.json index 3ebf523..4a151e6 100644 --- a/packages/i18n/src/locales/bn/common.json +++ b/packages/i18n/src/locales/bn/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/bn/guilds.json b/packages/i18n/src/locales/bn/guilds.json index 4d27cd3..0c656b0 100644 --- a/packages/i18n/src/locales/bn/guilds.json +++ b/packages/i18n/src/locales/bn/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "কোনো সার্ভার পাওয়া যায়নি", "description": "নিশ্চিত করুন যে বটটি এমন একটি সার্ভারে যোগ করা হয়েছে যেখানে আপনার সার্ভার পরিচালনার অনুমতি আছে।" - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/bn/welcome.json b/packages/i18n/src/locales/bn/welcome.json index 1649f75..40c8217 100644 --- a/packages/i18n/src/locales/bn/welcome.json +++ b/packages/i18n/src/locales/bn/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/ca/common.json b/packages/i18n/src/locales/ca/common.json index a487d62..e8c0822 100644 --- a/packages/i18n/src/locales/ca/common.json +++ b/packages/i18n/src/locales/ca/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/ca/guilds.json b/packages/i18n/src/locales/ca/guilds.json index 605ca21..62cc2aa 100644 --- a/packages/i18n/src/locales/ca/guilds.json +++ b/packages/i18n/src/locales/ca/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "No s'han trobat servidors", "description": "Assegura't que el bot s'ha afegit a un servidor on tinguis el permis de Gestionar Servidor." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/ca/welcome.json b/packages/i18n/src/locales/ca/welcome.json index 0a1685e..86ae703 100644 --- a/packages/i18n/src/locales/ca/welcome.json +++ b/packages/i18n/src/locales/ca/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/cs/common.json b/packages/i18n/src/locales/cs/common.json index 30dff97..40bebc9 100644 --- a/packages/i18n/src/locales/cs/common.json +++ b/packages/i18n/src/locales/cs/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/cs/guilds.json b/packages/i18n/src/locales/cs/guilds.json index 44402b9..4edb902 100644 --- a/packages/i18n/src/locales/cs/guilds.json +++ b/packages/i18n/src/locales/cs/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Žádné servery nenalezeny", "description": "Ujistěte se, že bot je přidán na server, kde máte oprávnění Správa serveru." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/cs/welcome.json b/packages/i18n/src/locales/cs/welcome.json index 3ed55ae..a9e2ff5 100644 --- a/packages/i18n/src/locales/cs/welcome.json +++ b/packages/i18n/src/locales/cs/welcome.json @@ -128,7 +128,12 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" }, "saveChanges": "Save Changes", "saving": "Saving...", diff --git a/packages/i18n/src/locales/da/common.json b/packages/i18n/src/locales/da/common.json index 19cf685..74fe44d 100644 --- a/packages/i18n/src/locales/da/common.json +++ b/packages/i18n/src/locales/da/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/da/guilds.json b/packages/i18n/src/locales/da/guilds.json index 244303a..d64985b 100644 --- a/packages/i18n/src/locales/da/guilds.json +++ b/packages/i18n/src/locales/da/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Ingen servere fundet", "description": "Sørg for, at botten er tilføjet på en server, hvor du har tilladelsen Administrer server." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/da/welcome.json b/packages/i18n/src/locales/da/welcome.json index d3aa51d..c1e245e 100644 --- a/packages/i18n/src/locales/da/welcome.json +++ b/packages/i18n/src/locales/da/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/de/common.json b/packages/i18n/src/locales/de/common.json index 0be52ef..ee8007c 100644 --- a/packages/i18n/src/locales/de/common.json +++ b/packages/i18n/src/locales/de/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Sprachen suchen...", "noResults": "Keine Sprache gefunden" + }, + "variableField": { + "insert": "Variable einfügen", + "search": "Variablen suchen…", + "noMatches": "Keine Treffer", + "unknown": "Unbekannte Variable {{token}}", + "didYouMean": "meintest du {{suggestion}}?", + "preview": "Vorschau", + "notAvailable": "{{token}} ist für {{event}} nicht verfügbar" + }, + "variables": { + "user": "Erwähnt den Benutzer", + "userTag": "Das Tag des Benutzers (z. B. name#0001)", + "userName": "Der Benutzername", + "userId": "Die Benutzer-ID", + "userAvatar": "Die Avatar-URL des Benutzers", + "server": "Der Servername", + "serverId": "Die Server-ID", + "memberCount": "Die Mitgliederzahl des Servers", + "serverIcon": "Die URL des Server-Symbols", + "level": "Das vom Mitglied erreichte Level", + "entries": { + "user": "Erwähnt den Benutzer, der den Befehl ausgelöst hat", + "username": "Anzeigename des Benutzers", + "userId": "Benutzer-ID", + "server": "Servername", + "channel": "Kanal-Erwähnung", + "channelName": "Kanalname", + "memberCount": "Mitgliederzahl des Servers" + } } } diff --git a/packages/i18n/src/locales/de/guilds.json b/packages/i18n/src/locales/de/guilds.json index e49b6a9..b8b9283 100644 --- a/packages/i18n/src/locales/de/guilds.json +++ b/packages/i18n/src/locales/de/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Keine Server gefunden", "description": "Stellen Sie sicher, dass der Bot einem Server hinzugefuegt wurde, auf dem Sie die Berechtigung Server verwalten haben." - } + }, + "refresh": "Server aktualisieren" } diff --git a/packages/i18n/src/locales/de/welcome.json b/packages/i18n/src/locales/de/welcome.json index 2d375f6..400bcd4 100644 --- a/packages/i18n/src/locales/de/welcome.json +++ b/packages/i18n/src/locales/de/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Hintergrund hochgeladen", "uploadFailed": "Hintergrund konnte nicht hochgeladen werden", "previewFailed": "Vorschau konnte nicht erstellt werden" - } + }, + "previewFailedHint": "Vorschau konnte nicht gerendert werden. Aktualisiere oder wechsle den Modus.", + "clientMode": "Sofort", + "serverMode": "Server", + "switchToServer": "Zur Server-Vorschau wechseln", + "switchToClient": "Zur Sofort-Vorschau wechseln" } } diff --git a/packages/i18n/src/locales/el/common.json b/packages/i18n/src/locales/el/common.json index 5673268..4d2949b 100644 --- a/packages/i18n/src/locales/el/common.json +++ b/packages/i18n/src/locales/el/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/el/guilds.json b/packages/i18n/src/locales/el/guilds.json index c4c31af..e47a717 100644 --- a/packages/i18n/src/locales/el/guilds.json +++ b/packages/i18n/src/locales/el/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Δεν βρέθηκαν servers", "description": "Βεβαιωθείτε ότι το bot έχει προστεθεί σε έναν server όπου έχετε δικαίωμα διαχείρισης server." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/el/welcome.json b/packages/i18n/src/locales/el/welcome.json index 0aeec93..e5548e8 100644 --- a/packages/i18n/src/locales/el/welcome.json +++ b/packages/i18n/src/locales/el/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/en/welcome.json b/packages/i18n/src/locales/en/welcome.json index 3ed55ae..7a0d941 100644 --- a/packages/i18n/src/locales/en/welcome.json +++ b/packages/i18n/src/locales/en/welcome.json @@ -64,6 +64,11 @@ "previewAlt": "{{type}} image preview", "generatingPreview": "Generating preview...", "previewPlaceholder": "Preview will appear here", + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview", "template": "Template", "background": { "title": "Background", diff --git a/packages/i18n/src/locales/es/common.json b/packages/i18n/src/locales/es/common.json index 3c55afe..59c1024 100644 --- a/packages/i18n/src/locales/es/common.json +++ b/packages/i18n/src/locales/es/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Buscar idiomas...", "noResults": "No se encontró ningún idioma" + }, + "variableField": { + "insert": "Insertar variable", + "search": "Buscar variables…", + "noMatches": "Sin coincidencias", + "unknown": "Variable desconocida {{token}}", + "didYouMean": "¿quisiste decir {{suggestion}}?", + "preview": "Vista previa", + "notAvailable": "{{token}} no está disponible para {{event}}" + }, + "variables": { + "user": "Menciona al usuario", + "userTag": "La etiqueta del usuario (p. ej. name#0001)", + "userName": "El nombre de usuario", + "userId": "El ID del usuario", + "userAvatar": "La URL del avatar del usuario", + "server": "El nombre del servidor", + "serverId": "El ID del servidor", + "memberCount": "El número de miembros del servidor", + "serverIcon": "La URL del icono del servidor", + "level": "El nivel que alcanzó el miembro", + "entries": { + "user": "Menciona al usuario que ejecutó el comando", + "username": "Nombre visible del usuario", + "userId": "ID del usuario", + "server": "Nombre del servidor", + "channel": "Mención del canal", + "channelName": "Nombre del canal", + "memberCount": "Número de miembros del servidor" + } } } diff --git a/packages/i18n/src/locales/es/guilds.json b/packages/i18n/src/locales/es/guilds.json index ea16b9c..5b37a88 100644 --- a/packages/i18n/src/locales/es/guilds.json +++ b/packages/i18n/src/locales/es/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "No se encontraron servidores", "description": "Asegurate de que el bot este agregado a un servidor donde tengas el permiso Gestionar servidor." - } + }, + "refresh": "Actualizar servidores" } diff --git a/packages/i18n/src/locales/es/welcome.json b/packages/i18n/src/locales/es/welcome.json index 2f72db5..c9a13e8 100644 --- a/packages/i18n/src/locales/es/welcome.json +++ b/packages/i18n/src/locales/es/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Fondo subido", "uploadFailed": "No se pudo subir el fondo", "previewFailed": "No se pudo generar la vista previa" - } + }, + "previewFailedHint": "No se pudo generar la vista previa. Prueba a actualizar o cambiar de modo.", + "clientMode": "Instantánea", + "serverMode": "Servidor", + "switchToServer": "Cambiar a vista previa del servidor", + "switchToClient": "Cambiar a vista previa instantánea" } } diff --git a/packages/i18n/src/locales/et/common.json b/packages/i18n/src/locales/et/common.json index c9f51d2..9af08da 100644 --- a/packages/i18n/src/locales/et/common.json +++ b/packages/i18n/src/locales/et/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/et/guilds.json b/packages/i18n/src/locales/et/guilds.json index a8fb47d..f6d6ce6 100644 --- a/packages/i18n/src/locales/et/guilds.json +++ b/packages/i18n/src/locales/et/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Servereid ei leitud", "description": "Veendu, et bot on lisatud serverile, kus sul on serveri haldamise õigus." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/et/welcome.json b/packages/i18n/src/locales/et/welcome.json index 1bfdc16..074f7e5 100644 --- a/packages/i18n/src/locales/et/welcome.json +++ b/packages/i18n/src/locales/et/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/eu/common.json b/packages/i18n/src/locales/eu/common.json index 1135a26..cbb7f1c 100644 --- a/packages/i18n/src/locales/eu/common.json +++ b/packages/i18n/src/locales/eu/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/eu/guilds.json b/packages/i18n/src/locales/eu/guilds.json index 50efb5c..79ea672 100644 --- a/packages/i18n/src/locales/eu/guilds.json +++ b/packages/i18n/src/locales/eu/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Ez da zerbitzaririk aurkitu", "description": "Ziurtatu bot-a gehituta dagoela Zerbitzaria Kudeatu baimena duzun zerbitzari batean." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/eu/welcome.json b/packages/i18n/src/locales/eu/welcome.json index 0ba4bd5..029fbfa 100644 --- a/packages/i18n/src/locales/eu/welcome.json +++ b/packages/i18n/src/locales/eu/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/fa/common.json b/packages/i18n/src/locales/fa/common.json index ba21208..2758d72 100644 --- a/packages/i18n/src/locales/fa/common.json +++ b/packages/i18n/src/locales/fa/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/fa/guilds.json b/packages/i18n/src/locales/fa/guilds.json index 08e92cb..99bc476 100644 --- a/packages/i18n/src/locales/fa/guilds.json +++ b/packages/i18n/src/locales/fa/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "سروری یافت نشد", "description": "مطمئن شوید ربات به سروری اضافه شده که در آن دسترسی مدیریت سرور را دارید." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/fa/welcome.json b/packages/i18n/src/locales/fa/welcome.json index d64857e..19a8365 100644 --- a/packages/i18n/src/locales/fa/welcome.json +++ b/packages/i18n/src/locales/fa/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/fi/common.json b/packages/i18n/src/locales/fi/common.json index 36c028e..bc144fe 100644 --- a/packages/i18n/src/locales/fi/common.json +++ b/packages/i18n/src/locales/fi/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/fi/guilds.json b/packages/i18n/src/locales/fi/guilds.json index 92dfa75..a073d7b 100644 --- a/packages/i18n/src/locales/fi/guilds.json +++ b/packages/i18n/src/locales/fi/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Palvelimia ei löytynyt", "description": "Varmista, että botti on lisätty palvelimelle, jossa sinulla on Hallitse palvelinta -oikeus." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/fi/welcome.json b/packages/i18n/src/locales/fi/welcome.json index b1b2e20..409a892 100644 --- a/packages/i18n/src/locales/fi/welcome.json +++ b/packages/i18n/src/locales/fi/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/fil/common.json b/packages/i18n/src/locales/fil/common.json index 8f19bd1..6e8b555 100644 --- a/packages/i18n/src/locales/fil/common.json +++ b/packages/i18n/src/locales/fil/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/fil/guilds.json b/packages/i18n/src/locales/fil/guilds.json index 80cd0df..a6046a4 100644 --- a/packages/i18n/src/locales/fil/guilds.json +++ b/packages/i18n/src/locales/fil/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Walang nahanap na mga server", "description": "Siguraduhing naidagdag na ang bot sa isang server kung saan mayroon kang pahintulot na Pamahalaan ang Server." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/fil/welcome.json b/packages/i18n/src/locales/fil/welcome.json index dc5fcc4..3b01ed4 100644 --- a/packages/i18n/src/locales/fil/welcome.json +++ b/packages/i18n/src/locales/fil/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/fr/common.json b/packages/i18n/src/locales/fr/common.json index 199c12b..2084e56 100644 --- a/packages/i18n/src/locales/fr/common.json +++ b/packages/i18n/src/locales/fr/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Rechercher des langues...", "noResults": "Aucune langue trouvée" + }, + "variableField": { + "insert": "Insérer une variable", + "search": "Rechercher des variables…", + "noMatches": "Aucun résultat", + "unknown": "Variable inconnue {{token}}", + "didYouMean": "vouliez-vous dire {{suggestion}} ?", + "preview": "Aperçu", + "notAvailable": "{{token}} n'est pas disponible pour {{event}}" + }, + "variables": { + "user": "Mentionne l'utilisateur", + "userTag": "Le tag de l'utilisateur (p. ex. name#0001)", + "userName": "Le nom d'utilisateur", + "userId": "L'identifiant de l'utilisateur", + "userAvatar": "L'URL de l'avatar de l'utilisateur", + "server": "Le nom du serveur", + "serverId": "L'identifiant du serveur", + "memberCount": "Le nombre de membres du serveur", + "serverIcon": "L'URL de l'icône du serveur", + "level": "Le niveau atteint par le membre", + "entries": { + "user": "Mentionne l'utilisateur qui a exécuté la commande", + "username": "Nom d'affichage de l'utilisateur", + "userId": "Identifiant de l'utilisateur", + "server": "Nom du serveur", + "channel": "Mention du salon", + "channelName": "Nom du salon", + "memberCount": "Nombre de membres du serveur" + } } } diff --git a/packages/i18n/src/locales/fr/guilds.json b/packages/i18n/src/locales/fr/guilds.json index 8d5e53b..684e09b 100644 --- a/packages/i18n/src/locales/fr/guilds.json +++ b/packages/i18n/src/locales/fr/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Aucun serveur trouve", "description": "Assurez-vous que le bot est ajoute a un serveur ou vous avez la permission Gerer le serveur." - } + }, + "refresh": "Actualiser les serveurs" } diff --git a/packages/i18n/src/locales/fr/welcome.json b/packages/i18n/src/locales/fr/welcome.json index 1598b3e..b5a332e 100644 --- a/packages/i18n/src/locales/fr/welcome.json +++ b/packages/i18n/src/locales/fr/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Arrière-plan téléversé", "uploadFailed": "Échec du téléversement de l'arrière-plan", "previewFailed": "Échec de la génération de l'aperçu" - } + }, + "previewFailedHint": "Échec du rendu de l'aperçu. Essayez d'actualiser ou de changer de mode.", + "clientMode": "Instantané", + "serverMode": "Serveur", + "switchToServer": "Passer à l'aperçu serveur", + "switchToClient": "Passer à l'aperçu instantané" } } diff --git a/packages/i18n/src/locales/gl/common.json b/packages/i18n/src/locales/gl/common.json index f89e31e..b67de80 100644 --- a/packages/i18n/src/locales/gl/common.json +++ b/packages/i18n/src/locales/gl/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/gl/guilds.json b/packages/i18n/src/locales/gl/guilds.json index 5991bef..967f4ca 100644 --- a/packages/i18n/src/locales/gl/guilds.json +++ b/packages/i18n/src/locales/gl/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Non se atoparon servidores", "description": "Asegúrate de que o bot está engadido a un servidor onde tes o permiso de Xestionar Servidor." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/gl/welcome.json b/packages/i18n/src/locales/gl/welcome.json index cd0abd9..d90cc10 100644 --- a/packages/i18n/src/locales/gl/welcome.json +++ b/packages/i18n/src/locales/gl/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/he/common.json b/packages/i18n/src/locales/he/common.json index 0efc059..8cc430d 100644 --- a/packages/i18n/src/locales/he/common.json +++ b/packages/i18n/src/locales/he/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/he/guilds.json b/packages/i18n/src/locales/he/guilds.json index 90d4c9e..3d5b4f4 100644 --- a/packages/i18n/src/locales/he/guilds.json +++ b/packages/i18n/src/locales/he/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "לא נמצאו שרתים", "description": "ודא שהבוט הוסף לשרת שבו יש לך הרשאת ניהול שרת." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/he/welcome.json b/packages/i18n/src/locales/he/welcome.json index aa5ad25..626bb08 100644 --- a/packages/i18n/src/locales/he/welcome.json +++ b/packages/i18n/src/locales/he/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/hi/common.json b/packages/i18n/src/locales/hi/common.json index 40449af..e8152aa 100644 --- a/packages/i18n/src/locales/hi/common.json +++ b/packages/i18n/src/locales/hi/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/hi/guilds.json b/packages/i18n/src/locales/hi/guilds.json index 009da41..dbe7acd 100644 --- a/packages/i18n/src/locales/hi/guilds.json +++ b/packages/i18n/src/locales/hi/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "कोई सर्वर नहीं मिला", "description": "सुनिश्चित करें कि बॉट उस सर्वर में जोड़ा गया है जहां आपके पास सर्वर प्रबंधन की अनुमति है।" - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/hi/welcome.json b/packages/i18n/src/locales/hi/welcome.json index ea5e4d2..aba8d79 100644 --- a/packages/i18n/src/locales/hi/welcome.json +++ b/packages/i18n/src/locales/hi/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/hr/common.json b/packages/i18n/src/locales/hr/common.json index b6edec0..245712a 100644 --- a/packages/i18n/src/locales/hr/common.json +++ b/packages/i18n/src/locales/hr/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/hr/guilds.json b/packages/i18n/src/locales/hr/guilds.json index 6a2a338..2c2a286 100644 --- a/packages/i18n/src/locales/hr/guilds.json +++ b/packages/i18n/src/locales/hr/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Nisu pronadeni posluzitelji", "description": "Provjerite je li bot dodan na posluzitelj na kojem imate dozvolu Upravljanje posluziteljem." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/hr/welcome.json b/packages/i18n/src/locales/hr/welcome.json index 35d49a4..96ce5f9 100644 --- a/packages/i18n/src/locales/hr/welcome.json +++ b/packages/i18n/src/locales/hr/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/hu/common.json b/packages/i18n/src/locales/hu/common.json index 31a8033..abb7536 100644 --- a/packages/i18n/src/locales/hu/common.json +++ b/packages/i18n/src/locales/hu/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/hu/guilds.json b/packages/i18n/src/locales/hu/guilds.json index 32dd7c0..bd115b3 100644 --- a/packages/i18n/src/locales/hu/guilds.json +++ b/packages/i18n/src/locales/hu/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "No servers found", "description": "Make sure the bot is added to a server where you have the Manage Server permission." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/hu/welcome.json b/packages/i18n/src/locales/hu/welcome.json index 3ed55ae..a9e2ff5 100644 --- a/packages/i18n/src/locales/hu/welcome.json +++ b/packages/i18n/src/locales/hu/welcome.json @@ -128,7 +128,12 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" }, "saveChanges": "Save Changes", "saving": "Saving...", diff --git a/packages/i18n/src/locales/id/common.json b/packages/i18n/src/locales/id/common.json index c8e0fde..1c40945 100644 --- a/packages/i18n/src/locales/id/common.json +++ b/packages/i18n/src/locales/id/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/id/guilds.json b/packages/i18n/src/locales/id/guilds.json index 52fc876..048414e 100644 --- a/packages/i18n/src/locales/id/guilds.json +++ b/packages/i18n/src/locales/id/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Tidak ada server ditemukan", "description": "Pastikan bot telah ditambahkan ke server di mana Anda memiliki izin Kelola Server." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/id/welcome.json b/packages/i18n/src/locales/id/welcome.json index 25f633a..c38e465 100644 --- a/packages/i18n/src/locales/id/welcome.json +++ b/packages/i18n/src/locales/id/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/it/common.json b/packages/i18n/src/locales/it/common.json index 0dea50e..dcc5531 100644 --- a/packages/i18n/src/locales/it/common.json +++ b/packages/i18n/src/locales/it/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/it/guilds.json b/packages/i18n/src/locales/it/guilds.json index 14717fc..ec80612 100644 --- a/packages/i18n/src/locales/it/guilds.json +++ b/packages/i18n/src/locales/it/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Nessun server trovato", "description": "Assicurati che il bot sia aggiunto a un server dove hai il permesso Gestisci server." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/it/welcome.json b/packages/i18n/src/locales/it/welcome.json index e260260..40361fd 100644 --- a/packages/i18n/src/locales/it/welcome.json +++ b/packages/i18n/src/locales/it/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/ja/common.json b/packages/i18n/src/locales/ja/common.json index 8871c9a..9f446e9 100644 --- a/packages/i18n/src/locales/ja/common.json +++ b/packages/i18n/src/locales/ja/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "言語を検索...", "noResults": "言語が見つかりません" + }, + "variableField": { + "insert": "変数を挿入", + "search": "変数を検索…", + "noMatches": "一致なし", + "unknown": "不明な変数 {{token}}", + "didYouMean": "もしかして {{suggestion}} ですか?", + "preview": "プレビュー", + "notAvailable": "{{token}} は {{event}} では使用できません" + }, + "variables": { + "user": "ユーザーをメンションします", + "userTag": "ユーザーのタグ(例: name#0001)", + "userName": "ユーザー名", + "userId": "ユーザーID", + "userAvatar": "ユーザーのアバターURL", + "server": "サーバー名", + "serverId": "サーバーID", + "memberCount": "サーバーのメンバー数", + "serverIcon": "サーバーのアイコンURL", + "level": "メンバーが到達したレベル", + "entries": { + "user": "コマンドを実行したユーザーをメンションします", + "username": "ユーザーの表示名", + "userId": "ユーザーID", + "server": "サーバー名", + "channel": "チャンネルのメンション", + "channelName": "チャンネル名", + "memberCount": "サーバーのメンバー数" + } } } diff --git a/packages/i18n/src/locales/ja/guilds.json b/packages/i18n/src/locales/ja/guilds.json index 3a14e3c..dd65cc9 100644 --- a/packages/i18n/src/locales/ja/guilds.json +++ b/packages/i18n/src/locales/ja/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "サーバーが見つかりません", "description": "Botが追加されており、サーバー管理権限を持つサーバーがあることを確認してください。" - } + }, + "refresh": "サーバーを更新" } diff --git a/packages/i18n/src/locales/ja/welcome.json b/packages/i18n/src/locales/ja/welcome.json index c89c6d8..d8a278c 100644 --- a/packages/i18n/src/locales/ja/welcome.json +++ b/packages/i18n/src/locales/ja/welcome.json @@ -138,6 +138,11 @@ "uploaded": "背景をアップロードしました", "uploadFailed": "背景のアップロードに失敗しました", "previewFailed": "プレビューの生成に失敗しました" - } + }, + "previewFailedHint": "プレビューの生成に失敗しました。更新するかモードを切り替えてください。", + "clientMode": "即時", + "serverMode": "サーバー", + "switchToServer": "サーバープレビューに切り替え", + "switchToClient": "即時プレビューに切り替え" } } diff --git a/packages/i18n/src/locales/ko/common.json b/packages/i18n/src/locales/ko/common.json index 5f77183..ba25b11 100644 --- a/packages/i18n/src/locales/ko/common.json +++ b/packages/i18n/src/locales/ko/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/ko/guilds.json b/packages/i18n/src/locales/ko/guilds.json index aaa649e..e14c3fa 100644 --- a/packages/i18n/src/locales/ko/guilds.json +++ b/packages/i18n/src/locales/ko/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "서버를 찾을 수 없습니다", "description": "봇이 추가되어 있고 서버 관리 권한이 있는 서버가 있는지 확인하세요." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/ko/welcome.json b/packages/i18n/src/locales/ko/welcome.json index 9ca1a1a..11e2f0e 100644 --- a/packages/i18n/src/locales/ko/welcome.json +++ b/packages/i18n/src/locales/ko/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/lt/common.json b/packages/i18n/src/locales/lt/common.json index 844cf84..87e8f66 100644 --- a/packages/i18n/src/locales/lt/common.json +++ b/packages/i18n/src/locales/lt/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/lt/guilds.json b/packages/i18n/src/locales/lt/guilds.json index 8b2e147..a1d43c7 100644 --- a/packages/i18n/src/locales/lt/guilds.json +++ b/packages/i18n/src/locales/lt/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Serveriu nerasta", "description": "Isitikinkite, kad botas pridetas prie serverio, kuriame turite Serverio valdymo leidima." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/lt/welcome.json b/packages/i18n/src/locales/lt/welcome.json index be4b81e..3ea114e 100644 --- a/packages/i18n/src/locales/lt/welcome.json +++ b/packages/i18n/src/locales/lt/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/lv/common.json b/packages/i18n/src/locales/lv/common.json index 9f9acd6..2a102f2 100644 --- a/packages/i18n/src/locales/lv/common.json +++ b/packages/i18n/src/locales/lv/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/lv/guilds.json b/packages/i18n/src/locales/lv/guilds.json index 69e6ced..2a9afa8 100644 --- a/packages/i18n/src/locales/lv/guilds.json +++ b/packages/i18n/src/locales/lv/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Serveri nav atrasti", "description": "Parliecinieties, ka bots ir pievienots serverim, kura jums ir Servera parvaldibas atlauja." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/lv/welcome.json b/packages/i18n/src/locales/lv/welcome.json index e274496..a47aec7 100644 --- a/packages/i18n/src/locales/lv/welcome.json +++ b/packages/i18n/src/locales/lv/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/ms/common.json b/packages/i18n/src/locales/ms/common.json index 49d5d56..be34e21 100644 --- a/packages/i18n/src/locales/ms/common.json +++ b/packages/i18n/src/locales/ms/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/ms/guilds.json b/packages/i18n/src/locales/ms/guilds.json index 679be49..dee094f 100644 --- a/packages/i18n/src/locales/ms/guilds.json +++ b/packages/i18n/src/locales/ms/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Tiada pelayan ditemui", "description": "Pastikan bot telah ditambahkan ke pelayan di mana anda mempunyai kebenaran Urus Pelayan." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/ms/welcome.json b/packages/i18n/src/locales/ms/welcome.json index 4971546..eb50eb0 100644 --- a/packages/i18n/src/locales/ms/welcome.json +++ b/packages/i18n/src/locales/ms/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/nl/common.json b/packages/i18n/src/locales/nl/common.json index e7237bc..16bf430 100644 --- a/packages/i18n/src/locales/nl/common.json +++ b/packages/i18n/src/locales/nl/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/nl/guilds.json b/packages/i18n/src/locales/nl/guilds.json index 8937a00..f1237ce 100644 --- a/packages/i18n/src/locales/nl/guilds.json +++ b/packages/i18n/src/locales/nl/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Geen servers gevonden", "description": "Zorg ervoor dat de bot is toegevoegd aan een server waar je de toestemming Server beheren hebt." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/nl/welcome.json b/packages/i18n/src/locales/nl/welcome.json index 385417d..467b7fa 100644 --- a/packages/i18n/src/locales/nl/welcome.json +++ b/packages/i18n/src/locales/nl/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/no/common.json b/packages/i18n/src/locales/no/common.json index 29c9dac..e169ffc 100644 --- a/packages/i18n/src/locales/no/common.json +++ b/packages/i18n/src/locales/no/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/no/guilds.json b/packages/i18n/src/locales/no/guilds.json index 62cd64f..9e811b1 100644 --- a/packages/i18n/src/locales/no/guilds.json +++ b/packages/i18n/src/locales/no/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Ingen servere funnet", "description": "Sørg for at boten er lagt til på en server der du har tillatelsen Administrer server." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/no/welcome.json b/packages/i18n/src/locales/no/welcome.json index bfa5df2..5486230 100644 --- a/packages/i18n/src/locales/no/welcome.json +++ b/packages/i18n/src/locales/no/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/pl/common.json b/packages/i18n/src/locales/pl/common.json index 37c5adc..521a3df 100644 --- a/packages/i18n/src/locales/pl/common.json +++ b/packages/i18n/src/locales/pl/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/pl/guilds.json b/packages/i18n/src/locales/pl/guilds.json index e63ac1f..f3a3ba6 100644 --- a/packages/i18n/src/locales/pl/guilds.json +++ b/packages/i18n/src/locales/pl/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Nie znaleziono serwerów", "description": "Upewnij się, że bot jest dodany do serwera, na którym masz uprawnienie Zarządzanie serwerem." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/pl/welcome.json b/packages/i18n/src/locales/pl/welcome.json index acdc6d8..a5ad85e 100644 --- a/packages/i18n/src/locales/pl/welcome.json +++ b/packages/i18n/src/locales/pl/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/pt/common.json b/packages/i18n/src/locales/pt/common.json index f0a4659..a8a9861 100644 --- a/packages/i18n/src/locales/pt/common.json +++ b/packages/i18n/src/locales/pt/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Buscar idiomas...", "noResults": "Nenhum idioma encontrado" + }, + "variableField": { + "insert": "Inserir variável", + "search": "Pesquisar variáveis…", + "noMatches": "Nenhuma correspondência", + "unknown": "Variável desconhecida {{token}}", + "didYouMean": "você quis dizer {{suggestion}}?", + "preview": "Pré-visualização", + "notAvailable": "{{token}} não está disponível para {{event}}" + }, + "variables": { + "user": "Menciona o usuário", + "userTag": "A tag do usuário (ex.: name#0001)", + "userName": "O nome de usuário", + "userId": "O ID do usuário", + "userAvatar": "A URL do avatar do usuário", + "server": "O nome do servidor", + "serverId": "O ID do servidor", + "memberCount": "A contagem de membros do servidor", + "serverIcon": "A URL do ícone do servidor", + "level": "O nível que o membro alcançou", + "entries": { + "user": "Menciona o usuário que acionou o comando", + "username": "Nome de exibição do usuário", + "userId": "ID do usuário", + "server": "Nome do servidor", + "channel": "Menção do canal", + "channelName": "Nome do canal", + "memberCount": "Contagem de membros do servidor" + } } } diff --git a/packages/i18n/src/locales/pt/guilds.json b/packages/i18n/src/locales/pt/guilds.json index f65327a..735ca8d 100644 --- a/packages/i18n/src/locales/pt/guilds.json +++ b/packages/i18n/src/locales/pt/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Nenhum servidor encontrado", "description": "Certifique-se de que o bot esta adicionado a um servidor onde tem a permissao Gerir servidor." - } + }, + "refresh": "Atualizar servidores" } diff --git a/packages/i18n/src/locales/pt/welcome.json b/packages/i18n/src/locales/pt/welcome.json index 9a488e8..69a1ce8 100644 --- a/packages/i18n/src/locales/pt/welcome.json +++ b/packages/i18n/src/locales/pt/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Plano de fundo enviado", "uploadFailed": "Falha ao enviar o plano de fundo", "previewFailed": "Falha ao gerar a prévia" - } + }, + "previewFailedHint": "Falha ao gerar a pré-visualização. Tente atualizar ou trocar de modo.", + "clientMode": "Instantânea", + "serverMode": "Servidor", + "switchToServer": "Mudar para pré-visualização do servidor", + "switchToClient": "Mudar para pré-visualização instantânea" } } diff --git a/packages/i18n/src/locales/ro/common.json b/packages/i18n/src/locales/ro/common.json index 7dc9725..21aeb10 100644 --- a/packages/i18n/src/locales/ro/common.json +++ b/packages/i18n/src/locales/ro/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/ro/guilds.json b/packages/i18n/src/locales/ro/guilds.json index e73abff..f52f9fe 100644 --- a/packages/i18n/src/locales/ro/guilds.json +++ b/packages/i18n/src/locales/ro/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Nu s-au găsit servere", "description": "Asigură-te că botul este adăugat pe un server unde ai permisiunea de a Gestiona Serverul." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/ro/welcome.json b/packages/i18n/src/locales/ro/welcome.json index 4a00daf..9bfe2cd 100644 --- a/packages/i18n/src/locales/ro/welcome.json +++ b/packages/i18n/src/locales/ro/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/ru/common.json b/packages/i18n/src/locales/ru/common.json index bf56730..d618e96 100644 --- a/packages/i18n/src/locales/ru/common.json +++ b/packages/i18n/src/locales/ru/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Поиск языков...", "noResults": "Язык не найден" + }, + "variableField": { + "insert": "Вставить переменную", + "search": "Поиск переменных…", + "noMatches": "Совпадений нет", + "unknown": "Неизвестная переменная {{token}}", + "didYouMean": "возможно, вы имели в виду {{suggestion}}?", + "preview": "Предпросмотр", + "notAvailable": "{{token}} недоступна для {{event}}" + }, + "variables": { + "user": "Упоминает пользователя", + "userTag": "Тег пользователя (например, name#0001)", + "userName": "Имя пользователя", + "userId": "ID пользователя", + "userAvatar": "URL аватара пользователя", + "server": "Название сервера", + "serverId": "ID сервера", + "memberCount": "Количество участников сервера", + "serverIcon": "URL значка сервера", + "level": "Уровень, которого достиг участник", + "entries": { + "user": "Упоминает пользователя, вызвавшего команду", + "username": "Отображаемое имя пользователя", + "userId": "ID пользователя", + "server": "Название сервера", + "channel": "Упоминание канала", + "channelName": "Название канала", + "memberCount": "Количество участников сервера" + } } } diff --git a/packages/i18n/src/locales/ru/guilds.json b/packages/i18n/src/locales/ru/guilds.json index 2497674..3d66d2e 100644 --- a/packages/i18n/src/locales/ru/guilds.json +++ b/packages/i18n/src/locales/ru/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Серверы не найдены", "description": "Убедитесь, что бот добавлен на сервер, где у вас есть право «Управление сервером»." - } + }, + "refresh": "Обновить серверы" } diff --git a/packages/i18n/src/locales/ru/welcome.json b/packages/i18n/src/locales/ru/welcome.json index 1e7b6f4..f6d1c1d 100644 --- a/packages/i18n/src/locales/ru/welcome.json +++ b/packages/i18n/src/locales/ru/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Фон загружен", "uploadFailed": "Не удалось загрузить фон", "previewFailed": "Не удалось создать предпросмотр" - } + }, + "previewFailedHint": "Не удалось отрисовать предпросмотр. Попробуйте обновить или сменить режим.", + "clientMode": "Мгновенный", + "serverMode": "Сервер", + "switchToServer": "Переключить на серверный предпросмотр", + "switchToClient": "Переключить на мгновенный предпросмотр" } } diff --git a/packages/i18n/src/locales/sk/common.json b/packages/i18n/src/locales/sk/common.json index 251f86e..4020bf0 100644 --- a/packages/i18n/src/locales/sk/common.json +++ b/packages/i18n/src/locales/sk/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/sk/guilds.json b/packages/i18n/src/locales/sk/guilds.json index 32dd7c0..bd115b3 100644 --- a/packages/i18n/src/locales/sk/guilds.json +++ b/packages/i18n/src/locales/sk/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "No servers found", "description": "Make sure the bot is added to a server where you have the Manage Server permission." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/sk/welcome.json b/packages/i18n/src/locales/sk/welcome.json index 3ed55ae..a9e2ff5 100644 --- a/packages/i18n/src/locales/sk/welcome.json +++ b/packages/i18n/src/locales/sk/welcome.json @@ -128,7 +128,12 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" }, "saveChanges": "Save Changes", "saving": "Saving...", diff --git a/packages/i18n/src/locales/sl/common.json b/packages/i18n/src/locales/sl/common.json index e5f40ce..c5a04b3 100644 --- a/packages/i18n/src/locales/sl/common.json +++ b/packages/i18n/src/locales/sl/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/sl/guilds.json b/packages/i18n/src/locales/sl/guilds.json index 0632077..c6b731a 100644 --- a/packages/i18n/src/locales/sl/guilds.json +++ b/packages/i18n/src/locales/sl/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Ni najdenih strežnikov", "description": "Prepričajte se, da je bot dodan strežniku, na katerem imate dovoljenje Upravljanje strežnika." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/sl/welcome.json b/packages/i18n/src/locales/sl/welcome.json index a2d85bc..01e5907 100644 --- a/packages/i18n/src/locales/sl/welcome.json +++ b/packages/i18n/src/locales/sl/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/sr/common.json b/packages/i18n/src/locales/sr/common.json index 5d8cccb..920e3b3 100644 --- a/packages/i18n/src/locales/sr/common.json +++ b/packages/i18n/src/locales/sr/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/sr/guilds.json b/packages/i18n/src/locales/sr/guilds.json index a461390..7fa8edc 100644 --- a/packages/i18n/src/locales/sr/guilds.json +++ b/packages/i18n/src/locales/sr/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Nisu pronadeni serveri", "description": "Proverite da li je bot dodat na server na kojem imate dozvolu za upravljanje serverom." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/sr/welcome.json b/packages/i18n/src/locales/sr/welcome.json index 8ee95b7..1b74d96 100644 --- a/packages/i18n/src/locales/sr/welcome.json +++ b/packages/i18n/src/locales/sr/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/sv/common.json b/packages/i18n/src/locales/sv/common.json index 71dfd2a..65d27de 100644 --- a/packages/i18n/src/locales/sv/common.json +++ b/packages/i18n/src/locales/sv/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/sv/guilds.json b/packages/i18n/src/locales/sv/guilds.json index eea5364..d7411b6 100644 --- a/packages/i18n/src/locales/sv/guilds.json +++ b/packages/i18n/src/locales/sv/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Inga servrar hittades", "description": "Se till att boten är tillagd på en server där du har behörigheten Hantera server." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/sv/welcome.json b/packages/i18n/src/locales/sv/welcome.json index 7a687f4..4281610 100644 --- a/packages/i18n/src/locales/sv/welcome.json +++ b/packages/i18n/src/locales/sv/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/sw/common.json b/packages/i18n/src/locales/sw/common.json index 3afaf56..7004514 100644 --- a/packages/i18n/src/locales/sw/common.json +++ b/packages/i18n/src/locales/sw/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/sw/guilds.json b/packages/i18n/src/locales/sw/guilds.json index c7eac9b..f2d84c7 100644 --- a/packages/i18n/src/locales/sw/guilds.json +++ b/packages/i18n/src/locales/sw/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Hakuna seva zilizopatikana", "description": "Hakikisha bot imeongezwa kwenye seva ambayo una ruhusa ya Kusimamia Seva." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/sw/welcome.json b/packages/i18n/src/locales/sw/welcome.json index c89d6e7..8b85e55 100644 --- a/packages/i18n/src/locales/sw/welcome.json +++ b/packages/i18n/src/locales/sw/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/ta/common.json b/packages/i18n/src/locales/ta/common.json index 55a62e6..e7b67ff 100644 --- a/packages/i18n/src/locales/ta/common.json +++ b/packages/i18n/src/locales/ta/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/ta/guilds.json b/packages/i18n/src/locales/ta/guilds.json index d6fe784..e108095 100644 --- a/packages/i18n/src/locales/ta/guilds.json +++ b/packages/i18n/src/locales/ta/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "சேவையகங்கள் எதுவும் கிடைக்கவில்லை", "description": "சேவையக நிர்வாகிப்பு அனுமதி உங்களிடம் உள்ள சேவையகத்தில் பாட் சேர்க்கப்பட்டுள்ளதை உறுதிப்படுத்தவும்." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/ta/welcome.json b/packages/i18n/src/locales/ta/welcome.json index 2a80ded..2ac02db 100644 --- a/packages/i18n/src/locales/ta/welcome.json +++ b/packages/i18n/src/locales/ta/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/th/common.json b/packages/i18n/src/locales/th/common.json index 6798a99..b84eeb3 100644 --- a/packages/i18n/src/locales/th/common.json +++ b/packages/i18n/src/locales/th/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/th/guilds.json b/packages/i18n/src/locales/th/guilds.json index 77a7516..017b920 100644 --- a/packages/i18n/src/locales/th/guilds.json +++ b/packages/i18n/src/locales/th/guilds.json @@ -2,5 +2,9 @@ "title": "เซิร์ฟเวอร์ของคุณ", "subtitle": "เลือกเซิร์ฟเวอร์ที่ต้องการจัดการและตั้งค่า", "loading": "กำลังโหลดเซิร์ฟเวอร์...", - "empty": { "title": "ไม่พบเซิร์ฟเวอร์", "description": "ตรวจสอบว่าบอทถูกเพิ่มในเซิร์ฟเวอร์ที่คุณมีสิทธิ์จัดการเซิร์ฟเวอร์" } + "empty": { + "title": "ไม่พบเซิร์ฟเวอร์", + "description": "ตรวจสอบว่าบอทถูกเพิ่มในเซิร์ฟเวอร์ที่คุณมีสิทธิ์จัดการเซิร์ฟเวอร์" + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/th/welcome.json b/packages/i18n/src/locales/th/welcome.json index c009504..a826468 100644 --- a/packages/i18n/src/locales/th/welcome.json +++ b/packages/i18n/src/locales/th/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/tr/common.json b/packages/i18n/src/locales/tr/common.json index dfac9eb..e605f7b 100644 --- a/packages/i18n/src/locales/tr/common.json +++ b/packages/i18n/src/locales/tr/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/tr/guilds.json b/packages/i18n/src/locales/tr/guilds.json index 1b24a5c..9e47c95 100644 --- a/packages/i18n/src/locales/tr/guilds.json +++ b/packages/i18n/src/locales/tr/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Sunucu bulunamadı", "description": "Botun, Sunucuyu Yönet iznine sahip olduğunuz bir sunucuya eklendiğinden emin olun." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/tr/welcome.json b/packages/i18n/src/locales/tr/welcome.json index 934b4f9..dba4110 100644 --- a/packages/i18n/src/locales/tr/welcome.json +++ b/packages/i18n/src/locales/tr/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/uk/common.json b/packages/i18n/src/locales/uk/common.json index 8598cd1..013aa41 100644 --- a/packages/i18n/src/locales/uk/common.json +++ b/packages/i18n/src/locales/uk/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/uk/guilds.json b/packages/i18n/src/locales/uk/guilds.json index 4c201db..fd18b0d 100644 --- a/packages/i18n/src/locales/uk/guilds.json +++ b/packages/i18n/src/locales/uk/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Серверів не знайдено", "description": "Переконайтеся, що бот додано на сервер, де у вас є право «Керування сервером»." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/uk/welcome.json b/packages/i18n/src/locales/uk/welcome.json index 933687e..ba1db1f 100644 --- a/packages/i18n/src/locales/uk/welcome.json +++ b/packages/i18n/src/locales/uk/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/ur/common.json b/packages/i18n/src/locales/ur/common.json index f2f049e..005e8b5 100644 --- a/packages/i18n/src/locales/ur/common.json +++ b/packages/i18n/src/locales/ur/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "زبانیں تلاش کریں...", "noResults": "کوئی زبان نہیں ملی" + }, + "variableField": { + "insert": "متغیر داخل کریں", + "search": "متغیرات تلاش کریں…", + "noMatches": "کوئی مماثلت نہیں", + "unknown": "نامعلوم متغیر {{token}}", + "didYouMean": "کیا آپ کا مطلب {{suggestion}} تھا؟", + "preview": "پیش نظارہ", + "notAvailable": "{{token}} {{event}} کے لیے دستیاب نہیں" + }, + "variables": { + "user": "صارف کا ذکر کرتا ہے", + "userTag": "صارف کا ٹیگ (مثلاً name#0001)", + "userName": "صارف کا صارف نام", + "userId": "صارف کی شناخت (ID)", + "userAvatar": "صارف کی اوتار کی URL", + "server": "سرور کا نام", + "serverId": "سرور کی شناخت (ID)", + "memberCount": "سرور کے اراکین کی تعداد", + "serverIcon": "سرور کے آئیکن کی URL", + "level": "وہ لیول جو رکن نے حاصل کیا", + "entries": { + "user": "اُس صارف کا ذکر کریں جس نے کمانڈ چلائی", + "username": "صارف کا ظاہر ہونے والا نام", + "userId": "صارف کی شناخت (ID)", + "server": "سرور کا نام", + "channel": "چینل کا ذکر", + "channelName": "چینل کا نام", + "memberCount": "سرور کے اراکین کی تعداد" + } } } diff --git a/packages/i18n/src/locales/ur/guilds.json b/packages/i18n/src/locales/ur/guilds.json index a49ab48..f41270c 100644 --- a/packages/i18n/src/locales/ur/guilds.json +++ b/packages/i18n/src/locales/ur/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "کوئی سرور نہیں ملا", "description": "یقینی بنائیں کہ بوٹ کسی ایسے سرور میں شامل ہے جہاں آپ کے پاس Manage Server کی اجازت ہے۔" - } + }, + "refresh": "سرورز کو تازہ کریں" } diff --git a/packages/i18n/src/locales/ur/welcome.json b/packages/i18n/src/locales/ur/welcome.json index 0395655..24ebf97 100644 --- a/packages/i18n/src/locales/ur/welcome.json +++ b/packages/i18n/src/locales/ur/welcome.json @@ -138,6 +138,11 @@ "uploaded": "پس منظر اپ لوڈ ہو گیا", "uploadFailed": "پس منظر اپ لوڈ کرنے میں ناکامی", "previewFailed": "پیش نظارہ بنانے میں ناکامی" - } + }, + "previewFailedHint": "پیش نظارہ رینڈر نہیں ہو سکا۔ تازہ کریں یا موڈ تبدیل کریں۔", + "clientMode": "فوری", + "serverMode": "سرور", + "switchToServer": "سرور پیش نظارے پر جائیں", + "switchToClient": "فوری پیش نظارے پر جائیں" } } diff --git a/packages/i18n/src/locales/vi/common.json b/packages/i18n/src/locales/vi/common.json index 3cba074..ebda2b0 100644 --- a/packages/i18n/src/locales/vi/common.json +++ b/packages/i18n/src/locales/vi/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/vi/guilds.json b/packages/i18n/src/locales/vi/guilds.json index f1e9794..1c3b242 100644 --- a/packages/i18n/src/locales/vi/guilds.json +++ b/packages/i18n/src/locales/vi/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "Không tìm thấy máy chủ", "description": "Hãy đảm bảo bot đã được thêm vào máy chủ mà bạn có quyền Quản Lý Máy Chủ." - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/vi/welcome.json b/packages/i18n/src/locales/vi/welcome.json index 46ce2d4..5d5d439 100644 --- a/packages/i18n/src/locales/vi/welcome.json +++ b/packages/i18n/src/locales/vi/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } } diff --git a/packages/i18n/src/locales/zh-CN/common.json b/packages/i18n/src/locales/zh-CN/common.json index 0868ad3..3a8187a 100644 --- a/packages/i18n/src/locales/zh-CN/common.json +++ b/packages/i18n/src/locales/zh-CN/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "搜索语言……", "noResults": "未找到语言" + }, + "variableField": { + "insert": "插入变量", + "search": "搜索变量…", + "noMatches": "无匹配项", + "unknown": "未知变量 {{token}}", + "didYouMean": "你是否想输入 {{suggestion}}?", + "preview": "预览", + "notAvailable": "{{token}} 不适用于 {{event}}" + }, + "variables": { + "user": "提及该用户", + "userTag": "用户的标签(例如 name#0001)", + "userName": "用户的用户名", + "userId": "用户的 ID", + "userAvatar": "用户的头像 URL", + "server": "服务器名称", + "serverId": "服务器 ID", + "memberCount": "服务器的成员数量", + "serverIcon": "服务器的图标 URL", + "level": "成员达到的等级", + "entries": { + "user": "提及触发该命令的用户", + "username": "用户的显示名称", + "userId": "用户 ID", + "server": "服务器名称", + "channel": "频道提及", + "channelName": "频道名称", + "memberCount": "服务器成员数量" + } } } diff --git a/packages/i18n/src/locales/zh-CN/guilds.json b/packages/i18n/src/locales/zh-CN/guilds.json index 49dbeb1..c2a6eb1 100644 --- a/packages/i18n/src/locales/zh-CN/guilds.json +++ b/packages/i18n/src/locales/zh-CN/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "未找到服务器", "description": "请确保机器人已添加到服务器,且您在该服务器拥有管理服务器权限。" - } + }, + "refresh": "刷新服务器" } diff --git a/packages/i18n/src/locales/zh-CN/welcome.json b/packages/i18n/src/locales/zh-CN/welcome.json index 13d1e0e..25fd368 100644 --- a/packages/i18n/src/locales/zh-CN/welcome.json +++ b/packages/i18n/src/locales/zh-CN/welcome.json @@ -138,6 +138,11 @@ "uploaded": "背景已上传", "uploadFailed": "上传背景失败", "previewFailed": "生成预览失败" - } + }, + "previewFailedHint": "预览渲染失败。请尝试刷新或切换模式。", + "clientMode": "即时", + "serverMode": "服务器", + "switchToServer": "切换到服务器预览", + "switchToClient": "切换到即时预览" } } diff --git a/packages/i18n/src/locales/zh-TW/common.json b/packages/i18n/src/locales/zh-TW/common.json index 3c68b5c..b4d3c9f 100644 --- a/packages/i18n/src/locales/zh-TW/common.json +++ b/packages/i18n/src/locales/zh-TW/common.json @@ -141,5 +141,35 @@ "language": { "searchPlaceholder": "Search languages...", "noResults": "No language found" + }, + "variableField": { + "insert": "Insert variable", + "search": "Search variables…", + "noMatches": "No matches", + "unknown": "Unknown variable {{token}}", + "didYouMean": "did you mean {{suggestion}}?", + "preview": "Preview", + "notAvailable": "{{token}} isn't available for {{event}}" + }, + "variables": { + "user": "Mentions the user", + "userTag": "The user's tag (e.g. name#0001)", + "userName": "The user's username", + "userId": "The user's ID", + "userAvatar": "The user's avatar URL", + "server": "The server name", + "serverId": "The server ID", + "memberCount": "The server's member count", + "serverIcon": "The server icon URL", + "level": "The level the member reached", + "entries": { + "user": "Mention the user who triggered the command", + "username": "Display name of the user", + "userId": "User ID", + "server": "Server name", + "channel": "Channel mention", + "channelName": "Channel name", + "memberCount": "Server member count" + } } } diff --git a/packages/i18n/src/locales/zh-TW/guilds.json b/packages/i18n/src/locales/zh-TW/guilds.json index 7675bc2..4fc3a8e 100644 --- a/packages/i18n/src/locales/zh-TW/guilds.json +++ b/packages/i18n/src/locales/zh-TW/guilds.json @@ -5,5 +5,6 @@ "empty": { "title": "找不到伺服器", "description": "請確認機器人已新增至伺服器,且您擁有管理伺服器權限。" - } + }, + "refresh": "Refresh servers" } diff --git a/packages/i18n/src/locales/zh-TW/welcome.json b/packages/i18n/src/locales/zh-TW/welcome.json index 0436026..1e7e384 100644 --- a/packages/i18n/src/locales/zh-TW/welcome.json +++ b/packages/i18n/src/locales/zh-TW/welcome.json @@ -138,6 +138,11 @@ "uploaded": "Background uploaded", "uploadFailed": "Failed to upload background", "previewFailed": "Failed to generate preview" - } + }, + "previewFailedHint": "Preview failed to render. Try refreshing or switching modes.", + "clientMode": "Instant", + "serverMode": "Server", + "switchToServer": "Switch to server preview", + "switchToClient": "Switch to instant preview" } }