mirror of
https://github.com/lukevella/rallly.git
synced 2025-05-23 13:56:18 +02:00
Locale update patch (#229)
This commit is contained in:
parent
e0a5cfec39
commit
f46c9b0348
12 changed files with 108 additions and 100 deletions
|
@ -5,7 +5,6 @@
|
||||||
"applyToAllDates": "Auf alle Termine anwenden",
|
"applyToAllDates": "Auf alle Termine anwenden",
|
||||||
"areYouSure": "Bist du sicher?",
|
"areYouSure": "Bist du sicher?",
|
||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
"blog": "Blog",
|
|
||||||
"calendarHelp": "Du kannst keine Umfrage ohne Optionen erstellen. Füge mindestens eine Option hinzu, um fortzufahren.",
|
"calendarHelp": "Du kannst keine Umfrage ohne Optionen erstellen. Füge mindestens eine Option hinzu, um fortzufahren.",
|
||||||
"calendarHelpTitle": "Irgendwas vergessen?",
|
"calendarHelpTitle": "Irgendwas vergessen?",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
|
@ -29,7 +28,6 @@
|
||||||
"demoPollNotice": "Demo-Umfragen werden automatisch nach einem Tag gelöscht",
|
"demoPollNotice": "Demo-Umfragen werden automatisch nach einem Tag gelöscht",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"descriptionPlaceholder": "Hallo ihr, bitte wählt alle Termine aus, die für euch passen!",
|
"descriptionPlaceholder": "Hallo ihr, bitte wählt alle Termine aus, die für euch passen!",
|
||||||
"discussions": "Diskussion",
|
|
||||||
"donate": "Spenden",
|
"donate": "Spenden",
|
||||||
"editDetails": "Details bearbeiten",
|
"editDetails": "Details bearbeiten",
|
||||||
"editOptions": "Optionen bearbeiten",
|
"editOptions": "Optionen bearbeiten",
|
||||||
|
@ -107,7 +105,6 @@
|
||||||
"specifyTimesDescription": "Start- und Endzeit für jede Option angeben",
|
"specifyTimesDescription": "Start- und Endzeit für jede Option angeben",
|
||||||
"stepSummary": "Schritt {{current}} von {{total}}",
|
"stepSummary": "Schritt {{current}} von {{total}}",
|
||||||
"sunday": "Sonntag",
|
"sunday": "Sonntag",
|
||||||
"support": "Hilfe",
|
|
||||||
"timeAndDate": "Datum & Uhrzeit",
|
"timeAndDate": "Datum & Uhrzeit",
|
||||||
"timeFormat": "Uhrzeitformat:",
|
"timeFormat": "Uhrzeitformat:",
|
||||||
"timeZone": "Zeitzone:",
|
"timeZone": "Zeitzone:",
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
{
|
{
|
||||||
"language": "Sprache",
|
"blog": "Blog",
|
||||||
|
"discussions": "Diskussion",
|
||||||
|
"donate": "Spenden",
|
||||||
"english": "Englisch",
|
"english": "Englisch",
|
||||||
|
"footerCredit": "Made by <a>@imlukevella</a>",
|
||||||
|
"footerSponsor": "This project is user-funded. Please consider supporting it by <a>donating</a>.",
|
||||||
"german": "Deutsch",
|
"german": "Deutsch",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"blog": "Blog",
|
"language": "Sprache",
|
||||||
"support": "Support",
|
"links": "Links",
|
||||||
"donate": "Donate",
|
"poweredBy": "Unterstützt von",
|
||||||
"volunteerTranslator": "Help translate this site",
|
"privacyPolicy": "Datenschutzerklärung",
|
||||||
"starOnGithub": "Star us on Github",
|
"starOnGithub": "Star us on Github",
|
||||||
"footerCredit": "Made by <a>@imlukevella</a>",
|
"support": "Hilfe",
|
||||||
"footerSponsor": "This project is user-funded. Please consider supporting it by <a>donating</a>."
|
"volunteerTranslator": "Help translate this site"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,8 @@
|
||||||
"3Ls": "Ja – mit 3 <e>L</e>s",
|
"3Ls": "Ja – mit 3 <e>L</e>s",
|
||||||
"adFree": "Ohne Werbung",
|
"adFree": "Ohne Werbung",
|
||||||
"adFreeDescription": "Gönn deinem Adblocker eine Pause - du brauchst ihn hier nicht.",
|
"adFreeDescription": "Gönn deinem Adblocker eine Pause - du brauchst ihn hier nicht.",
|
||||||
"blog": "Blog",
|
|
||||||
"comments": "Kommentare",
|
"comments": "Kommentare",
|
||||||
"commentsDescription": "Teilnehmer können deine Umfrage kommentieren, die Kommentare sind für alle sichtbar.",
|
"commentsDescription": "Teilnehmer können deine Umfrage kommentieren, die Kommentare sind für alle sichtbar.",
|
||||||
"discussions": "Diskussion",
|
|
||||||
"features": "Funktionen",
|
"features": "Funktionen",
|
||||||
"featuresSubheading": "Terminfindung leicht gemacht",
|
"featuresSubheading": "Terminfindung leicht gemacht",
|
||||||
"follow": "Folgen",
|
"follow": "Folgen",
|
||||||
|
@ -30,15 +28,10 @@
|
||||||
"social": "Social",
|
"social": "Social",
|
||||||
"participantCount": "{{count}} Teilnehmer",
|
"participantCount": "{{count}} Teilnehmer",
|
||||||
"perfect": "Perfekt!",
|
"perfect": "Perfekt!",
|
||||||
"poweredBy": "Unterstützt von",
|
|
||||||
"principles": "Grundsätze",
|
"principles": "Grundsätze",
|
||||||
"principlesSubheading": "Wir sind nicht wie die anderen",
|
"principlesSubheading": "Wir sind nicht wie die anderen",
|
||||||
"privacyPolicy": "Datenschutzrichtlinie",
|
|
||||||
"selfHostable": "Selfhosting möglich",
|
"selfHostable": "Selfhosting möglich",
|
||||||
"selfHostableDescription": "Betreibe es auf deinem eigenen Server, um die volle Kontrolle über deine Daten zu haben",
|
"selfHostableDescription": "Betreibe es auf deinem eigenen Server, um die volle Kontrolle über deine Daten zu haben",
|
||||||
"sponsorThisProject": "Dieses Projekt unterstützen",
|
|
||||||
"star": "Star",
|
|
||||||
"support": "Hilfe",
|
|
||||||
"timeSlots": "Zeitfenster",
|
"timeSlots": "Zeitfenster",
|
||||||
"timeSlotsDescription": "Wähle individuelle Start- und Endzeiten für jede Option in deiner Umfrage. Die Zeiten können automatisch an die Zeitzone jedes Teilnehmers angepasst werden oder so eingestellt werden, dass Zeitzonen komplett ignoriert werden."
|
"timeSlotsDescription": "Wähle individuelle Start- und Endzeiten für jede Option in deiner Umfrage. Die Zeiten können automatisch an die Zeitzone jedes Teilnehmers angepasst werden oder so eingestellt werden, dass Zeitzonen komplett ignoriert werden."
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
"applyToAllDates": "Apply to all dates",
|
"applyToAllDates": "Apply to all dates",
|
||||||
"areYouSure": "Are you sure?",
|
"areYouSure": "Are you sure?",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"blog": "Blog",
|
|
||||||
"calendarHelp": "You can't create a poll without any options. Add at least one option to continue.",
|
"calendarHelp": "You can't create a poll without any options. Add at least one option to continue.",
|
||||||
"calendarHelpTitle": "Forget something?",
|
"calendarHelpTitle": "Forget something?",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
@ -29,7 +28,6 @@
|
||||||
"demoPollNotice": "Demo polls are automatically deleted after 1 day",
|
"demoPollNotice": "Demo polls are automatically deleted after 1 day",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"descriptionPlaceholder": "Hey everyone, please choose the dates that work for you!",
|
"descriptionPlaceholder": "Hey everyone, please choose the dates that work for you!",
|
||||||
"discussions": "Discussions",
|
|
||||||
"donate": "Donate",
|
"donate": "Donate",
|
||||||
"editDetails": "Edit details",
|
"editDetails": "Edit details",
|
||||||
"editOptions": "Edit options",
|
"editOptions": "Edit options",
|
||||||
|
@ -107,7 +105,6 @@
|
||||||
"specifyTimesDescription": "Include start and end times for each option",
|
"specifyTimesDescription": "Include start and end times for each option",
|
||||||
"stepSummary": "Step {{current}} of {{total}}",
|
"stepSummary": "Step {{current}} of {{total}}",
|
||||||
"sunday": "Sunday",
|
"sunday": "Sunday",
|
||||||
"support": "Support",
|
|
||||||
"timeAndDate": "Time & date",
|
"timeAndDate": "Time & date",
|
||||||
"timeFormat": "Time format:",
|
"timeFormat": "Time format:",
|
||||||
"timeZone": "Time Zone:",
|
"timeZone": "Time Zone:",
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
{
|
{
|
||||||
"language": "Language",
|
"blog": "Blog",
|
||||||
|
"discussions": "Discussions",
|
||||||
|
"donate": "Donate",
|
||||||
"english": "English",
|
"english": "English",
|
||||||
|
"footerCredit": "Made by <a>@imlukevella</a>",
|
||||||
|
"footerSponsor": "This project is user-funded. Please consider supporting it by <a>donating</a>.",
|
||||||
"german": "German",
|
"german": "German",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"blog": "Blog",
|
"language": "Language",
|
||||||
"support": "Support",
|
"links": "Links",
|
||||||
"donate": "Donate",
|
"poweredBy": "Powered by",
|
||||||
"volunteerTranslator": "Help translate this site",
|
"privacyPolicy": "Privacy Policy",
|
||||||
"starOnGithub": "Star us on Github",
|
"starOnGithub": "Star us on Github",
|
||||||
"footerCredit": "Made by <a>@imlukevella</a>",
|
"support": "Support",
|
||||||
"footerSponsor": "This project is user-funded. Please consider supporting it by <a>donating</a>."
|
"volunteerTranslator": "Help translate this site"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,8 @@
|
||||||
"3Ls": "Yes—with 3 <e>L</e>s",
|
"3Ls": "Yes—with 3 <e>L</e>s",
|
||||||
"adFree": "Ad-free",
|
"adFree": "Ad-free",
|
||||||
"adFreeDescription": "You can give your ad-blocker a rest — You won't need it here.",
|
"adFreeDescription": "You can give your ad-blocker a rest — You won't need it here.",
|
||||||
"blog": "Blog",
|
|
||||||
"comments": "Comments",
|
"comments": "Comments",
|
||||||
"commentsDescription": "Participants can comment on your poll and the comments will be visible to everyone.",
|
"commentsDescription": "Participants can comment on your poll and the comments will be visible to everyone.",
|
||||||
"discussions": "Discussions",
|
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"featuresSubheading": "Scheduling, the smart way",
|
"featuresSubheading": "Scheduling, the smart way",
|
||||||
"follow": "Follow",
|
"follow": "Follow",
|
||||||
|
@ -30,15 +28,10 @@
|
||||||
"social": "Social",
|
"social": "Social",
|
||||||
"participantCount": "{{count}} participant",
|
"participantCount": "{{count}} participant",
|
||||||
"perfect": "Perfect!",
|
"perfect": "Perfect!",
|
||||||
"poweredBy": "Powered by",
|
|
||||||
"principles": "Principles",
|
"principles": "Principles",
|
||||||
"principlesSubheading": "We're not like the others",
|
"principlesSubheading": "We're not like the others",
|
||||||
"privacyPolicy": "Privacy policy",
|
|
||||||
"selfHostable": "Self-hostable",
|
"selfHostable": "Self-hostable",
|
||||||
"selfHostableDescription": "Run it on your own server to take full control of your data",
|
"selfHostableDescription": "Run it on your own server to take full control of your data",
|
||||||
"sponsorThisProject": "Sponsor this project",
|
|
||||||
"star": "Star",
|
|
||||||
"support": "Support",
|
|
||||||
"timeSlots": "Time slots",
|
"timeSlots": "Time slots",
|
||||||
"timeSlotsDescription": "Set individual start and end times for each option in your poll. Times can be automatically adjusted to each participant's timezone or they can be set to ignore timezones completely."
|
"timeSlotsDescription": "Set individual start and end times for each option in your poll. Times can be automatically adjusted to each participant's timezone or they can be set to ignore timezones completely."
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import Vercel from "~/public/vercel-logotype-dark.svg";
|
||||||
import { LanguageSelect } from "../poll/language-selector";
|
import { LanguageSelect } from "../poll/language-selector";
|
||||||
|
|
||||||
const Footer: React.VoidFunctionComponent = () => {
|
const Footer: React.VoidFunctionComponent = () => {
|
||||||
const { t } = useTranslation(["common", "homepage"]);
|
const { t } = useTranslation("common");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
return (
|
return (
|
||||||
<div className="mt-16 bg-slate-50/70">
|
<div className="mt-16 bg-slate-50/70">
|
||||||
|
@ -26,7 +26,7 @@ const Footer: React.VoidFunctionComponent = () => {
|
||||||
<p>
|
<p>
|
||||||
<Trans
|
<Trans
|
||||||
t={t}
|
t={t}
|
||||||
i18nKey="common:footerSponsor"
|
i18nKey="footerSponsor"
|
||||||
components={{
|
components={{
|
||||||
a: (
|
a: (
|
||||||
<a
|
<a
|
||||||
|
@ -40,7 +40,7 @@ const Footer: React.VoidFunctionComponent = () => {
|
||||||
<div>
|
<div>
|
||||||
<Trans
|
<Trans
|
||||||
t={t}
|
t={t}
|
||||||
i18nKey="common:footerCredit"
|
i18nKey="footerCredit"
|
||||||
components={{
|
components={{
|
||||||
a: (
|
a: (
|
||||||
<a
|
<a
|
||||||
|
@ -70,25 +70,25 @@ const Footer: React.VoidFunctionComponent = () => {
|
||||||
className="inline-flex h-8 items-center rounded-full bg-slate-100 pl-2 pr-3 text-sm text-slate-400 transition-colors hover:bg-primary-500 hover:text-white hover:no-underline focus:ring-2 focus:ring-primary-500 focus:ring-offset-1 active:bg-primary-600"
|
className="inline-flex h-8 items-center rounded-full bg-slate-100 pl-2 pr-3 text-sm text-slate-400 transition-colors hover:bg-primary-500 hover:text-white hover:no-underline focus:ring-2 focus:ring-primary-500 focus:ring-offset-1 active:bg-primary-600"
|
||||||
>
|
>
|
||||||
<Star className="mr-2 inline-block w-5" />
|
<Star className="mr-2 inline-block w-5" />
|
||||||
<span>{t("common:starOnGithub")}</span>
|
<span>{t("starOnGithub")}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<div className="mb-4 font-medium">{t("homepage:links")}</div>
|
<div className="mb-4 font-medium">{t("links")}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline"
|
className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline"
|
||||||
href="https://github.com/lukevella/rallly/discussions"
|
href="https://github.com/lukevella/rallly/discussions"
|
||||||
>
|
>
|
||||||
{t("homepage:discussions")}
|
{t("discussions")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link href="https://blog.rallly.co">
|
<Link href="https://blog.rallly.co">
|
||||||
<a className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline">
|
<a className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline">
|
||||||
{t("homepage:blog")}
|
{t("blog")}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
@ -97,20 +97,20 @@ const Footer: React.VoidFunctionComponent = () => {
|
||||||
href="https://support.rallly.co"
|
href="https://support.rallly.co"
|
||||||
className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline"
|
className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline"
|
||||||
>
|
>
|
||||||
{t("homepage:support")}
|
{t("support")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link href="/privacy-policy">
|
<Link href="/privacy-policy">
|
||||||
<a className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline">
|
<a className="font-normal leading-loose text-slate-400 hover:text-slate-800 hover:no-underline">
|
||||||
{t("homepage:privacyPolicy")}
|
{t("privacyPolicy")}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="lg:col-span-3">
|
<div className="lg:col-span-3">
|
||||||
<div className="mb-4 font-medium">{t("homepage:poweredBy")}</div>
|
<div className="mb-4 font-medium">{t("poweredBy")}</div>
|
||||||
<div className="block space-y-4">
|
<div className="block space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
|
@ -133,7 +133,7 @@ const Footer: React.VoidFunctionComponent = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="lg:col-span-3">
|
<div className="lg:col-span-3">
|
||||||
<div className="mb-4 font-medium">{t("common:language")}</div>
|
<div className="mb-4 font-medium">{t("language")}</div>
|
||||||
<LanguageSelect
|
<LanguageSelect
|
||||||
className="mb-4 w-full"
|
className="mb-4 w-full"
|
||||||
onChange={(locale) => {
|
onChange={(locale) => {
|
||||||
|
@ -145,7 +145,7 @@ const Footer: React.VoidFunctionComponent = () => {
|
||||||
className="inline-flex items-center rounded-md border px-3 py-2 text-xs text-slate-500"
|
className="inline-flex items-center rounded-md border px-3 py-2 text-xs text-slate-500"
|
||||||
>
|
>
|
||||||
<Translate className="mr-2 h-5 w-5" />
|
<Translate className="mr-2 h-5 w-5" />
|
||||||
{t("common:volunteerTranslator")} →
|
{t("volunteerTranslator")} →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -51,8 +51,10 @@ const Poll: React.VoidFunctionComponent = () => {
|
||||||
Math.floor((width - (minSidebarWidth + actionColumnWidth)) / columnWidth),
|
Math.floor((width - (minSidebarWidth + actionColumnWidth)) / columnWidth),
|
||||||
);
|
);
|
||||||
|
|
||||||
const sidebarWidth =
|
const sidebarWidth = Math.min(
|
||||||
width - (numberOfVisibleColumns * columnWidth + actionColumnWidth);
|
width - (numberOfVisibleColumns * columnWidth + actionColumnWidth),
|
||||||
|
300,
|
||||||
|
);
|
||||||
|
|
||||||
const availableSpace = Math.min(
|
const availableSpace = Math.min(
|
||||||
numberOfVisibleColumns * columnWidth,
|
numberOfVisibleColumns * columnWidth,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
import ArrowLeft from "@/components/icons/arrow-left.svg";
|
||||||
import ArrowRight from "@/components/icons/arrow-right.svg";
|
import ArrowRight from "@/components/icons/arrow-right.svg";
|
||||||
|
|
||||||
import { requiredString } from "../../../utils/form-validation";
|
import { requiredString } from "../../../utils/form-validation";
|
||||||
|
@ -16,7 +16,6 @@ import { VoteSelector } from "../vote-selector";
|
||||||
import ControlledScrollArea from "./controlled-scroll-area";
|
import ControlledScrollArea from "./controlled-scroll-area";
|
||||||
import { usePollContext } from "./poll-context";
|
import { usePollContext } from "./poll-context";
|
||||||
|
|
||||||
const MotionButton = motion(Button);
|
|
||||||
export interface ParticipantRowFormProps {
|
export interface ParticipantRowFormProps {
|
||||||
defaultValues?: Partial<ParticipantForm>;
|
defaultValues?: Partial<ParticipantForm>;
|
||||||
onSubmit: (data: ParticipantFormSubmitted) => Promise<void>;
|
onSubmit: (data: ParticipantFormSubmitted) => Promise<void>;
|
||||||
|
@ -35,6 +34,7 @@ const ParticipantRowForm: React.ForwardRefRenderFunction<
|
||||||
sidebarWidth,
|
sidebarWidth,
|
||||||
numberOfColumns,
|
numberOfColumns,
|
||||||
goToNextPage,
|
goToNextPage,
|
||||||
|
goToPreviousPage,
|
||||||
maxScrollPosition,
|
maxScrollPosition,
|
||||||
setScrollPosition,
|
setScrollPosition,
|
||||||
} = usePollContext();
|
} = usePollContext();
|
||||||
|
@ -161,28 +161,30 @@ const ParticipantRowForm: React.ForwardRefRenderFunction<
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{maxScrollPosition > 0 ? (
|
||||||
<div className="flex items-center space-x-2 px-2 transition-all">
|
<div className="flex items-center space-x-2 px-2 transition-all">
|
||||||
{scrollPosition < maxScrollPosition ? (
|
<Button
|
||||||
<AnimatePresence initial={false}>
|
disabled={scrollPosition === 0}
|
||||||
{scrollPosition < maxScrollPosition ? (
|
className="text-xs"
|
||||||
<MotionButton
|
rounded={true}
|
||||||
transition={{ duration: 0.1 }}
|
onClick={() => {
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
goToPreviousPage();
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
}}
|
||||||
exit={{ opacity: 0, scale: 0.8 }}
|
>
|
||||||
className="text-xs"
|
<ArrowLeft className="h-4 w-4" />
|
||||||
rounded={true}
|
</Button>
|
||||||
onClick={() => {
|
<Button
|
||||||
goToNextPage();
|
disabled={scrollPosition >= maxScrollPosition}
|
||||||
}}
|
className="text-xs"
|
||||||
>
|
rounded={true}
|
||||||
<ArrowRight className="h-4 w-4" />
|
onClick={() => {
|
||||||
</MotionButton>
|
goToNextPage();
|
||||||
) : null}
|
}}
|
||||||
</AnimatePresence>
|
>
|
||||||
) : null}
|
<ArrowRight className="h-4 w-4" />
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,7 +42,7 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
||||||
openLoginModal: () => void;
|
openLoginModal: () => void;
|
||||||
}> = ({ openLoginModal }) => {
|
}> = ({ openLoginModal }) => {
|
||||||
const { user } = useSession();
|
const { user } = useSession();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation(["common", "app"]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed top-0 z-40 flex h-12 w-full shrink-0 items-center justify-between border-b bg-gray-50
|
className="fixed top-0 z-40 flex h-12 w-full shrink-0 items-center justify-between border-b bg-gray-50
|
||||||
|
@ -58,7 +58,7 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
||||||
className="flex w-full cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
className="flex w-full cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
||||||
>
|
>
|
||||||
<Login className="h-5 opacity-75" />
|
<Login className="h-5 opacity-75" />
|
||||||
<span className="inline-block">{t("login")}</span>
|
<span className="inline-block">{t("app:login")}</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
|
@ -96,7 +96,9 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
||||||
className="group flex items-center whitespace-nowrap rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
className="group flex items-center whitespace-nowrap rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
||||||
>
|
>
|
||||||
<Adjustments className="h-5 opacity-75 group-hover:text-primary-500" />
|
<Adjustments className="h-5 opacity-75 group-hover:text-primary-500" />
|
||||||
<span className="ml-2 hidden sm:block">{t("preferences")}</span>
|
<span className="ml-2 hidden sm:block">
|
||||||
|
{t("app:preferences")}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -110,7 +112,7 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
||||||
className="group flex items-center rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
className="group flex items-center rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
|
||||||
>
|
>
|
||||||
<Menu className="w-5 group-hover:text-primary-500" />
|
<Menu className="w-5 group-hover:text-primary-500" />
|
||||||
<span className="ml-2 hidden sm:block">{t("menu")}</span>
|
<span className="ml-2 hidden sm:block">{t("app:menu")}</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -124,13 +126,13 @@ const MobileNavigation: React.VoidFunctionComponent<{
|
||||||
const AppMenu: React.VoidFunctionComponent<{ className?: string }> = ({
|
const AppMenu: React.VoidFunctionComponent<{ className?: string }> = ({
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation(["common", "app"]);
|
||||||
return (
|
return (
|
||||||
<div className={clsx("space-y-1", className)}>
|
<div className={clsx("space-y-1", className)}>
|
||||||
<Link href="/new">
|
<Link href="/new">
|
||||||
<a className="flex cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 pr-4 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300">
|
<a className="flex cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 pr-4 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300">
|
||||||
<Pencil className="h-5 opacity-75 " />
|
<Pencil className="h-5 opacity-75 " />
|
||||||
<span className="inline-block">{t("newPoll")}</span>
|
<span className="inline-block">{t("app:newPoll")}</span>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
|
@ -140,7 +142,7 @@ const AppMenu: React.VoidFunctionComponent<{ className?: string }> = ({
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Support className="h-5 opacity-75" />
|
<Support className="h-5 opacity-75" />
|
||||||
<span className="inline-block">{t("support")}</span>
|
<span className="inline-block">{t("common:support")}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -150,7 +152,7 @@ const UserDropdown: React.VoidFunctionComponent<
|
||||||
DropdownProps & { openLoginModal: () => void }
|
DropdownProps & { openLoginModal: () => void }
|
||||||
> = ({ children, openLoginModal, ...forwardProps }) => {
|
> = ({ children, openLoginModal, ...forwardProps }) => {
|
||||||
const { logout, user } = useSession();
|
const { logout, user } = useSession();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation(["common", "app"]);
|
||||||
const modalContext = useModalContext();
|
const modalContext = useModalContext();
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -161,7 +163,7 @@ const UserDropdown: React.VoidFunctionComponent<
|
||||||
{user.isGuest ? (
|
{user.isGuest ? (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={Question}
|
icon={Question}
|
||||||
label={t("whatsThis")}
|
label={t("app:whatsThis")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modalContext.render({
|
modalContext.render({
|
||||||
showClose: true,
|
showClose: true,
|
||||||
|
@ -180,14 +182,14 @@ const UserDropdown: React.VoidFunctionComponent<
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>{t("guestSessionNotice")}</p>
|
<p>{t("app:guestSessionNotice")}</p>
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href="https://support.rallly.co/guest-sessions"
|
href="https://support.rallly.co/guest-sessions"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{t("guestSessionReadMore")}
|
{t("app:guestSessionReadMore")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -206,19 +208,19 @@ const UserDropdown: React.VoidFunctionComponent<
|
||||||
) : null}
|
) : null}
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={Logout}
|
icon={Logout}
|
||||||
label={user.isGuest ? t("forgetMe") : t("logout")}
|
label={user.isGuest ? t("app:forgetMe") : t("app:logout")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (user?.isGuest) {
|
if (user?.isGuest) {
|
||||||
modalContext.render({
|
modalContext.render({
|
||||||
title: t("areYouSure"),
|
title: t("app:areYouSure"),
|
||||||
description: t("endingGuestSessionNotice"),
|
description: t("app:endingGuestSessionNotice"),
|
||||||
|
|
||||||
onOk: logout,
|
onOk: logout,
|
||||||
okButtonProps: {
|
okButtonProps: {
|
||||||
type: "danger",
|
type: "danger",
|
||||||
},
|
},
|
||||||
okText: t("endSession"),
|
okText: t("app:endSession"),
|
||||||
cancelText: t("cancel"),
|
cancelText: t("app:cancel"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logout();
|
logout();
|
||||||
|
@ -233,7 +235,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}> = ({ children, ...rest }) => {
|
}> = ({ children, ...rest }) => {
|
||||||
const { user } = useSession();
|
const { user } = useSession();
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation(["common", "app"]);
|
||||||
const [loginModal, openLoginModal] = useModal({
|
const [loginModal, openLoginModal] = useModal({
|
||||||
footer: null,
|
footer: null,
|
||||||
overlayClosable: true,
|
overlayClosable: true,
|
||||||
|
@ -257,7 +259,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
||||||
<Link href="/new">
|
<Link href="/new">
|
||||||
<a className="group mb-1 flex items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20">
|
<a className="group mb-1 flex items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20">
|
||||||
<Pencil className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
<Pencil className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||||
<span className="grow text-left">{t("newPoll")}</span>
|
<span className="grow text-left">{t("app:newPoll")}</span>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
|
@ -267,14 +269,14 @@ const StandardLayout: React.VoidFunctionComponent<{
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Support className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
<Support className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||||
<span className="grow text-left">{t("support")}</span>
|
<span className="grow text-left">{t("common:support")}</span>
|
||||||
</a>
|
</a>
|
||||||
<Popover
|
<Popover
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
trigger={
|
trigger={
|
||||||
<button className="group flex w-full items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20">
|
<button className="group flex w-full items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20">
|
||||||
<Adjustments className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
<Adjustments className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||||
<span className="grow text-left">{t("preferences")}</span>
|
<span className="grow text-left">{t("app:preferences")}</span>
|
||||||
<DotsVertical className="h-4 text-slate-500 opacity-0 transition-opacity group-hover:opacity-100" />
|
<DotsVertical className="h-4 text-slate-500 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
@ -287,7 +289,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
||||||
className="group flex w-full items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20"
|
className="group flex w-full items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20"
|
||||||
>
|
>
|
||||||
<Login className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
<Login className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
|
||||||
<span className="grow text-left">{t("login")}</span>
|
<span className="grow text-left">{t("app:login")}</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -313,7 +315,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
||||||
{user.shortName}
|
{user.shortName}
|
||||||
</div>
|
</div>
|
||||||
<div className="truncate text-xs text-slate-500">
|
<div className="truncate text-xs text-slate-500">
|
||||||
{user.isGuest ? t("guest") : t("user")}
|
{user.isGuest ? t("app:guest") : t("app:user")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DotsVertical className="h-4 text-slate-500 opacity-0 transition-opacity group-hover:opacity-100" />
|
<DotsVertical className="h-4 text-slate-500 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||||
|
@ -345,16 +347,16 @@ const StandardLayout: React.VoidFunctionComponent<{
|
||||||
className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline"
|
className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{t("support")}
|
{t("common:support")}
|
||||||
</a>
|
</a>
|
||||||
<Link href="https://github.com/lukevella/rallly/discussions">
|
<Link href="https://github.com/lukevella/rallly/discussions">
|
||||||
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
|
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
|
||||||
{t("discussions")}
|
{t("common:discussions")}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="https://blog.rallly.co">
|
<Link href="https://blog.rallly.co">
|
||||||
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
|
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
|
||||||
{t("blog")}
|
{t("common:blog")}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="hidden text-slate-300 lg:block">•</div>
|
<div className="hidden text-slate-300 lg:block">•</div>
|
||||||
|
@ -385,7 +387,7 @@ const StandardLayout: React.VoidFunctionComponent<{
|
||||||
className="inline-flex h-8 items-center rounded-full bg-slate-100 pl-2 pr-3 text-sm text-slate-400 transition-colors hover:bg-primary-500 hover:text-white hover:no-underline focus:ring-2 focus:ring-primary-500 focus:ring-offset-1 active:bg-primary-600"
|
className="inline-flex h-8 items-center rounded-full bg-slate-100 pl-2 pr-3 text-sm text-slate-400 transition-colors hover:bg-primary-500 hover:text-white hover:no-underline focus:ring-2 focus:ring-primary-500 focus:ring-offset-1 active:bg-primary-600"
|
||||||
>
|
>
|
||||||
<Cash className="mr-1 inline-block w-5" />
|
<Cash className="mr-1 inline-block w-5" />
|
||||||
<span>{t("donate")}</span>
|
<span>{t("app:donate")}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,6 +22,5 @@ export function middleware({ headers, cookies, nextUrl }: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
// these are paths we should rewrite to prev
|
matcher: ["/admin/:id", "/demo", "/p/:id", "/profile", "/new", "/login"],
|
||||||
matcher: ["/admin/:id", "/demo", "/p/:id", "/profile", "/new"],
|
|
||||||
};
|
};
|
||||||
|
|
15
tests/i18n.spec.ts
Normal file
15
tests/i18n.spec.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import test, { expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test("should show correct language if supported", async ({ browser }) => {
|
||||||
|
const context = await browser.newContext({ locale: "de" });
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page.locator("text=Los geht's")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should default to english", async ({ browser }) => {
|
||||||
|
const context = await browser.newContext({ locale: "mt" });
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto("/new");
|
||||||
|
await expect(page.locator("h1", { hasText: "New poll" })).toBeVisible();
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue