diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json new file mode 100644 index 0000000..d5e749b --- /dev/null +++ b/apps/web/messages/en.json @@ -0,0 +1,530 @@ +{ + "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": { + "loading": { + "sessionCheck": "Checking your session..." + }, + "header": { + "title": "Welcome Back", + "subtitle": "Sign in to your account using email, Google, or Discord." + }, + "oauth": { + "discord": "Continue with Discord", + "google": "Continue with Google", + "loading": "Signing in...", + "discordAlt": "Sign in with Discord", + "googleAlt": "Sign in with Google", + "loadingAlt": "Signing in..." + }, + "divider": { + "or": "OR" + }, + "form": { + "emailLabel": "Email Address", + "emailPlaceholder": "Enter your email", + "passwordLabel": "Password", + "passwordPlaceholder": "Enter your password", + "passwordShow": "Show password", + "passwordHide": "Hide password", + "buttonContinue": "Continue", + "buttonSignIn": "Sign In", + "buttonSigningIn": "Signing In...", + "errorInvalidEmail": "Please enter a valid email address.", + "errorShortPassword": "Password must be at least 8 characters long.", + "errorSignInFailed": "Sign-in failed. Please check your credentials and try again." + }, + "footer": { + "noAccount": "Don't have an account?", + "signUp": "Sign up" + } + }, + "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, plural, one {# embed} other {# embeds}}", + "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": "Account Settings", + "subtitle": "Manage your profile, authentication, and usage limits.", + "accountType": { + "title": "Account Type", + "desc": "Check your current plan and features.", + "error": "Could not load account type." + }, + "accountQuotes": { + "free": "The perfect starting point for managing your webhooks.", + "paid": "More power and higher limits for growing needs.", + "premium": "Maximum power for the most demanding users and projects." + }, + "usage": { + "title": "Usage", + "desc": "Track your current usage and limits", + "webhookMessages": "Webhook Messages Sent (Daily)", + "mediaStorage": "Media Storage Used", + "error": "Could not load usage data." + }, + "plans": { + "title": "Manage Plans", + "desc": "Upgrade, downgrade, or cancel your current subscription.", + "button": "View Plans" + }, + "support": { + "title": "Support", + "desc": "Share your love for the app or get in touch for support.", + "message": "We love hearing feedback! If you find the app helpful, consider sharing your positive experience on Twitter.", + "dm": "For support or bug reports, please DM @ctrix on Twitter.", + "button": "Share on X (Twitter)" + }, + "clearData": { + "title": "Clear Local Data", + "desc": "This will clear all local storage data, including your preferences and cached data. This is useful for troubleshooting. You will be logged out.", + "confirm": "Clear Data and Reload" + }, + "auth": { + "password": { + "title": "Password", + "descCreate": "Create a password to enable email login", + "descChange": "Change your account password", + "buttonCreate": "Create Password", + "buttonChange": "Change Password" + }, + "discord": { + "title": "Discord Account", + "descLinked": "Your Discord account is linked", + "descLink": "Link your Discord account for OAuth login", + "statusConnected": "Connected", + "idLabel": "Discord ID", + "usernameLabel": "Discord Username", + "buttonLink": "Link Discord Account" + } + } + }, + "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" + } + } + }, + "avatarCard": { + "actions": { + "edit": "Edit", + "copyUrl": "Copy Avatar URL", + "delete": "Delete", + "select": "Select Avatar" + }, + "info": { + "created": "Created: {date}" + }, + "toast": { + "deleteSuccessTitle": "Avatar deleted", + "deleteSuccessDescription": "Avatar deleted successfully", + "deleteErrorTitle": "Error deleting avatar", + "copySuccessTitle": "Avatar URL copied", + "copySuccessDescription": "Avatar URL copied to clipboard" + }, + "deleteDialog": { + "title": "Delete Avatar", + "description": "Are you sure you want to delete \"{username}\"? This action cannot be undone.", + "cancel": "Cancel", + "confirm": "Delete" + } + }, + "avatarSelector": { + "title": "Select Predefined Avatar", + "searchPlaceholder": "Search avatars...", + "addButton": "Add", + "emptyState": { + "noAvatars": "No predefined avatars yet", + "noResults": "No avatars found" + } + }, + "createAvatarDialog": { + "title": { + "create": "Create New Avatar", + "edit": "Edit Avatar" + }, + "form": { + "usernameLabel": "Username", + "usernamePlaceholder": "e.g., BotHelper, Announcer", + "avatarIconLabel": "Avatar Icon", + "urlPlaceholder": "https://example.com/avatar.png", + "browse": "Browse" + }, + "actions": { + "cancel": "Cancel", + "create": "Create Avatar", + "update": "Update Avatar", + "creating": "Creating...", + "updating": "Updating..." + }, + "toast": { + "createSuccessTitle": "Avatar created", + "createSuccessDescription": "Avatar created successfully", + "updateSuccessTitle": "Avatar updated", + "updateSuccessDescription": "Avatar updated successfully", + "errorTitle": "Error", + "unexpectedError": "An unexpected error occurred", + "limitReachedTitle": "Limit Reached", + "mediaLimitLink": "Check your usage in settings." + } + }, + "themeSelector": { + "title": "Theme", + "description": "Choose your preferred theme or let the system decide", + "themes": { + "light": "Light", + "dark": "Dark", + "system": "System" + } + }, + "addWebhookDialog": { + "triggerButton": "Add Webhook", + "title": "Add New Webhook", + "description": "Add a Discord webhook to start sending messages. You can find webhook URLs in your Discord server settings.", + "form": { + "nameLabel": "Webhook Name", + "namePlaceholder": "My Discord Webhook", + "urlLabel": "Webhook URL", + "urlPlaceholder": "https://discord.com/api/webhooks/...", + "descriptionLabel": "Description (Optional)", + "descriptionPlaceholder": "What is this webhook used for?" + }, + "actions": { + "cancel": "Cancel", + "add": "Add Webhook", + "adding": "Adding..." + } + }, + "dashboardHeader": { + "title": "Webhook Manager", + "subtitle": "Discord Integration", + "languageSelector": "Lang", + "accountType": { + "free": "Free", + "paid": "Paid", + "premium": "Premium" + }, + "accountTypeTooltip": { + "free": "Explore the basics, unlock your potential.", + "paid": "Elevate your experience, achieve more.", + "premium": "Unleash the ultimate power, no limits." + }, + "menu": { + "settings": "Settings", + "logout": "Log out" + } + }, + "embedBuilder": { + "header": { + "title": "Discord Embeds", + "subtitle": "Add rich embeds to your message (max {max})", + "addButton": "Add Embed" + }, + "emptyState": { + "noEmbeds": "No embeds added yet", + "callToAction": "Click \"Add Embed\" to create rich message content" + }, + "embedItem": { + "titleLabel": "Embed {index}", + "removeButton": "Remove", + "fields": { + "title": "Title", + "titlePlaceholder": "Embed title", + "url": "URL", + "urlPlaceholder": "Embed URL", + "description": "Description", + "descriptionPlaceholder": "Embed description", + "color": "Color", + "image": "Image", + "imagePlaceholder": "Image URL", + "thumbnail": "Thumbnail", + "thumbnailPlaceholder": "Thumbnail URL", + "timestamp": "Timestamp" + }, + "author": { + "title": "Author", + "selectButton": "Select Avatar", + "clearButton": "Clear Author", + "manualName": "Name", + "manualNamePlaceholder": "Author name", + "manualIconUrl": "Icon URL", + "manualIconUrlPlaceholder": "Author icon URL", + "manualUrl": "URL", + "manualUrlPlaceholder": "Author URL" + }, + "fieldBuilder": { + "title": "Fields", + "addButton": "Add Field", + "fieldLabel": "Field {index}", + "removeButton": "Remove", + "name": "Name", + "namePlaceholder": "Field name", + "value": "Value", + "valuePlaceholder": "Field value", + "inline": "Inline" + }, + "footer": { + "title": "Footer", + "text": "Text", + "textPlaceholder": "Footer text", + "iconUrl": "Icon URL", + "iconUrlPlaceholder": "Footer icon URL" + } + } + }, + "templateForm": { + "tabs": { + "info": "Template Info", + "message": "Message", + "embeds": "Embeds" + }, + "infoTab": { + "header": "Template Information", + "nameLabel": "Template Name", + "namePlaceholder": "My Awesome Template", + "descriptionLabel": "Description (Optional)", + "descriptionPlaceholder": "What is this template for? Describe its purpose..." + }, + "messageTab": { + "contentHeader": "Message Content", + "contentLabel": "Message Text", + "contentPlaceholder": "Enter your message content here... (Max 2000 characters)", + "characterCount": "{current}/2000 characters", + "avatarHeader": "Message Avatar", + "avatarSelectButton": "Select Avatar" + } + }, + "common": { + "delete": "Eliminar", + "areYouSure": "¿Estás seguro?" + }, + "webhookCard": { + "status": { + "active": "Activo", + "inactive": "Inactivo" + }, + "details": { + "description": "Descripción:", + "messagesSent": "Mensajes enviados:", + "created": "Creado:", + "lastUsed": "Último uso:", + "todoPlaceholder": "PENDIENTE" + }, + "actions": { + "testWebhook": "Probar Webhook", + "testing": "Probando...", + "deactivate": "Desactivar", + "activate": "Activar", + "delete": "Eliminar" + }, + "dialog": { + "title": "¿Estás seguro?", + "description": "Esta acción no se puede deshacer. Esto eliminará permanentemente el webhook \"{webhookName}\" de tu cuenta." + }, + "toasts": { + "updateSuccessTitle": "Webhook actualizado", + "updateSuccessDesc": "Webhook actualizado correctamente", + "updateErrorTitle": "Error al actualizar el webhook", + "deleteSuccessTitle": "Webhook eliminado", + "deleteSuccessDesc": "El webhook ha sido eliminado de tu cuenta", + "testSuccessTitle": "¡Prueba exitosa!", + "testSuccessDesc": "Mensaje de prueba enviado a Discord", + "testFailTitle": "Prueba fallida", + "testFailGenericDesc": "Ocurrió un error al probar el webhook" + } + } +} + diff --git a/apps/web/messages/es.json b/apps/web/messages/es.json new file mode 100644 index 0000000..0bbd5d3 --- /dev/null +++ b/apps/web/messages/es.json @@ -0,0 +1,531 @@ +{ + "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": { + "loading": { + "sessionCheck": "Verificando tu sesión..." + }, + "header": { + "title": "Bienvenido de nuevo", + "subtitle": "Inicia sesión en tu cuenta usando correo electrónico, Google o Discord." + }, + "oauth": { + "discord": "Continuar con Discord", + "google": "Continuar con Google", + "loading": "Iniciando sesión...", + "discordAlt": "Iniciar sesión con Discord", + "googleAlt": "Iniciar sesión con Google", + "loadingAlt": "Iniciando sesión..." + }, + "divider": { + "or": "O" + }, + "form": { + "emailLabel": "Correo electrónico", + "emailPlaceholder": "Introduce tu correo electrónico", + "passwordLabel": "Contraseña", + "passwordPlaceholder": "Introduce tu contraseña", + "passwordShow": "Mostrar contraseña", + "passwordHide": "Ocultar contraseña", + "buttonContinue": "Continuar", + "buttonSignIn": "Iniciar sesión", + "buttonSigningIn": "Iniciando sesión...", + "errorInvalidEmail": "Por favor, introduce un correo electrónico válido.", + "errorShortPassword": "La contraseña debe tener al menos 8 caracteres.", + "errorSignInFailed": "Error al iniciar sesión. Verifica tus credenciales e inténtalo de nuevo." + }, + "footer": { + "noAccount": "¿No tienes una cuenta?", + "signUp": "Regístrate" + } + }, + "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": "Buscar 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, plural, one {# incrustación} other {# incrustaciones}}", + "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 de la Cuenta", + "subtitle": "Administra tu perfil, autenticación y límites de uso.", + "accountType": { + "title": "Tipo de Cuenta", + "desc": "Consulta tu plan actual y las funciones disponibles.", + "error": "No se pudo cargar el tipo de cuenta." + }, + "accountQuotes": { + "free": "El punto de partida perfecto para gestionar tus webhooks.", + "paid": "Más potencia y límites superiores para necesidades crecientes.", + "premium": "Máxima potencia para los usuarios y proyectos más exigentes." + }, + "usage": { + "title": "Uso", + "desc": "Realiza un seguimiento de tu uso actual y límites", + "webhookMessages": "Mensajes de Webhook Enviados (Diarios)", + "mediaStorage": "Almacenamiento de Medios Utilizado", + "error": "No se pudieron cargar los datos de uso." + }, + "plans": { + "title": "Gestionar Planes", + "desc": "Actualiza, degrada o cancela tu suscripción actual.", + "button": "Ver Planes" + }, + "support": { + "title": "Soporte", + "desc": "Comparte tu aprecio por la aplicación o ponte en contacto para recibir soporte.", + "message": "¡Nos encanta recibir comentarios! Si la aplicación te resulta útil, considera compartir tu experiencia positiva en Twitter.", + "dm": "Para soporte o informes de errores, por favor envía un MD a @ctrix en Twitter.", + "button": "Compartir en X (Twitter)" + }, + "clearData": { + "title": "Borrar Datos Locales", + "desc": "Esto eliminará todos los datos de almacenamiento local, incluyendo tus preferencias y datos en caché. Es útil para la resolución de problemas. Se cerrará tu sesión.", + "confirm": "Borrar Datos y Recargar" + }, + "auth": { + "password": { + "title": "Contraseña", + "descCreate": "Crea una contraseña para habilitar el inicio de sesión por correo electrónico", + "descChange": "Cambia la contraseña de tu cuenta", + "buttonCreate": "Crear Contraseña", + "buttonChange": "Cambiar Contraseña" + }, + "discord": { + "title": "Cuenta de Discord", + "descLinked": "Tu cuenta de Discord está vinculada", + "descLink": "Vincula tu cuenta de Discord para iniciar sesión con OAuth", + "statusConnected": "Conectada", + "idLabel": "ID de Discord", + "usernameLabel": "Nombre de Usuario de Discord", + "buttonLink": "Vincular Cuenta de Discord" + } + } + }, + "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" + } + } + }, + "avatarCard": { + "actions": { + "edit": "Editar", + "copyUrl": "Copiar URL del Avatar", + "delete": "Eliminar", + "select": "Seleccionar Avatar" + }, + "info": { + "created": "Creado: {date}" + }, + "toast": { + "deleteSuccessTitle": "Avatar eliminado", + "deleteSuccessDescription": "Avatar eliminado con éxito", + "deleteErrorTitle": "Error al eliminar el avatar", + "copySuccessTitle": "URL del Avatar copiada", + "copySuccessDescription": "URL del Avatar copiada al portapapeles" + }, + "deleteDialog": { + "title": "Eliminar Avatar", + "description": "¿Estás seguro de que quieres eliminar \"{username}\"? Esta acción no se puede deshacer.", + "cancel": "Cancelar", + "confirm": "Eliminar" + } + }, + "avatarSelector": { + "title": "Seleccionar Avatar Predefinido", + "searchPlaceholder": "Buscar avatares...", + "addButton": "Añadir", + "emptyState": { + "noAvatars": "Aún no hay avatares predefinidos", + "noResults": "No se encontraron avatares" + } + }, + "createAvatarDialog": { + "title": { + "create": "Crear Nuevo Avatar", + "edit": "Editar Avatar" + }, + "form": { + "usernameLabel": "Nombre de Usuario", + "usernamePlaceholder": "Ejemplo: BotHelper, Anunciador", + "avatarIconLabel": "Icono de Avatar", + "urlPlaceholder": "https://ejemplo.com/avatar.png", + "browse": "Examinar" + }, + "actions": { + "cancel": "Cancelar", + "create": "Crear Avatar", + "update": "Actualizar Avatar", + "creating": "Creando...", + "updating": "Actualizando..." + }, + "toast": { + "createSuccessTitle": "Avatar creado", + "createSuccessDescription": "El avatar se creó con éxito", + "updateSuccessTitle": "Avatar actualizado", + "updateSuccessDescription": "El avatar se actualizó con éxito", + "errorTitle": "Error", + "unexpectedError": "Ocurrió un error inesperado", + "limitReachedTitle": "Límite Alcanzado", + "mediaLimitLink": "Verifica tu uso en la configuración." + } + }, + "themeSelector": { + "title": "Tema", + "description": "Elige tu tema preferido o deja que el sistema decida", + "themes": { + "light": "Claro", + "dark": "Oscuro", + "system": "Sistema" + } + }, + "addWebhookDialog": { + "triggerButton": "Añadir Webhook", + "title": "Añadir Nuevo Webhook", + "description": "Añade un webhook de Discord para empezar a enviar mensajes. Puedes encontrar las URL de los webhooks en la configuración de tu servidor de Discord.", + "form": { + "nameLabel": "Nombre del Webhook", + "namePlaceholder": "Mi Webhook de Discord", + "urlLabel": "URL del Webhook", + "urlPlaceholder": "https://discord.com/api/webhooks/...", + "descriptionLabel": "Descripción (Opcional)", + "descriptionPlaceholder": "¿Para qué se utiliza este webhook?" + }, + "actions": { + "cancel": "Cancelar", + "add": "Añadir Webhook", + "adding": "Añadiendo..." + } + }, + "dashboardHeader": { + "title": "Gestor de Webhooks", + "subtitle": "Integración con Discord", + "languageSelector": "Idioma", + "accountType": { + "free": "Gratuito", + "paid": "Pago", + "premium": "Premium" + }, + "accountTypeTooltip": { + "free": "Explora lo básico, desbloquea tu potencial.", + "paid": "Mejora tu experiencia, logra más.", + "premium": "Desata el poder definitivo, sin límites." + }, + "menu": { + "settings": "Configuración", + "logout": "Cerrar sesión" + } + }, + "embedBuilder": { + "header": { + "title": "Embeds de Discord", + "subtitle": "Añade embeds enriquecidos a tu mensaje (máx. {max})", + "addButton": "Añadir Embed" + }, + "emptyState": { + "noEmbeds": "Aún no se han añadido embeds", + "callToAction": "Haz clic en \"Añadir Embed\" para crear contenido de mensaje enriquecido" + }, + "embedItem": { + "titleLabel": "Embed {index}", + "removeButton": "Eliminar", + "fields": { + "title": "Título", + "titlePlaceholder": "Título del embed", + "url": "URL", + "urlPlaceholder": "URL del embed", + "description": "Descripción", + "descriptionPlaceholder": "Descripción del embed", + "color": "Color", + "image": "Imagen", + "imagePlaceholder": "URL de la imagen", + "thumbnail": "Miniatura", + "thumbnailPlaceholder": "URL de la miniatura", + "timestamp": "Marca de tiempo" + }, + "author": { + "title": "Autor", + "selectButton": "Seleccionar Avatar", + "clearButton": "Borrar Autor", + "manualName": "Nombre", + "manualNamePlaceholder": "Nombre del autor", + "manualIconUrl": "URL del Icono", + "manualIconUrlPlaceholder": "URL del icono del autor", + "manualUrl": "URL", + "manualUrlPlaceholder": "URL del autor" + }, + "fieldBuilder": { + "title": "Campos", + "addButton": "Añadir Campo", + "fieldLabel": "Campo {index}", + "removeButton": "Eliminar", + "name": "Nombre", + "namePlaceholder": "Nombre del campo", + "value": "Valor", + "valuePlaceholder": "Valor del campo", + "inline": "En línea" + }, + "footer": { + "title": "Pie de página", + "text": "Texto", + "textPlaceholder": "Texto del pie de página", + "iconUrl": "URL del Icono", + "iconUrlPlaceholder": "URL del icono del pie de página" + } + } + }, + "templateForm": { + "tabs": { + "info": "Información de la Plantilla", + "message": "Mensaje", + "embeds": "Embeds" + }, + "infoTab": { + "header": "Información de la Plantilla", + "nameLabel": "Nombre de la Plantilla", + "namePlaceholder": "Mi Plantilla Genial", + "descriptionLabel": "Descripción (Opcional)", + "descriptionPlaceholder": "¿Para qué sirve esta plantilla? Describe su propósito...", + "livePreviewHeader": "Vista Previa en Vivo" + }, + "messageTab": { + "contentHeader": "Contenido del Mensaje", + "contentLabel": "Texto del Mensaje", + "contentPlaceholder": "Introduce el contenido de tu mensaje aquí... (Máx. 2000 caracteres)", + "characterCount": "{current}/2000 caracteres", + "avatarHeader": "Avatar del Mensaje", + "avatarSelectButton": "Seleccionar Avatar" + } + }, + "common": { + "delete": "Eliminar", + "areYouSure": "¿Estás seguro?" + }, + "webhookCard": { + "status": { + "active": "Activo", + "inactive": "Inactivo" + }, + "details": { + "description": "Descripción:", + "messagesSent": "Mensajes enviados:", + "created": "Creado:", + "lastUsed": "Último uso:", + "todoPlaceholder": "PENDIENTE" + }, + "actions": { + "testWebhook": "Probar Webhook", + "testing": "Probando...", + "deactivate": "Desactivar", + "activate": "Activar", + "delete": "Eliminar" + }, + "dialog": { + "title": "¿Estás seguro?", + "description": "Esta acción no se puede deshacer. Esto eliminará permanentemente el webhook \"{webhookName}\" de tu cuenta." + }, + "toasts": { + "updateSuccessTitle": "Webhook actualizado", + "updateSuccessDesc": "Webhook actualizado correctamente", + "updateErrorTitle": "Error al actualizar el webhook", + "deleteSuccessTitle": "Webhook eliminado", + "deleteSuccessDesc": "El webhook ha sido eliminado de tu cuenta", + "testSuccessTitle": "¡Prueba exitosa!", + "testSuccessDesc": "Mensaje de prueba enviado a Discord", + "testFailTitle": "Prueba fallida", + "testFailGenericDesc": "Ocurrió un error al probar el webhook" + } + } +} + 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')}