From b7bb3d947820fc6450a9c777fa48f9b0833ec011 Mon Sep 17 00:00:00 2001 From: BruceWayne Date: Thu, 9 Oct 2025 20:42:16 +0530 Subject: [PATCH 1/4] implemented internationalization --- apps/web/messages/en.json | 252 ++++++++++++++++++ apps/web/messages/es.json | 252 ++++++++++++++++++ apps/web/next.config.ts | 5 +- apps/web/package.json | 3 +- .../{ => [locale]}/api/auth/logout/route.ts | 0 .../api/auth/refresh-token/route.ts | 0 .../api/auth/set-refresh-token/route.ts | 0 .../auth/callback/AuthCallbackClient.tsx | 0 .../app/{ => [locale]}/auth/callback/page.tsx | 0 .../src/app/{ => [locale]}/auth/layout.tsx | 0 .../dashboard/avatars/loading.tsx | 0 .../{ => [locale]}/dashboard/avatars/page.tsx | 31 ++- .../app/{ => [locale]}/dashboard/layout.tsx | 0 .../src/app/{ => [locale]}/dashboard/page.tsx | 100 ++++--- .../{ => [locale]}/dashboard/plans/page.tsx | 85 +++--- .../{ => [locale]}/dashboard/send/loading.tsx | 0 .../{ => [locale]}/dashboard/send/page.tsx | 110 +++++--- .../dashboard/settings/page.tsx | 149 +++-------- .../dashboard/templates/create/loading.tsx | 0 .../dashboard/templates/create/page.tsx | 22 +- .../dashboard/templates/loading.tsx | 0 .../dashboard/templates/page.tsx | 64 +++-- .../dashboard/webhooks/loading.tsx | 0 .../dashboard/webhooks/page.tsx | 33 +-- apps/web/src/app/{ => [locale]}/globals.css | 0 apps/web/src/app/{ => [locale]}/layout.tsx | 26 +- .../web/src/app/{ => [locale]}/login/page.tsx | 21 +- apps/web/src/app/[locale]/page.tsx | 129 +++++++++ apps/web/src/app/page.tsx | 206 -------------- apps/web/src/components/dashboard-header.tsx | 39 ++- apps/web/src/i18n/navigation.ts | 7 + apps/web/src/i18n/request.ts | 16 ++ apps/web/src/i18n/routing.ts | 14 + apps/web/src/middleware.ts | 11 + pnpm-lock.yaml | 109 ++++++++ 35 files changed, 1155 insertions(+), 529 deletions(-) create mode 100644 apps/web/messages/en.json create mode 100644 apps/web/messages/es.json rename apps/web/src/app/{ => [locale]}/api/auth/logout/route.ts (100%) rename apps/web/src/app/{ => [locale]}/api/auth/refresh-token/route.ts (100%) rename apps/web/src/app/{ => [locale]}/api/auth/set-refresh-token/route.ts (100%) rename apps/web/src/app/{ => [locale]}/auth/callback/AuthCallbackClient.tsx (100%) rename apps/web/src/app/{ => [locale]}/auth/callback/page.tsx (100%) rename apps/web/src/app/{ => [locale]}/auth/layout.tsx (100%) rename apps/web/src/app/{ => [locale]}/dashboard/avatars/loading.tsx (100%) rename apps/web/src/app/{ => [locale]}/dashboard/avatars/page.tsx (79%) rename apps/web/src/app/{ => [locale]}/dashboard/layout.tsx (100%) rename apps/web/src/app/{ => [locale]}/dashboard/page.tsx (52%) rename apps/web/src/app/{ => [locale]}/dashboard/plans/page.tsx (50%) rename apps/web/src/app/{ => [locale]}/dashboard/send/loading.tsx (100%) rename apps/web/src/app/{ => [locale]}/dashboard/send/page.tsx (81%) rename apps/web/src/app/{ => [locale]}/dashboard/settings/page.tsx (57%) rename apps/web/src/app/{ => [locale]}/dashboard/templates/create/loading.tsx (100%) rename apps/web/src/app/{ => [locale]}/dashboard/templates/create/page.tsx (88%) rename apps/web/src/app/{ => [locale]}/dashboard/templates/loading.tsx (100%) rename apps/web/src/app/{ => [locale]}/dashboard/templates/page.tsx (87%) rename apps/web/src/app/{ => [locale]}/dashboard/webhooks/loading.tsx (100%) rename apps/web/src/app/{ => [locale]}/dashboard/webhooks/page.tsx (90%) rename apps/web/src/app/{ => [locale]}/globals.css (100%) rename apps/web/src/app/{ => [locale]}/layout.tsx (67%) rename apps/web/src/app/{ => [locale]}/login/page.tsx (68%) create mode 100644 apps/web/src/app/[locale]/page.tsx delete mode 100644 apps/web/src/app/page.tsx create mode 100644 apps/web/src/i18n/navigation.ts create mode 100644 apps/web/src/i18n/request.ts create mode 100644 apps/web/src/i18n/routing.ts create mode 100644 apps/web/src/middleware.ts diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json new file mode 100644 index 0000000..e259037 --- /dev/null +++ b/apps/web/messages/en.json @@ -0,0 +1,252 @@ +{ + "meta": { + "title": "Discord Webhook Manager - Supercharge Your Discord Communication", + "description": "Effortlessly manage, automate, and send rich messages to your Discord channels. Streamline announcements, updates, and interactions with our intuitive Webhook Manager." + }, + "home": { + "hero": { + "heading": "Supercharge Your Discord Communication", + "subtext": "Eliminate manual work, costly bots, and complex custom solutions. Effortlessly manage, automate, and send rich messages to your Discord channels, streamlining announcements and interactions with our intuitive Webhook Manager.", + "button_logged_in": "Go to Dashboard", + "button_logged_out": "Get Started for Free" + }, + "whatWeDo": { + "title": "What We Do", + "features": { + "centralized": { + "title": "Centralized Webhook Management", + "desc": "Keep all your Discord webhooks organized in one place. Easily add, edit, and delete webhooks, eliminating the hassle of juggling multiple Discord servers or manual configurations." + }, + "richMessages": { + "title": "Rich Message Composition", + "desc": "Craft stunning Discord messages with full support for embeds, custom avatars, and usernames. Make your announcements stand out without the need for custom bots or tedious manual work." + }, + "customAvatars": { + "title": "Custom Avatars & Identities", + "desc": "Define and reuse custom avatars and usernames for your webhook messages, allowing for dynamic and engaging announcements tailored to different requirements." + }, + "ui": { + "title": "Intuitive User Interface", + "desc": "Our clean and user-friendly dashboard makes managing your Discord webhooks a breeze, even for beginners." + }, + "secure": { + "title": "Secure & Reliable", + "desc": "Built with security in mind, ensuring your webhook data and Discord interactions are safe and dependable." + } + } + }, + "contact": { + "title": "Get in Touch", + "desc": "Have questions, feedback, or just want to say hello? Feel free to reach out!", + "email": "coderck@proton.me", + "github": "ctrixcode", + "portfolio": "My Portfolio" + }, + "footer": { + "copyright": "Discord Webhook Manager. All rights reserved." + } + }, + "login": { + "checking": "Checking your session...", + "welcome": "Welcome!", + "loginMessage": "Please log in with your Discord account to manage your webhooks.", + "loginButton": "Login with Discord" + }, + "dashboard": { + "welcome": "Welcome back!", + "manage": "Manage your Discord webhooks and messages", + "quickActions": "Quick Actions", + "webhook": "Webhook", + "sendMessage": "Send Message", + "template": "Template", + "gettingStarted": "Getting Started", + "gettingStartedDesc": "New to webhook management? Here's how to get started quickly.", + "steps": { + "oneTitle": "Connect your first webhook", + "oneDesc": "Add a Discord webhook URL to start sending messages.", + "twoTitle": "Send your first message", + "twoDesc": "Test your webhook by sending a message to your Discord channel.", + "threeTitle": "Create templates and schedule", + "threeDesc": "Build reusable templates and schedule messages for later." + }, + "title": "Webhooks", + "subtitle": "Manage your Discord webhooks", + "stats": { + "total": "Total Webhooks", + "active": "active", + "activeTitle": "Active Webhooks", + "ready": "Ready to send messages" + }, + "search": { + "placeholder": "Search webhooks..." + }, + "filters": { + "all": "All", + "active": "Active", + "inactive": "Inactive" + }, + "empty": { + "noResults": { + "title": "No webhooks found", + "desc": "Try adjusting your search query or filters" + }, + "noWebhooks": { + "title": "No webhooks yet", + "desc": "Get started by adding your first Discord webhook" + } + }, + "templates": { + "title": "Message Templates", + "subtitle": "Create and manage reusable message templates", + "createButton": "Create Template", + "searchPlaceholder": "Search templates...", + "badgeUses": "TODO uses", + "edit": "Edit", + "delete": "Delete", + "contentPreview": "Content Preview:", + "noContent": "No content", + "embedCount": "{count} embed{plural}", + "noTemplatesTitle": "No templates yet", + "noTemplatesSubtitle": "Create your first message template to get started", + "noResultsTitle": "No templates found", + "noResultsSubtitle": "Try adjusting your search query", + "deleteDialogTitle": "Delete template?", + "deleteDialogDescription": "This action cannot be undone. This will permanently delete the template \"{name}\" from your account.", + "cancel": "Cancel", + "confirmDelete": "Delete", + "toast": { + "deletedTitle": "Template deleted", + "deletedDescription": "Template has been removed" + } + }, + "createTemplate": { + "back": "Back", + "createTitle": "Create Template", + "editTitle": "Edit Template", + "subtitle": "Design your Discord message with live preview", + "save": "Save", + "update": "Update", + "saving": "Saving...", + "toast": { + "savedTitle": "Template saved", + "savedDesc": "Template saved successfully!", + "errorTitle": "Error saving template" + } + }, + "settingsPage": { + "title": "Settings", + "subtitle": "Manage your account and application preferences", + "accountType": { + "title": "Account Type", + "desc": "Your current subscription level", + "error": "Could not load account type." + }, + "accountQuotes": { + "free": "Explore the basics, unlock your potential.", + "paid": "Elevate your experience, achieve more.", + "premium": "Unleash the ultimate power, no limits." + }, + "usage": { + "title": "Usage", + "desc": "Track your current usage and limits", + "webhookMessages": "Daily Webhook Messages", + "mediaStorage": "Media Storage", + "error": "Could not load usage data." + }, + "plans": { + "title": "Subscription Plans", + "desc": "View details about different subscription tiers and their benefits", + "button": "View Plans" + }, + "support": { + "title": "Support Us", + "desc": "Help us grow by sharing Discord Webhook Manager", + "message": "If you enjoy using Discord Webhook Manager, consider sharing it on X (formerly Twitter)!", + "button": "Share on X (Twitter)", + "dm": "DM me on X if you want to get paid subscriptions." + }, + "clearData": { + "title": "Clear All Data?", + "desc": "Are you sure you want to clear all data? This action cannot be undone.", + "confirm": "Clear Data" + } + }, + "sendMessagePage": { + "title": "Send Message", + "subtitle": "Send messages immediately to one or multiple webhooks", + "sendTo": "Send to {count, plural, one {# webhook} other {# webhooks}}", + "sending": "Sending...", + "tabs": { + "content": "Content", + "settings": "Settings", + "embeds": "Embeds", + "webhooks": "Webhooks" + }, + "composeMessage": "Compose Message", + "characters": "characters", + "clear": "Clear", + "loadTemplate": "Load Template", + "messageText": "Message Text", + "messagePlaceholder": "Enter your message content...", + "appearance": "Message Appearance", + "chooseAvatar": "Choose from your saved avatar profiles", + "tts": "Text-to-Speech", + "enableTts": "Enable TTS for this message", + "threadName": "Thread Name (Optional)", + "threadHint": "If specified, the message will be sent to a new thread", + "messageUrl": "Discord Message URL (Optional)", + "urlHint": "If provided, the message will replace the existing Discord message at this URL.", + "selectWebhooks": "Select Webhooks ({selected}/{total})", + "noWebhooks": "No webhooks available. Add some webhooks first.", + "loadingWebhooks": "Loading webhooks...", + "success": "Message sent successfully to {count, plural, one {# webhook} other {# webhooks}}", + "errorSelect": "Please select at least one webhook", + "errorContent": "Please enter a message or add an embed", + "errorGeneral": "An unexpected error occurred" + }, + "plansPage": { + "title": "Subscription Plans", + "subtitle": "Choose the plan that best fits your needs.", + "plans": { + "free": { + "name": "Free", + "description": "Perfect for getting started", + "features": { + "messages_day": "{count} Webhook Messages/Day", + "media_storage": "{count} MB Media Storage", + "unlimited_messages": "Unlimited Webhook Messages/Day", + "large_storage": "{count} GB Media Storage", + "custom_avatars": "Custom Avatars", + "priority_support": "Priority Support", + "dedicated_support": "24/7 Dedicated Support" + }, + "cta_current": "Current Plan" + }, + "paid": { + "name": "Paid", + "description": "For growing communities", + "cta_upgrade": "Upgrade" + }, + "premium": { + "name": "Premium", + "description": "Unleash full power", + "cta_upgrade": "Upgrade" + } + } + }, + "avatarsPage": { + "title": "Predefined Avatars", + "subtitle": "Create and manage reusable avatar profiles for your webhooks", + "createButton": "Create Avatar", + "searchPlaceholder": "Search avatars...", + "loading": "Loading avatars...", + "emptyState": { + "noAvatarsTitle": "No avatars yet", + "noAvatarsMessage": "Create your first predefined avatar to get started", + "noResultsTitle": "No avatars found", + "noResultsMessage": "Try adjusting your search terms" + } + } + } +} + diff --git a/apps/web/messages/es.json b/apps/web/messages/es.json new file mode 100644 index 0000000..c4df07a --- /dev/null +++ b/apps/web/messages/es.json @@ -0,0 +1,252 @@ +{ + "meta": { + "title": "Discord Webhook Manager - Potencia tu comunicación en Discord", + "description": "Administra, automatiza y envía mensajes enriquecidos a tus canales de Discord sin esfuerzo. Optimiza los anuncios, actualizaciones e interacciones con nuestro intuitivo Administrador de Webhooks." + }, + "home": { + "hero": { + "heading": "Potencia tu comunicación en Discord", + "subtext": "Elimina el trabajo manual, los bots costosos y las soluciones personalizadas complejas. Administra, automatiza y envía mensajes enriquecidos a tus canales de Discord, optimizando los anuncios e interacciones con nuestro intuitivo Administrador de Webhooks.", + "button_logged_in": "Ir al Panel", + "button_logged_out": "Comienza gratis" + }, + "whatWeDo": { + "title": "Lo que hacemos", + "features": { + "centralized": { + "title": "Gestión centralizada de Webhooks", + "desc": "Mantén todos tus webhooks de Discord organizados en un solo lugar. Agrega, edita y elimina webhooks fácilmente, eliminando la molestia de manejar múltiples servidores o configuraciones manuales." + }, + "richMessages": { + "title": "Composición de mensajes enriquecidos", + "desc": "Crea impresionantes mensajes de Discord con soporte completo para embeds, avatares personalizados y nombres de usuario. Haz que tus anuncios destaquen sin necesidad de bots personalizados o trabajo tedioso." + }, + "customAvatars": { + "title": "Avatares e identidades personalizadas", + "desc": "Define y reutiliza avatares y nombres de usuario personalizados para tus mensajes de webhook, permitiendo anuncios dinámicos y atractivos adaptados a diferentes necesidades." + }, + "ui": { + "title": "Interfaz de usuario intuitiva", + "desc": "Nuestro panel limpio y fácil de usar hace que la gestión de tus webhooks de Discord sea muy sencilla, incluso para principiantes." + }, + "secure": { + "title": "Seguro y confiable", + "desc": "Construido con la seguridad en mente, garantizando que tus datos de webhook y las interacciones con Discord sean seguras y confiables." + } + } + }, + "contact": { + "title": "Ponte en contacto", + "desc": "¿Tienes preguntas, comentarios o simplemente quieres saludar? ¡No dudes en comunicarte!", + "email": "coderck@proton.me", + "github": "ctrixcode", + "portfolio": "Mi Portafolio" + }, + "footer": { + "copyright": "Discord Webhook Manager. Todos los derechos reservados." + } + }, + "login": { + "checking": "Comprobando tu sesión...", + "welcome": "¡Bienvenido!", + "loginMessage": "Inicia sesión con tu cuenta de Discord para administrar tus webhooks.", + "loginButton": "Iniciar sesión con Discord" + }, + "dashboard": { + "welcome": "¡Bienvenido de nuevo!", + "manage": "Administra tus webhooks y mensajes de Discord", + "quickActions": "Acciones rápidas", + "webhook": "Webhook", + "sendMessage": "Enviar mensaje", + "template": "Plantilla", + "gettingStarted": "Empezando", + "gettingStartedDesc": "¿Nuevo en la gestión de webhooks? Aquí te mostramos cómo empezar rápidamente.", + "steps": { + "oneTitle": "Conecta tu primer webhook", + "oneDesc": "Agrega una URL de webhook de Discord para comenzar a enviar mensajes.", + "twoTitle": "Envía tu primer mensaje", + "twoDesc": "Prueba tu webhook enviando un mensaje a tu canal de Discord.", + "threeTitle": "Crea plantillas y programa mensajes", + "threeDesc": "Crea plantillas reutilizables y programa mensajes para más tarde." + }, + "title": "Webhooks", + "subtitle": "Administra tus webhooks de Discord", + "stats": { + "total": "Webhooks Totales", + "active": "Webhooks Activos", + "activeTitle": "Listos para enviar mensajes", + "ready": "activos" + }, + "filters": { + "all": "Todos", + "active": "Activos", + "inactive": "Inactivos", + "searchPlaceholder": "Buscar webhooks..." + }, + "empty": { + "noResultsTitle": "No se encontraron webhooks", + "noResultsSubtitle": "Prueba ajustando tu búsqueda o filtros", + "noWebhooks": { + "title": "Aún no hay webhooks", + "desc": "Comienza agregando tu primer webhook de Discord" + }, + "noWebhooksSubtitle": "Comienza agregando tu primer webhook de Discord" + }, + "search": { + "placeholder": "Search webhooks..." + }, + "templates": { + "title": "Plantillas de Mensajes", + "subtitle": "Crea y administra plantillas de mensajes reutilizables", + "createButton": "Crear Plantilla", + "searchPlaceholder": "Buscar plantillas...", + "badgeUses": "TODO usos", + "edit": "Editar", + "delete": "Eliminar", + "contentPreview": "Vista previa del contenido:", + "noContent": "Sin contenido", + "embedCount": "{count} incrustación{plural}", + "noTemplatesTitle": "Aún no hay plantillas", + "noTemplatesSubtitle": "Crea tu primera plantilla de mensaje para comenzar", + "noResultsTitle": "No se encontraron plantillas", + "noResultsSubtitle": "Prueba ajustando tu búsqueda", + "deleteDialogTitle": "¿Eliminar plantilla?", + "deleteDialogDescription": "Esta acción no se puede deshacer. Esto eliminará permanentemente la plantilla \"{name}\" de tu cuenta.", + "cancel": "Cancelar", + "confirmDelete": "Eliminar", + "toast": { + "deletedTitle": "Plantilla eliminada", + "deletedDescription": "La plantilla ha sido eliminada" + } + }, + "createTemplate": { + "back": "Atrás", + "createTitle": "Crear plantilla", + "editTitle": "Editar plantilla", + "subtitle": "Diseña tu mensaje de Discord con vista previa en vivo", + "save": "Guardar", + "update": "Actualizar", + "saving": "Guardando...", + "toast": { + "savedTitle": "Plantilla guardada", + "savedDesc": "¡Plantilla guardada con éxito!", + "errorTitle": "Error al guardar la plantilla" + } + }, + "settingsPage": { + "title": "Configuración", + "subtitle": "Administra tu cuenta y preferencias de la aplicación", + "accountType": { + "title": "Tipo de cuenta", + "desc": "Tu nivel actual de suscripción", + "error": "No se pudo cargar el tipo de cuenta." + }, + "accountQuotes": { + "free": "Explora lo básico, desbloquea tu potencial.", + "paid": "Eleva tu experiencia, logra más.", + "premium": "Libera el máximo poder, sin límites." + }, + "usage": { + "title": "Uso", + "desc": "Supervisa tu uso actual y tus límites", + "webhookMessages": "Mensajes de Webhook diarios", + "mediaStorage": "Almacenamiento multimedia", + "error": "No se pudieron cargar los datos de uso." + }, + "plans": { + "title": "Planes de suscripción", + "desc": "Consulta los diferentes niveles de suscripción y sus beneficios", + "button": "Ver planes" + }, + "support": { + "title": "Apóyanos", + "desc": "Ayúdanos a crecer compartiendo Discord Webhook Manager", + "message": "Si disfrutas usar Discord Webhook Manager, ¡considera compartirlo en X (anteriormente Twitter)!", + "button": "Compartir en X (Twitter)", + "dm": "Envíame un DM en X si quieres obtener suscripciones de pago." + }, + "clearData": { + "title": "¿Borrar todos los datos?", + "desc": "¿Estás seguro de que quieres borrar todos los datos? Esta acción no se puede deshacer.", + "confirm": "Borrar datos" + } + }, + "sendMessagePage": { + "title": "Enviar Mensaje", + "subtitle": "Envía mensajes inmediatamente a uno o varios webhooks", + "sendTo": "Enviar a {count, plural, one {# webhook} other {# webhooks}}", + "sending": "Enviando...", + "tabs": { + "content": "Contenido", + "settings": "Configuración", + "embeds": "Embeds", + "webhooks": "Webhooks" + }, + "composeMessage": "Componer Mensaje", + "characters": "caracteres", + "clear": "Limpiar", + "loadTemplate": "Cargar Plantilla", + "messageText": "Texto del Mensaje", + "messagePlaceholder": "Introduce el contenido de tu mensaje...", + "appearance": "Apariencia del Mensaje", + "chooseAvatar": "Elige entre tus perfiles de avatar guardados", + "tts": "Texto a Voz", + "enableTts": "Habilitar TTS para este mensaje", + "threadName": "Nombre del Hilo (Opcional)", + "threadHint": "Si se especifica, el mensaje se enviará a un nuevo hilo", + "messageUrl": "URL del Mensaje de Discord (Opcional)", + "urlHint": "Si se proporciona, el mensaje reemplazará al mensaje de Discord existente en esta URL.", + "selectWebhooks": "Seleccionar Webhooks ({selected}/{total})", + "noWebhooks": "No hay webhooks disponibles. Añade algunos webhooks primero.", + "loadingWebhooks": "Cargando webhooks...", + "success": "Mensaje enviado con éxito a {count, plural, one {# webhook} other {# webhooks}}", + "errorSelect": "Selecciona al menos un webhook", + "errorContent": "Introduce un mensaje o añade un embed", + "errorGeneral": "Ocurrió un error inesperado" + }, + "plansPage": { + "title": "Planes de Suscripción", + "subtitle": "Elige el plan que mejor se adapte a tus necesidades.", + "plans": { + "free": { + "name": "Gratis", + "description": "Perfecto para empezar", + "features": { + "messages_day": "{count} Mensajes de Webhook/Día", + "media_storage": "{count} MB de Almacenamiento Multimedia", + "unlimited_messages": "Mensajes de Webhook Ilimitados/Día", + "large_storage": "{count} GB de Almacenamiento Multimedia", + "custom_avatars": "Avatares Personalizados", + "priority_support": "Soporte Prioritario", + "dedicated_support": "Soporte Dedicado 24/7" + }, + "cta_current": "Plan Actual" + }, + "paid": { + "name": "Pago", + "description": "Para comunidades en crecimiento", + "cta_upgrade": "Actualizar" + }, + "premium": { + "name": "Premium", + "description": "Libera todo el poder", + "cta_upgrade": "Actualizar" + } + } + }, + "avatarsPage": { + "title": "Avatares Predefinidos", + "subtitle": "Crea y gestiona perfiles de avatar reutilizables para tus webhooks", + "createButton": "Crear Avatar", + "searchPlaceholder": "Buscar avatares...", + "loading": "Cargando avatares...", + "emptyState": { + "noAvatarsTitle": "Aún no hay avatares", + "noAvatarsMessage": "Crea tu primer avatar predefinido para empezar", + "noResultsTitle": "No se encontraron avatares", + "noResultsMessage": "Intenta ajustar tus términos de búsqueda" + } + } + } +} + diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 15d0653..bb67621 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,4 +1,7 @@ import type { NextConfig } from 'next'; +import createNextIntlPlugin from 'next-intl/plugin'; + +const withNextIntl = createNextIntlPlugin(); const nextConfig: NextConfig = { images: { @@ -7,4 +10,4 @@ const nextConfig: NextConfig = { }, }; -export default nextConfig; +export default withNextIntl(nextConfig); diff --git a/apps/web/package.json b/apps/web/package.json index 455e4a6..c45807c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -53,6 +53,7 @@ "input-otp": "^1.4.2", "lucide-react": "^0.539.0", "next": "^15.4.6", + "next-intl": "^4.3.11", "next-themes": "^0.4.6", "react": "^19.1.0", "react-day-picker": "^9.8.1", @@ -66,9 +67,9 @@ "zod": "^4.0.17" }, "devDependencies": { + "@eslint/eslintrc": "^3", "@repo/eslint-config": "workspace:*", "@repo/typescript-config": "workspace:*", - "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.12", "@types/node": "^20.19.11", "@types/react": "^19.1.10", diff --git a/apps/web/src/app/api/auth/logout/route.ts b/apps/web/src/app/[locale]/api/auth/logout/route.ts similarity index 100% rename from apps/web/src/app/api/auth/logout/route.ts rename to apps/web/src/app/[locale]/api/auth/logout/route.ts diff --git a/apps/web/src/app/api/auth/refresh-token/route.ts b/apps/web/src/app/[locale]/api/auth/refresh-token/route.ts similarity index 100% rename from apps/web/src/app/api/auth/refresh-token/route.ts rename to apps/web/src/app/[locale]/api/auth/refresh-token/route.ts diff --git a/apps/web/src/app/api/auth/set-refresh-token/route.ts b/apps/web/src/app/[locale]/api/auth/set-refresh-token/route.ts similarity index 100% rename from apps/web/src/app/api/auth/set-refresh-token/route.ts rename to apps/web/src/app/[locale]/api/auth/set-refresh-token/route.ts diff --git a/apps/web/src/app/auth/callback/AuthCallbackClient.tsx b/apps/web/src/app/[locale]/auth/callback/AuthCallbackClient.tsx similarity index 100% rename from apps/web/src/app/auth/callback/AuthCallbackClient.tsx rename to apps/web/src/app/[locale]/auth/callback/AuthCallbackClient.tsx diff --git a/apps/web/src/app/auth/callback/page.tsx b/apps/web/src/app/[locale]/auth/callback/page.tsx similarity index 100% rename from apps/web/src/app/auth/callback/page.tsx rename to apps/web/src/app/[locale]/auth/callback/page.tsx diff --git a/apps/web/src/app/auth/layout.tsx b/apps/web/src/app/[locale]/auth/layout.tsx similarity index 100% rename from apps/web/src/app/auth/layout.tsx rename to apps/web/src/app/[locale]/auth/layout.tsx diff --git a/apps/web/src/app/dashboard/avatars/loading.tsx b/apps/web/src/app/[locale]/dashboard/avatars/loading.tsx similarity index 100% rename from apps/web/src/app/dashboard/avatars/loading.tsx rename to apps/web/src/app/[locale]/dashboard/avatars/loading.tsx diff --git a/apps/web/src/app/dashboard/avatars/page.tsx b/apps/web/src/app/[locale]/dashboard/avatars/page.tsx similarity index 79% rename from apps/web/src/app/dashboard/avatars/page.tsx rename to apps/web/src/app/[locale]/dashboard/avatars/page.tsx index e43777d..ce98be0 100644 --- a/apps/web/src/app/dashboard/avatars/page.tsx +++ b/apps/web/src/app/[locale]/dashboard/avatars/page.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { useState, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; // 1. Import useTranslations import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Plus, Search, Users } from 'lucide-react'; @@ -16,6 +17,7 @@ import { CreateAvatarDialog } from '@/components/avatars/create-avatar-dialog'; import { Spinner } from '@/components/ui/spinner'; export default function AvatarsPage() { + const t = useTranslations('dashboard.avatarsPage'); // 2. Initialize translations const { user } = useAuth(); const router = useRouter(); const queryClient = useQueryClient(); @@ -67,10 +69,12 @@ export default function AvatarsPage() {

- Predefined Avatars + {/* 3. Translate Title */} + {t('title')}

- Create and manage reusable avatar profiles for your webhooks + {/* 4. Translate Subtitle */} + {t('subtitle')}

@@ -86,7 +91,8 @@ export default function AvatarsPage() {
setSearchQuery(e.target.value)} className="pl-10 bg-slate-800/50 border-slate-700 text-white placeholder:text-slate-400 focus:border-purple-500" @@ -96,6 +102,8 @@ export default function AvatarsPage() { {/* Content */} {isLoading ? (
+ {/* 7. Loading state is purely visual, but if you wanted a message: +

{t('loading')}

*/}
) : filteredAvatars.length === 0 ? ( @@ -103,12 +111,16 @@ export default function AvatarsPage() {

- {searchQuery ? 'No avatars found' : 'No avatars yet'} + {/* 8. Translate No Avatars/No Results Title */} + {searchQuery + ? t('emptyState.noResultsTitle') + : t('emptyState.noAvatarsTitle')}

+ {/* 9. Translate No Avatars/No Results Message */} {searchQuery - ? 'Try adjusting your search terms' - : 'Create your first predefined avatar to get started'} + ? t('emptyState.noResultsMessage') + : t('emptyState.noAvatarsMessage')}

{!searchQuery && ( )}
@@ -135,7 +148,7 @@ export default function AvatarsPage() {
)} - {/* Create/Edit Dialog */} + {/* Create/Edit Dialog is assumed to be translated internally by nextintl */}

- Welcome back! + {t('welcome')}

-

- Manage your Discord webhooks and messages -

+

{t('manage')}

- Quick Actions + {t('quickActions')} @@ -33,25 +35,27 @@ export default function DashboardPage() { variant="outline" > - Webhook + {t('webhook')} + +
@@ -60,53 +64,29 @@ export default function DashboardPage() { - Getting Started -

- New to webhook management? Here's how to get started quickly. -

+ {t('gettingStarted')} +

{t('gettingStartedDesc')}

-
-
- 1 -
-
-

- Connect your first webhook -

-

- Add a Discord webhook URL to start sending messages -

-
-
-
-
- 2 -
-
-

- Send your first message -

-

- Test your webhook by sending a message to your Discord - channel -

-
-
-
-
- 3 -
-
-

- Create templates and schedule -

-

- Build reusable templates and schedule messages for later -

-
-
+ + +
@@ -114,3 +94,19 @@ export default function DashboardPage() { ); } + +function Step({ number, title, description, color }: any) { + return ( +
+
+ {number} +
+
+

{title}

+

{description}

+
+
+ ); +} diff --git a/apps/web/src/app/dashboard/plans/page.tsx b/apps/web/src/app/[locale]/dashboard/plans/page.tsx similarity index 50% rename from apps/web/src/app/dashboard/plans/page.tsx rename to apps/web/src/app/[locale]/dashboard/plans/page.tsx index 6101412..4df639c 100644 --- a/apps/web/src/app/dashboard/plans/page.tsx +++ b/apps/web/src/app/[locale]/dashboard/plans/page.tsx @@ -1,6 +1,7 @@ 'use client'; import React from 'react'; import { Gem, CheckCircle2 } from 'lucide-react'; +import { useTranslations } from 'next-intl'; // Import useTranslations import { SettingsCard } from '@/components/settings/settings-card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -25,75 +26,82 @@ const PlanFeature: React.FC = ({ text, available }) => ( ); export default function PlansPage() { + const t = useTranslations('dashboard.plansPage'); + const tPlan = useTranslations('dashboard.plansPage.plans'); // Helper for nested path + + // Define the plans data structure using translation keys const plans = [ { - name: 'Free', - description: 'Perfect for getting started', + key: 'free', // Use a key for dynamic translation lookup price: '€0', features: [ - { text: '15 Webhook Messages/Day', available: true }, - { text: '15 MB Media Storage', available: true }, - // { text: '5 Webhooks', available: true }, - // { text: 'Basic Analytics', available: true }, - { text: 'Custom Avatars', available: true }, - { text: 'Priority Support', available: false }, + { textKey: 'free.features.messages_day', count: 15, available: true }, + { textKey: 'free.features.media_storage', count: 15, available: true }, + { textKey: 'free.features.custom_avatars', available: true }, + { textKey: 'free.features.priority_support', available: false }, ], - cta: 'Current Plan', + ctaKey: 'free.cta_current', ctaLink: '#', badgeClass: 'bg-blue-500/20 text-blue-400 border-blue-500/30', }, { - name: 'Paid', - description: 'For growing communities', + key: 'paid', price: '€4.99/month', features: [ - { text: '50 Webhook Messages/Day', available: true }, - { text: '50 MB Media Storage', available: true }, - // { text: '50 Webhooks', available: true }, - // { text: 'Advanced Analytics', available: true }, - { text: 'Priority Support', available: true }, - { text: 'Custom Avatars', available: true }, + { textKey: 'free.features.messages_day', count: 50, available: true }, + { textKey: 'free.features.media_storage', count: 50, available: true }, + { textKey: 'free.features.priority_support', available: true }, + { textKey: 'free.features.custom_avatars', available: true }, ], - cta: 'Upgrade', + ctaKey: 'paid.cta_upgrade', ctaLink: '#', badgeClass: 'bg-purple-500/20 text-purple-400 border-purple-500/30', }, { - name: 'Premium', - description: 'Unleash full power', + key: 'premium', price: '€14.99/month', features: [ - { text: 'Unlimited Webhook Messages/Day', available: true }, - { text: '1 GB Media Storage', available: true }, - // { text: 'Unlimited Webhooks', available: true }, - // { text: 'Real-time Analytics', available: true }, - { text: '24/7 Dedicated Support', available: true }, - { text: 'Custom Avatars', available: true }, + { textKey: 'free.features.unlimited_messages', available: true }, + { textKey: 'free.features.large_storage', count: 1, available: true }, + { textKey: 'free.features.dedicated_support', available: true }, + { textKey: 'free.features.custom_avatars', available: true }, ], - cta: 'Upgrade', + ctaKey: 'premium.cta_upgrade', ctaLink: '#', badgeClass: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30 shadow-lg shadow-yellow-500/20', }, ]; + // Helper function to render a feature text using the translation key and count + const renderFeatureText = (feature: any) => { + // The feature text is determined by looking up the key and applying the count placeholder if it exists. + if (feature.count !== undefined) { + // We use tPlan since the keys start from 'plansPage.plans.free.features' + return tPlan(feature.textKey, { count: feature.count }); + } + return tPlan(feature.textKey); + }; + return (
+ {/* Translate Title */}

- Subscription Plans + {t('title')}

-

- Choose the plan that best fits your needs. -

+ {/* Translate Subtitle */} +

{t('subtitle')}

{plans.map(plan => ( } >
@@ -102,7 +110,8 @@ export default function PlansPage() { - {plan.name.toUpperCase()} + {/* Translate and uppercase Name */} + {tPlan(`${plan.key}.name`).toUpperCase()}
@@ -110,7 +119,8 @@ export default function PlansPage() { {plan.features.map((feature, index) => ( ))} @@ -121,7 +131,8 @@ export default function PlansPage() { asChild className="w-full bg-purple-600 hover:bg-purple-700 text-white" > - {plan.cta} + {/* Translate CTA */} + {tPlan(plan.ctaKey)}
diff --git a/apps/web/src/app/dashboard/send/loading.tsx b/apps/web/src/app/[locale]/dashboard/send/loading.tsx similarity index 100% rename from apps/web/src/app/dashboard/send/loading.tsx rename to apps/web/src/app/[locale]/dashboard/send/loading.tsx diff --git a/apps/web/src/app/dashboard/send/page.tsx b/apps/web/src/app/[locale]/dashboard/send/page.tsx similarity index 81% rename from apps/web/src/app/dashboard/send/page.tsx rename to apps/web/src/app/[locale]/dashboard/send/page.tsx index c3be5c2..deebef8 100644 --- a/apps/web/src/app/dashboard/send/page.tsx +++ b/apps/web/src/app/[locale]/dashboard/send/page.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { useState, useEffect } from 'react'; import { useSearchParams, useRouter, usePathname } from 'next/navigation'; +import { useTranslations } from 'next-intl'; import { Select, SelectContent, @@ -29,10 +30,12 @@ import { useToast } from '@/hooks/use-toast'; import { SendMessageData } from '@/lib/api/types/webhook'; import { DISCORD_MAX_MESSAGE_LENGTH } from '@/constants/discord'; import Link from 'next/link'; -import { EmbedBuilder } from '../../../components/embed-builder'; +import { EmbedBuilder } from '../../../../components/embed-builder'; import { ApiError } from '@/lib/error'; export default function SendMessagePage() { + // 2. Initialize the translation function for the "sendMessagePage" namespace + const t = useTranslations('dashboard.sendMessagePage'); const { toast } = useToast(); const searchParams = useSearchParams(); const router = useRouter(); @@ -174,7 +177,8 @@ export default function SendMessagePage() { toast({ variant: 'destructive', title: 'Error', - description: 'Please select at least one webhook', + // 3. Translate errorSelect + description: t('errorSelect'), }); return; } @@ -183,7 +187,8 @@ export default function SendMessagePage() { toast({ variant: 'destructive', title: 'Error', - description: 'Please enter a message or add an embed', + // 4. Translate errorContent + description: t('errorContent'), }); return; } @@ -208,13 +213,14 @@ export default function SendMessagePage() { toast({ variant: 'success', title: 'Success', - description: `Message sent successfully to ${selectedWebhooks.length} webhook${selectedWebhooks.length > 1 ? 's' : ''}`, + // 5. Use translation with count pluralization for success message + description: t('success', { count: selectedWebhooks.length }), }); } else { toast({ variant: 'destructive', title: 'Error', - description: response.message || 'Failed to send message', + description: response.message || t('errorGeneral'), // 6. Use fallback translation }); } } catch (error) { @@ -234,6 +240,7 @@ export default function SendMessagePage() { className="text-blue-400 hover:underline" onClick={() => toastResponse.dismiss()} > + {/* Note: This specific link text 'Check your usage in settings.' is not in the provided translation object, so it remains hardcoded or requires a new key */} Check your usage in settings. @@ -243,14 +250,15 @@ export default function SendMessagePage() { toast({ variant: 'destructive', title: 'Error', - description: error.message || 'An unexpected error occurred', + description: error.message || t('errorGeneral'), }); } } else { toast({ variant: 'destructive', title: 'Error', - description: 'something went wrong', + // 7. Translate general error + description: t('errorGeneral'), }); } } finally { @@ -264,10 +272,10 @@ export default function SendMessagePage() { {/* Header */}
-

Send Message

-

- Send messages immediately to one or multiple webhooks -

+ {/* 8. Translate title */} +

{t('title')}

+ {/* 9. Translate subtitle */} +

{t('subtitle')}

@@ -287,7 +297,8 @@ export default function SendMessagePage() { {/* Message Composer */} - Compose Message + {/* 12. Translate Card Title */} + {t('composeMessage')}
@@ -298,7 +309,8 @@ export default function SendMessagePage() { htmlFor="template-select" className="text-slate-200" > - Load Template + {/* 13. Translate loadTemplate */} + {t('loadTemplate')}