From f46c9b034832e8e290cebd750122d37e553236a4 Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Thu, 21 Jul 2022 17:11:10 +0100 Subject: [PATCH] Locale update patch (#229) --- public/locales/de/app.json | 3 -- public/locales/de/common.json | 18 ++++--- public/locales/de/homepage.json | 7 --- public/locales/en/app.json | 3 -- public/locales/en/common.json | 18 ++++--- public/locales/en/homepage.json | 7 --- src/components/page-layout/footer.tsx | 24 ++++----- src/components/poll/desktop-poll.tsx | 6 ++- .../desktop-poll/participant-row-form.tsx | 50 ++++++++--------- src/components/standard-layout.tsx | 54 ++++++++++--------- src/middleware.ts | 3 +- tests/i18n.spec.ts | 15 ++++++ 12 files changed, 108 insertions(+), 100 deletions(-) create mode 100644 tests/i18n.spec.ts diff --git a/public/locales/de/app.json b/public/locales/de/app.json index 3824f615e..6603e8234 100644 --- a/public/locales/de/app.json +++ b/public/locales/de/app.json @@ -5,7 +5,6 @@ "applyToAllDates": "Auf alle Termine anwenden", "areYouSure": "Bist du sicher?", "back": "Zurück", - "blog": "Blog", "calendarHelp": "Du kannst keine Umfrage ohne Optionen erstellen. Füge mindestens eine Option hinzu, um fortzufahren.", "calendarHelpTitle": "Irgendwas vergessen?", "cancel": "Abbrechen", @@ -29,7 +28,6 @@ "demoPollNotice": "Demo-Umfragen werden automatisch nach einem Tag gelöscht", "description": "Beschreibung", "descriptionPlaceholder": "Hallo ihr, bitte wählt alle Termine aus, die für euch passen!", - "discussions": "Diskussion", "donate": "Spenden", "editDetails": "Details bearbeiten", "editOptions": "Optionen bearbeiten", @@ -107,7 +105,6 @@ "specifyTimesDescription": "Start- und Endzeit für jede Option angeben", "stepSummary": "Schritt {{current}} von {{total}}", "sunday": "Sonntag", - "support": "Hilfe", "timeAndDate": "Datum & Uhrzeit", "timeFormat": "Uhrzeitformat:", "timeZone": "Zeitzone:", diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 3f23dc4d5..11b3cafd2 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -1,13 +1,17 @@ { - "language": "Sprache", + "blog": "Blog", + "discussions": "Diskussion", + "donate": "Spenden", "english": "Englisch", + "footerCredit": "Made by @imlukevella", + "footerSponsor": "This project is user-funded. Please consider supporting it by donating.", "german": "Deutsch", "home": "Home", - "blog": "Blog", - "support": "Support", - "donate": "Donate", - "volunteerTranslator": "Help translate this site", + "language": "Sprache", + "links": "Links", + "poweredBy": "Unterstützt von", + "privacyPolicy": "Datenschutzerklärung", "starOnGithub": "Star us on Github", - "footerCredit": "Made by @imlukevella", - "footerSponsor": "This project is user-funded. Please consider supporting it by donating." + "support": "Hilfe", + "volunteerTranslator": "Help translate this site" } diff --git a/public/locales/de/homepage.json b/public/locales/de/homepage.json index 09395ce58..837d4366b 100644 --- a/public/locales/de/homepage.json +++ b/public/locales/de/homepage.json @@ -2,10 +2,8 @@ "3Ls": "Ja – mit 3 Ls", "adFree": "Ohne Werbung", "adFreeDescription": "Gönn deinem Adblocker eine Pause - du brauchst ihn hier nicht.", - "blog": "Blog", "comments": "Kommentare", "commentsDescription": "Teilnehmer können deine Umfrage kommentieren, die Kommentare sind für alle sichtbar.", - "discussions": "Diskussion", "features": "Funktionen", "featuresSubheading": "Terminfindung leicht gemacht", "follow": "Folgen", @@ -30,15 +28,10 @@ "social": "Social", "participantCount": "{{count}} Teilnehmer", "perfect": "Perfekt!", - "poweredBy": "Unterstützt von", "principles": "Grundsätze", "principlesSubheading": "Wir sind nicht wie die anderen", - "privacyPolicy": "Datenschutzrichtlinie", "selfHostable": "Selfhosting möglich", "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", "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." } diff --git a/public/locales/en/app.json b/public/locales/en/app.json index 9808ede59..5fb1e33c7 100644 --- a/public/locales/en/app.json +++ b/public/locales/en/app.json @@ -5,7 +5,6 @@ "applyToAllDates": "Apply to all dates", "areYouSure": "Are you sure?", "back": "Back", - "blog": "Blog", "calendarHelp": "You can't create a poll without any options. Add at least one option to continue.", "calendarHelpTitle": "Forget something?", "cancel": "Cancel", @@ -29,7 +28,6 @@ "demoPollNotice": "Demo polls are automatically deleted after 1 day", "description": "Description", "descriptionPlaceholder": "Hey everyone, please choose the dates that work for you!", - "discussions": "Discussions", "donate": "Donate", "editDetails": "Edit details", "editOptions": "Edit options", @@ -107,7 +105,6 @@ "specifyTimesDescription": "Include start and end times for each option", "stepSummary": "Step {{current}} of {{total}}", "sunday": "Sunday", - "support": "Support", "timeAndDate": "Time & date", "timeFormat": "Time format:", "timeZone": "Time Zone:", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index e7654f0ab..b8183cc5a 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1,13 +1,17 @@ { - "language": "Language", + "blog": "Blog", + "discussions": "Discussions", + "donate": "Donate", "english": "English", + "footerCredit": "Made by @imlukevella", + "footerSponsor": "This project is user-funded. Please consider supporting it by donating.", "german": "German", "home": "Home", - "blog": "Blog", - "support": "Support", - "donate": "Donate", - "volunteerTranslator": "Help translate this site", + "language": "Language", + "links": "Links", + "poweredBy": "Powered by", + "privacyPolicy": "Privacy Policy", "starOnGithub": "Star us on Github", - "footerCredit": "Made by @imlukevella", - "footerSponsor": "This project is user-funded. Please consider supporting it by donating." + "support": "Support", + "volunteerTranslator": "Help translate this site" } diff --git a/public/locales/en/homepage.json b/public/locales/en/homepage.json index d34988a07..f2b55f800 100644 --- a/public/locales/en/homepage.json +++ b/public/locales/en/homepage.json @@ -2,10 +2,8 @@ "3Ls": "Yes—with 3 Ls", "adFree": "Ad-free", "adFreeDescription": "You can give your ad-blocker a rest — You won't need it here.", - "blog": "Blog", "comments": "Comments", "commentsDescription": "Participants can comment on your poll and the comments will be visible to everyone.", - "discussions": "Discussions", "features": "Features", "featuresSubheading": "Scheduling, the smart way", "follow": "Follow", @@ -30,15 +28,10 @@ "social": "Social", "participantCount": "{{count}} participant", "perfect": "Perfect!", - "poweredBy": "Powered by", "principles": "Principles", "principlesSubheading": "We're not like the others", - "privacyPolicy": "Privacy policy", "selfHostable": "Self-hostable", "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", "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." } diff --git a/src/components/page-layout/footer.tsx b/src/components/page-layout/footer.tsx index feeaff128..a16485936 100644 --- a/src/components/page-layout/footer.tsx +++ b/src/components/page-layout/footer.tsx @@ -15,7 +15,7 @@ import Vercel from "~/public/vercel-logotype-dark.svg"; import { LanguageSelect } from "../poll/language-selector"; const Footer: React.VoidFunctionComponent = () => { - const { t } = useTranslation(["common", "homepage"]); + const { t } = useTranslation("common"); const router = useRouter(); return (
@@ -26,7 +26,7 @@ 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" > - {t("common:starOnGithub")} + {t("starOnGithub")}
-
{t("homepage:links")}
+
{t("links")}
-
{t("homepage:poweredBy")}
+
{t("poweredBy")}
{
-
{t("common:language")}
+
{t("language")}
{ @@ -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" > - {t("common:volunteerTranslator")} → + {t("volunteerTranslator")} →
diff --git a/src/components/poll/desktop-poll.tsx b/src/components/poll/desktop-poll.tsx index c9311de1f..d3e2f0c4c 100644 --- a/src/components/poll/desktop-poll.tsx +++ b/src/components/poll/desktop-poll.tsx @@ -51,8 +51,10 @@ const Poll: React.VoidFunctionComponent = () => { Math.floor((width - (minSidebarWidth + actionColumnWidth)) / columnWidth), ); - const sidebarWidth = - width - (numberOfVisibleColumns * columnWidth + actionColumnWidth); + const sidebarWidth = Math.min( + width - (numberOfVisibleColumns * columnWidth + actionColumnWidth), + 300, + ); const availableSpace = Math.min( numberOfVisibleColumns * columnWidth, diff --git a/src/components/poll/desktop-poll/participant-row-form.tsx b/src/components/poll/desktop-poll/participant-row-form.tsx index b901bc5db..115a31972 100644 --- a/src/components/poll/desktop-poll/participant-row-form.tsx +++ b/src/components/poll/desktop-poll/participant-row-form.tsx @@ -1,9 +1,9 @@ import clsx from "clsx"; -import { AnimatePresence, motion } from "framer-motion"; import { useTranslation } from "next-i18next"; import * as React from "react"; import { Controller, useForm } from "react-hook-form"; +import ArrowLeft from "@/components/icons/arrow-left.svg"; import ArrowRight from "@/components/icons/arrow-right.svg"; import { requiredString } from "../../../utils/form-validation"; @@ -16,7 +16,6 @@ import { VoteSelector } from "../vote-selector"; import ControlledScrollArea from "./controlled-scroll-area"; import { usePollContext } from "./poll-context"; -const MotionButton = motion(Button); export interface ParticipantRowFormProps { defaultValues?: Partial; onSubmit: (data: ParticipantFormSubmitted) => Promise; @@ -35,6 +34,7 @@ const ParticipantRowForm: React.ForwardRefRenderFunction< sidebarWidth, numberOfColumns, goToNextPage, + goToPreviousPage, maxScrollPosition, setScrollPosition, } = usePollContext(); @@ -161,28 +161,30 @@ const ParticipantRowForm: React.ForwardRefRenderFunction< ); }} /> - -
- {scrollPosition < maxScrollPosition ? ( - - {scrollPosition < maxScrollPosition ? ( - { - goToNextPage(); - }} - > - - - ) : null} - - ) : null} -
+ {maxScrollPosition > 0 ? ( +
+ + +
+ ) : null} ); }; diff --git a/src/components/standard-layout.tsx b/src/components/standard-layout.tsx index d04a5afa5..62ee7872e 100644 --- a/src/components/standard-layout.tsx +++ b/src/components/standard-layout.tsx @@ -42,7 +42,7 @@ const MobileNavigation: React.VoidFunctionComponent<{ openLoginModal: () => void; }> = ({ openLoginModal }) => { const { user } = useSession(); - const { t } = useTranslation("app"); + const { t } = useTranslation(["common", "app"]); return (
- {t("login")} + {t("app:login")} )} @@ -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" > - {t("preferences")} + + {t("app:preferences")} + } > @@ -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" > - {t("menu")} + {t("app:menu")} } > @@ -124,13 +126,13 @@ const MobileNavigation: React.VoidFunctionComponent<{ const AppMenu: React.VoidFunctionComponent<{ className?: string }> = ({ className, }) => { - const { t } = useTranslation("app"); + const { t } = useTranslation(["common", "app"]); return ( ); @@ -150,7 +152,7 @@ const UserDropdown: React.VoidFunctionComponent< DropdownProps & { openLoginModal: () => void } > = ({ children, openLoginModal, ...forwardProps }) => { const { logout, user } = useSession(); - const { t } = useTranslation("app"); + const { t } = useTranslation(["common", "app"]); const modalContext = useModalContext(); if (!user) { return null; @@ -161,7 +163,7 @@ const UserDropdown: React.VoidFunctionComponent< {user.isGuest ? ( { modalContext.render({ showClose: true, @@ -180,14 +182,14 @@ const UserDropdown: React.VoidFunctionComponent<
-

{t("guestSessionNotice")}

+

{t("app:guestSessionNotice")}

- {t("guestSessionReadMore")} + {t("app:guestSessionReadMore")}
@@ -206,19 +208,19 @@ const UserDropdown: React.VoidFunctionComponent< ) : null} { if (user?.isGuest) { modalContext.render({ - title: t("areYouSure"), - description: t("endingGuestSessionNotice"), + title: t("app:areYouSure"), + description: t("app:endingGuestSessionNotice"), onOk: logout, okButtonProps: { type: "danger", }, - okText: t("endSession"), - cancelText: t("cancel"), + okText: t("app:endSession"), + cancelText: t("app:cancel"), }); } else { logout(); @@ -233,7 +235,7 @@ const StandardLayout: React.VoidFunctionComponent<{ children?: React.ReactNode; }> = ({ children, ...rest }) => { const { user } = useSession(); - const { t } = useTranslation("app"); + const { t } = useTranslation(["common", "app"]); const [loginModal, openLoginModal] = useModal({ footer: null, overlayClosable: true, @@ -257,7 +259,7 @@ const StandardLayout: React.VoidFunctionComponent<{ - {t("newPoll")} + {t("app:newPoll")} - {t("support")} + {t("common:support")} - {t("preferences")} + {t("app:preferences")} } @@ -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" > - {t("login")} + {t("app:login")} )} @@ -313,7 +315,7 @@ const StandardLayout: React.VoidFunctionComponent<{ {user.shortName}
- {user.isGuest ? t("guest") : t("user")} + {user.isGuest ? t("app:guest") : t("app:user")}
@@ -345,16 +347,16 @@ const StandardLayout: React.VoidFunctionComponent<{ className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline" rel="noreferrer" > - {t("support")} + {t("common:support")} - {t("discussions")} + {t("common:discussions")} - {t("blog")} + {t("common:blog")}
@@ -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" > - {t("donate")} + {t("app:donate")} diff --git a/src/middleware.ts b/src/middleware.ts index f221a2410..ba1c263ec 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -22,6 +22,5 @@ export function middleware({ headers, cookies, nextUrl }: NextRequest) { } export const config = { - // these are paths we should rewrite to prev - matcher: ["/admin/:id", "/demo", "/p/:id", "/profile", "/new"], + matcher: ["/admin/:id", "/demo", "/p/:id", "/profile", "/new", "/login"], }; diff --git a/tests/i18n.spec.ts b/tests/i18n.spec.ts new file mode 100644 index 000000000..1e64ee5e5 --- /dev/null +++ b/tests/i18n.spec.ts @@ -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(); +});