Locale update patch (#229)

This commit is contained in:
Luke Vella 2022-07-21 17:11:10 +01:00 committed by GitHub
parent e0a5cfec39
commit f46c9b0348
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 108 additions and 100 deletions

View file

@ -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:",

View file

@ -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"
} }

View file

@ -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."
} }

View file

@ -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:",

View file

@ -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"
} }

View file

@ -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."
} }

View file

@ -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")} &rarr; {t("volunteerTranslator")} &rarr;
</a> </a>
</div> </div>
</div> </div>

View file

@ -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,

View file

@ -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>
); );
}; };

View file

@ -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">&bull;</div> <div className="hidden text-slate-300 lg:block">&bull;</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>

View file

@ -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
View 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();
});