diff --git a/declarations/i18next.d.ts b/declarations/i18next.d.ts
index 8fdf4fd0e..a25acbd1b 100644
--- a/declarations/i18next.d.ts
+++ b/declarations/i18next.d.ts
@@ -2,6 +2,7 @@ import "react-i18next";
import app from "~/public/locales/en/app.json";
import common from "~/public/locales/en/common.json";
+import errors from "~/public/locales/en/errors.json";
import homepage from "~/public/locales/en/homepage.json";
declare module "next-i18next" {
@@ -9,5 +10,6 @@ declare module "next-i18next" {
homepage: typeof homepage;
app: typeof app;
common: typeof common;
+ errors: typeof errors;
}
}
diff --git a/next-i18next.config.js b/next-i18next.config.js
index 09f3cfbd8..34fb8cdc8 100644
--- a/next-i18next.config.js
+++ b/next-i18next.config.js
@@ -3,7 +3,7 @@ const path = require("path");
module.exports = {
i18n: {
defaultLocale: "en",
- locales: ["en", "de", "fr", "sv"],
+ locales: ["en", "es", "de", "fr", "sv"],
localePath: path.resolve("./public/locales"),
reloadOnPrerender: process.env.NODE_ENV === "development",
},
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index fbb0bfc38..cf129b88d 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -17,5 +17,9 @@
"starOnGithub": "Star us on Github",
"support": "Support",
"swedish": "Swedish",
- "volunteerTranslator": "Help translate this site"
+ "volunteerTranslator": "Help translate this site",
+ "notFoundTitle": "404 not found",
+ "notFoundDescription": "We couldn't find the page you're looking for.",
+ "goToHome": "Go to home",
+ "startChat": "Start chat"
}
diff --git a/public/locales/en/errors.json b/public/locales/en/errors.json
new file mode 100644
index 000000000..b267ef1db
--- /dev/null
+++ b/public/locales/en/errors.json
@@ -0,0 +1,6 @@
+{
+ "notFoundTitle": "404 not found",
+ "notFoundDescription": "We couldn't find the page you're looking for.",
+ "goToHome": "Go to home",
+ "startChat": "Start chat"
+}
diff --git a/public/locales/es/app.json b/public/locales/es/app.json
new file mode 100644
index 000000000..9e472a68b
--- /dev/null
+++ b/public/locales/es/app.json
@@ -0,0 +1,128 @@
+{
+ "12h": "12h",
+ "24h": "24h",
+ "addParticipant": "Añadir participante",
+ "addTimeOption": "Añadir hora",
+ "alreadyVoted": "Ya has votado",
+ "applyToAllDates": "Aplicar a todas las fechas",
+ "areYouSure": "¿Estás seguro?",
+ "back": "Volver",
+ "calendarHelp": "No puedes crear una encuesta sin ninguna opción. Añada al menos una opción para continuar.",
+ "calendarHelpTitle": "¿Olvidaste algo?",
+ "cancel": "Cancelar",
+ "comment": "Comentario",
+ "commentPlaceholder": "Dejar un comentario en esta encuesta (visible para todos)",
+ "comments": "Comentarios",
+ "continue": "Continuar",
+ "copied": "Copiado",
+ "copyLink": "Copiar enlace",
+ "createdBy": "de {{name}}",
+ "createPoll": "Crear encuesta",
+ "creatingDemo": "Creando una encuesta de demostración…",
+ "delete": "Borrar",
+ "deleteComment": "Borrar comentario",
+ "deleteDate": "Borrar fecha",
+ "deletedPoll": "Encuesta borrada",
+ "deletedPollInfo": "Esta encuesta ya no existe.",
+ "deletePoll": "Borrar encuesta",
+ "deletePollDescription": "Todos los datos relacionados con esta encuesta se eliminarán. Para confirmar, por favor escribe “{{confirmText}}” en el campo siguiente:",
+ "deletingOptionsWarning": "Estás eliminando opciones por las que algunos participantes han votado. También se eliminarán sus votos.",
+ "demoPollNotice": "Las encuestas de demostración se eliminan automáticamente después de 1 día",
+ "description": "Descripción",
+ "descriptionPlaceholder": "¡Hola a todos, por favor elijan las fechas que les convengan!",
+ "donate": "Donar",
+ "editDetails": "Editar detalles",
+ "editOptions": "Editar opciones",
+ "email": "Correo electrónico",
+ "emailPlaceholder": "jessie.smith@email.com",
+ "endingGuestSessionNotice": "Una vez que finalices la sesión de visitante, no se puede reanudar. No podrás editar ningún voto o comentario que hayas hecho con esta sesión.",
+ "endSession": "Cerrar Sesión",
+ "errorCreate": "¡Oh! Hubo un problema al crear tu encuesta. El error ha sido registrado y vamos a intentar arreglarlo.",
+ "exportToCsv": "Exportar a CSV",
+ "finish": "Finalizar",
+ "forgetMe": "Olvídame",
+ "goToAdmin": "Ir al panel de administración",
+ "guest": "Visitante",
+ "guestSessionNotice": "Estás usando una sesión de visitante. Esto nos permite reconocerte si vuelves más tarde para que puedas editar tus votos.",
+ "guestSessionReadMore": "Lee más sobre sesiones de visitantes.",
+ "hide": "Ocultar",
+ "ifNeedBe": "Si es necesario",
+ "linkHasExpired": "Tu enlace ha expirado o ya no es válido",
+ "loading": "Cargando…",
+ "loadingParticipants": "Cargando participantes…",
+ "location": "Ubicación",
+ "locationPlaceholder": "Café de Carlos",
+ "lockPoll": "Bloquear encuesta",
+ "login": "Iniciar sesión",
+ "loginCheckInbox": "Por favor, revisa tus correos electrónicos.",
+ "loginMagicLinkSent": "Se ha enviado un enlace mágico a:",
+ "loginSendMagicLink": "Enviarme un enlace mágico",
+ "loginViaMagicLink": "Iniciar sesión a través de un enlace mágico",
+ "loginViaMagicLinkDescription": "Te enviaremos un correo electrónico con un enlace mágico que puedes usar para iniciar sesión.",
+ "loginWithValidEmail": "Por favor ingresa un correo electrónico válido",
+ "logout": "Cerrar sesión",
+ "manage": "Gestionar",
+ "menu": "Menú",
+ "mixedOptionsDescription": "No puedes tener opciones de fecha y opciones de hora en la misma encuesta. ¿Cuáles quieres mantener?",
+ "mixedOptionsKeepDates": "Mantener las opciones de fecha",
+ "mixedOptionsKeepTimes": "Mantener las opciones de hora",
+ "mixedOptionsTitle": "Aguanta un minuto…🤔",
+ "monday": "Lunes",
+ "monthView": "Vista mensual",
+ "name": "Nombre",
+ "namePlaceholder": "Jessie Smith",
+ "newPoll": "Nueva encuesta",
+ "next": "Siguiente",
+ "nextMonth": "Siguiente mes",
+ "no": "No",
+ "noDatesSelected": "Ninguna fecha seleccionada",
+ "notificationsDisabled": "Las notificaciones han sido desactivadas",
+ "notificationsOff": "Las notificaciones están desactivadas",
+ "notificationsOn": "Las notificaciones están activadas",
+ "notificationsOnDescription": "Vamos a mandar un correo electrónico a {{email}} cuando haya actividad en esta encuesta.",
+ "notificationsVerifyEmail": "Tienes que verificar tu correo electrónico para activar las notificaciones",
+ "ok": "Aceptar",
+ "options": "Opciones",
+ "participant": "Participante",
+ "participantCount_other": "{{count}} participantes",
+ "participantCount": "{{count}} participante",
+ "pollHasBeenLocked": "Esta encuesta ha sido bloqueada",
+ "pollHasBeenVerified": "Esta encuesta ha sido verificada",
+ "pollOwnerNotice": "Hola {{name}}, parece que eres el dueño de esta encuesta.",
+ "pollsEmpty": "Ninguna encuesta creada",
+ "possibleAnswers": "Respuestas posibles",
+ "preferences": "Ajustes",
+ "previousMonth": "Mes anterior",
+ "profileLogin": "Perfil - Iniciar sesión",
+ "profileUser": "Perfil - {{username}}",
+ "requiredNameError": "El nombre es obligatorio",
+ "save": "Guardar",
+ "saveInstruction": "Selecciona tu disponibilidad y haz clic en {{save}}",
+ "share": "Compartir",
+ "shareDescription": "Dale este enlace a tus participantes para permitirles votar en tu encuesta.",
+ "shareLink": "Compartir con un enlace",
+ "specifyTimes": "Especificar tiempos",
+ "specifyTimesDescription": "Incluir horas de inicio y fin para cada opción",
+ "stepSummary": "Paso {{current}} de {{total}}",
+ "sunday": "Domingo",
+ "timeAndDate": "Fecha y hora",
+ "timeFormat": "Formato de hora:",
+ "timeZone": "Zona horaria:",
+ "title": "Título",
+ "titlePlaceholder": "Reunión mensual",
+ "today": "Hoy",
+ "unlockPoll": "Desbloquear encuesta",
+ "unverifiedMessage": "Se ha enviado un correo electrónico a {{email}} con un enlace para verificar la dirección de correo electrónico.",
+ "user": "Usuario",
+ "vote": "Votar",
+ "voteCount_other": "{{count}} votos",
+ "voteCount": "{{count}} voto",
+ "weekStartsOn": "La semana comienza el",
+ "weekView": "Vista semanal",
+ "whatsThis": "¿Qué es esto?",
+ "yes": "Sí",
+ "you": "Tú",
+ "yourDetails": "Tus datos",
+ "yourName": "Tu nombre…",
+ "yourPolls": "Tus encuestas"
+}
diff --git a/public/locales/es/common.json b/public/locales/es/common.json
new file mode 100644
index 000000000..1eda1e7df
--- /dev/null
+++ b/public/locales/es/common.json
@@ -0,0 +1,21 @@
+{
+ "blog": "Blog",
+ "discussions": "Discusiones",
+ "donate": "Donar",
+ "english": "Inglés",
+ "footerCredit": "Creado por @imlukevella",
+ "footerSponsor": "Este proyecto está financiado por los usuarios. Por favor, considera apoyarlo donando.",
+ "french": "Francés",
+ "german": "Alemán",
+ "home": "Inicio",
+ "italian": "Italiano",
+ "language": "Idioma",
+ "links": "Enlaces",
+ "poweredBy": "Con tecnología de",
+ "privacyPolicy": "Política de privacidad",
+ "spanish": "Español",
+ "starOnGithub": "Seguir el proyecto en GitHub",
+ "support": "Soporte",
+ "swedish": "Sueco",
+ "volunteerTranslator": "Ayuda a traducir esta página"
+}
diff --git a/public/locales/es/homepage.json b/public/locales/es/homepage.json
new file mode 100644
index 000000000..a95b29fe9
--- /dev/null
+++ b/public/locales/es/homepage.json
@@ -0,0 +1,36 @@
+{
+ "3Ls": "Sí—con 3 Ls",
+ "adFree": "Sin anuncios",
+ "adFreeDescription": "Puedes darle un descanso a tu bloqueador de anuncios — No lo necesitarás aquí.",
+ "comments": "Comentarios",
+ "commentsDescription": "Los participantes pueden comentar en tu encuesta y los comentarios serán visibles para todos.",
+ "features": "Características",
+ "featuresSubheading": "Programar reuniones, de una manera inteligente",
+ "follow": "Seguir",
+ "getStarted": "Empezar",
+ "heroSubText": "Encuentra la fecha correcta sin dar vueltas",
+ "heroText": "Programar reuniones con facilidad",
+ "links": "Enlaces",
+ "liveDemo": "Demostración en vivo",
+ "metaDescription": "Crea encuestas y vota para encontrar el mejor día o la mejor hora. Una alternativa gratuita a Doodle.",
+ "metaTitle": "Rallly - Programar reuniones",
+ "mobileFriendly": "Optimizado para dispositivos móviles",
+ "mobileFriendlyDescription": "Funciona muy bien en dispositivos móviles para que los participantes puedan responder a las encuestas dondequiera que estén.",
+ "new": "Nuevo",
+ "noLoginRequired": "No es necesario iniciar sesión",
+ "noLoginRequiredDescription": "No es necesario iniciar sesión para crear o participar en una encuesta",
+ "notifications": "Notificaciones",
+ "notificationsDescription": "Sabe quién ha respondido. Recibe notificaciones cuando los participantes voten o comenten en tu encuesta.",
+ "openSource": "Código abierto",
+ "openSourceDescription": "Ese proyecto es completamente de código abierto y disponible en GitHub.",
+ "participant": "Participante",
+ "participantCount_other": "{{count}} participantes",
+ "participantCount": "{{count}} participante",
+ "perfect": "¡Perfecto!",
+ "principles": "Principios",
+ "principlesSubheading": "No somos como los otros",
+ "selfHostable": "Autoalojable",
+ "selfHostableDescription": "Ejecútalo en tu propio servidor para tener el control total de tus datos",
+ "timeSlots": "Intervalos de tiempo",
+ "timeSlotsDescription": "Establece la hora de inicio y fin individual para cada opción de tu encuesta. Los tiempos se pueden ajustar automáticamente a la zona horaria de cada participante o pueden ser ajustados para ignorar completamente las zonas horarias."
+}
diff --git a/src/components/discussion/discussion.tsx b/src/components/discussion/discussion.tsx
index 2710524ac..20213bb69 100644
--- a/src/components/discussion/discussion.tsx
+++ b/src/components/discussion/discussion.tsx
@@ -1,11 +1,11 @@
import clsx from "clsx";
-import dayjs from "dayjs";
import { AnimatePresence, motion } from "framer-motion";
import { useTranslation } from "next-i18next";
import { usePlausible } from "next-plausible";
import * as React from "react";
import { Controller, useForm } from "react-hook-form";
+import { useDayjs } from "../../utils/dayjs";
import { requiredString } from "../../utils/form-validation";
import { trpc } from "../../utils/trpc";
import { Button } from "../button";
@@ -25,6 +25,7 @@ interface CommentForm {
}
const Discussion: React.VoidFunctionComponent = () => {
+ const { dayjs } = useDayjs();
const queryClient = trpc.useContext();
const { t } = useTranslation("app");
const { poll } = usePoll();
diff --git a/src/components/error-page.tsx b/src/components/error-page.tsx
index 402c3424b..c43f07ba7 100644
--- a/src/components/error-page.tsx
+++ b/src/components/error-page.tsx
@@ -1,5 +1,6 @@
import Head from "next/head";
import Link from "next/link";
+import { useTranslation } from "next-i18next";
import * as React from "react";
import { Button } from "@/components/button";
@@ -19,6 +20,7 @@ const ErrorPage: React.VoidFunctionComponent = ({
title,
description,
}) => {
+ const { t } = useTranslation("errors");
return (