chore: i18n/extract strings (#934)

This commit is contained in:
Mihai C 2021-10-13 13:49:15 +03:00 committed by GitHub
parent bee41b242b
commit 9e2f8de313
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 72 deletions

View file

@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useLocale } from "@lib/hooks/useLocale";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import showToast from "@lib/notification";
import { trpc } from "@lib/trpc";
@ -22,6 +23,7 @@ function convertMinsToHrsMins(mins: number) {
return `${hours}:${minutes}`;
}
export default function Availability() {
const { t } = useLocale();
const queryMe = trpc.useQuery(["viewer.me"]);
const formModal = useToggleQuery("edit");
@ -57,27 +59,25 @@ export default function Availability() {
return <Loader />;
}
if (queryMe.status !== "success") {
return <Alert severity="error" title="Something went wrong" />;
return <Alert severity="error" title={t("something_went_wrong")} />;
}
const user = queryMe.data;
return (
<div>
<Shell heading="Availability" subtitle="Configure times when you are available for bookings.">
<Shell heading={t("availability")} subtitle={t("configure_availability")}>
<div className="flex">
<div className="w-1/2 mr-2 bg-white border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
Change the start and end times of your day
</h3>
<h3 className="text-lg leading-6 font-medium text-gray-900">{t("change_start_end")}</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<p>
Currently, your day is set to start at {convertMinsToHrsMins(user.startTime)} and end at{" "}
{t("current_start_date")} {convertMinsToHrsMins(user.startTime)} {t("and_end_at")}{" "}
{convertMinsToHrsMins(user.endTime)}.
</p>
</div>
<div className="mt-5">
<Button href={formModal.hrefOn}>Change available times</Button>
<Button href={formModal.hrefOn}>{t("change_available_times")}</Button>
</div>
</div>
</div>
@ -85,14 +85,14 @@ export default function Availability() {
<div className="w-1/2 ml-2 border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
Something doesn&apos;t look right?
{t("something_doesnt_look_right")}
</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<p>Troubleshoot your availability to explore why your times are showing as they are.</p>
<p>{t("troubleshoot_availability")}</p>
</div>
<div className="mt-5">
<Link href="/availability/troubleshoot">
<a className="btn btn-white">Launch troubleshooter</a>
<a className="btn btn-white">{t("launch_troubleshooter")}</a>
</Link>
</div>
</div>
@ -111,12 +111,10 @@ export default function Availability() {
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
Change your available times
{t("change_your_available_times")}
</h3>
<div>
<p className="text-sm text-gray-500">
Set the start and end time of your day and a minimum buffer between your meetings.
</p>
<p className="text-sm text-gray-500">{t("change_start_end_buffer")}</p>
</div>
</div>
</div>
@ -136,19 +134,21 @@ export default function Availability() {
},
});
if (!response.ok) {
showToast("Something went wrong", "error");
showToast(t("something_went_wrong"), "error");
return;
}
await queryMe.refetch();
router.push(formModal.hrefOff);
showToast("The start and end times for your day have been changed successfully.", "success");
showToast(t("start_end_changed_successfully"), "success");
})}>
<div className="flex mb-4">
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Start time</label>
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">
{t("start_time")}
</label>
<div>
<label htmlFor="startHours" className="sr-only">
Hours
{t("hours")}
</label>
<input
{...formMethods.register("startHours")}
@ -162,7 +162,7 @@ export default function Availability() {
<span className="mx-2 pt-1">:</span>
<div>
<label htmlFor="startMins" className="sr-only">
Minutes
{t("minutes")}
</label>
<input
{...formMethods.register("startMins")}
@ -174,10 +174,10 @@ export default function Availability() {
</div>
</div>
<div className="flex mb-4">
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">End time</label>
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">{t("end_time")}</label>
<div>
<label htmlFor="endHours" className="sr-only">
Hours
{t("hours")}
</label>
<input
{...formMethods.register("endHours")}
@ -190,7 +190,7 @@ export default function Availability() {
<span className="mx-2 pt-1">:</span>
<div>
<label htmlFor="endMins" className="sr-only">
Minutes
{t("minutes")}
</label>
<input
{...formMethods.register("endMins")}
@ -202,10 +202,10 @@ export default function Availability() {
</div>
</div>
<div className="flex mb-4">
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Buffer</label>
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">{t("buffer")}</label>
<div>
<label htmlFor="bufferHours" className="sr-only">
Hours
{t("hours")}
</label>
<input
{...formMethods.register("bufferHours")}
@ -218,7 +218,7 @@ export default function Availability() {
<span className="mx-2 pt-1">:</span>
<div>
<label htmlFor="bufferMins" className="sr-only">
Minutes
{t("minutes")}
</label>
<input
{...formMethods.register("bufferMins")}
@ -231,10 +231,10 @@ export default function Availability() {
</div>
<div className="mt-5 sm:mt-4 sm:flex space-x-2">
<Button href={formModal.hrefOff} color="secondary" tabIndex={-1}>
Cancel
{t("cancel")}
</Button>
<Button type="submit" loading={formMethods.formState.isSubmitting}>
Update
{t("update")}
</Button>
</div>
</form>

View file

@ -1,9 +1,12 @@
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useEffect, useState } from "react";
import { getSession } from "@lib/auth";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -13,6 +16,7 @@ import Shell from "@components/Shell";
dayjs.extend(utc);
export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const [loading, setLoading] = useState(true);
const [availability, setAvailability] = useState([]);
const [selectedDate, setSelectedDate] = useState(dayjs());
@ -52,12 +56,10 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
return (
<div>
<Shell
heading="Troubleshoot"
subtitle="Understand why certain times are available and others are blocked.">
<Shell heading={t("troubleshoot")} subtitle={t("troubleshoot_description")}>
<div className="bg-white max-w-xl overflow-hidden shadow rounded-sm">
<div className="px-4 py-5 sm:p-6">
Here is an overview of your day on{" "}
{t("overview_of_day")}{" "}
<input
type="date"
className="inline border-none h-8 p-0"
@ -66,34 +68,33 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
setSelectedDate(dayjs(e.target.value));
}}
/>
<small className="block text-neutral-400">
Tip: Hover over the bold times for a full timestamp
</small>
<small className="block text-neutral-400">{t("hover_over_bold_times_tip")}</small>
<div className="mt-4 space-y-4">
<div className="bg-black overflow-hidden rounded-sm">
<div className="px-4 sm:px-6 py-2 text-white">
Your day starts at {convertMinsToHrsMins(user.startTime)}
{t("your_day_starts_at")} {convertMinsToHrsMins(user.startTime)}
</div>
</div>
{availability.map((slot) => (
<div key={slot.start} className="bg-neutral-100 overflow-hidden rounded-sm">
<div className="px-4 py-5 sm:p-6 text-black">
Your calendar shows you as busy between{" "}
{t("calendar_shows_busy_between")}{" "}
<span className="font-medium text-neutral-800" title={slot.start}>
{dayjs(slot.start).format("HH:mm")}
</span>{" "}
and{" "}
{t("and")}{" "}
<span className="font-medium text-neutral-800" title={slot.end}>
{dayjs(slot.end).format("HH:mm")}
</span>{" "}
on {dayjs(slot.start).format("D MMMM YYYY")}
{t("on")} {dayjs(slot.start).format("D")}{" "}
{t(dayjs(slot.start).format("MMMM").toLowerCase())} {dayjs(slot.start).format("YYYY")}
</div>
</div>
))}
{availability.length === 0 && <Loader />}
<div className="bg-black overflow-hidden rounded-sm">
<div className="px-4 sm:px-6 py-2 text-white">
Your day ends at {convertMinsToHrsMins(user.endTime)}
{t("your_day_ends_at")} {convertMinsToHrsMins(user.endTime)}
</div>
</div>
</div>
@ -106,6 +107,8 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session?.user?.id) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
@ -124,6 +127,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!user) return { redirect: { permanent: false, destination: "/auth/login" } };
return {
props: { session, user },
props: {
session,
user,
...(await serverSideTranslations(locale, ["common"])),
},
};
};

View file

@ -2,6 +2,7 @@ import { CalendarIcon } from "@heroicons/react/outline";
import { useRouter } from "next/router";
import { QueryCell } from "@lib/QueryCell";
import { useLocale } from "@lib/hooks/useLocale";
import { inferQueryInput, trpc } from "@lib/trpc";
import BookingsShell from "@components/BookingsShell";
@ -11,19 +12,21 @@ import BookingListItem from "@components/booking/BookingListItem";
type BookingListingStatus = inferQueryInput<"viewer.bookings">["status"];
const descriptionByStatus: Record<BookingListingStatus, string> = {
upcoming: "As soon as someone books a time with you it will show up here.",
past: "Your past bookings will show up here.",
cancelled: "Your cancelled bookings will show up here.",
};
export default function Bookings() {
const { t } = useLocale();
const descriptionByStatus: Record<BookingListingStatus, string> = {
upcoming: t("upcoming_bookings"),
past: t("past_bookings"),
cancelled: t("cancelled_bookings"),
};
const router = useRouter();
const status = router.query?.status as BookingListingStatus;
const query = trpc.useQuery(["viewer.bookings", { status }]);
return (
<Shell heading="Bookings" subtitle="See upcoming and past events booked through your event type links.">
<Shell heading={t("bookings")} subtitle={t("bookings_description")}>
<BookingsShell>
<div className="-mx-4 sm:mx-auto flex flex-col">
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
@ -44,8 +47,11 @@ export default function Bookings() {
empty={() => (
<EmptyScreen
Icon={CalendarIcon}
headline={`No ${status} bookings, yet`}
description={`You have no ${status} bookings. ${descriptionByStatus[status]}`}
headline={t("no_status_bookings_yet", { status: status })}
description={t("no_status_bookings_yet_description", {
status: status,
description: descriptionByStatus[status],
})}
/>
)}
/>

View file

@ -1,13 +1,17 @@
import { XIcon } from "@heroicons/react/outline";
import { ArrowRightIcon } from "@heroicons/react/solid";
import { useLocale } from "@lib/hooks/useLocale";
import { HeadSeo } from "@components/seo/head-seo";
import Button from "@components/ui/Button";
export default function NoMeetingFound() {
const { t } = useLocale();
return (
<div>
<HeadSeo title={`No meeting found`} description={`No Meeting found`} />
<HeadSeo title={t("no_meeting_found")} description={t("no_meeting_found")} />
<main className="max-w-3xl mx-auto my-24">
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
@ -26,19 +30,17 @@ export default function NoMeetingFound() {
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline">
No Meeting Found
{t("no_meeting_found")}
</h3>
</div>
<div className="mt-2">
<p className="text-sm text-center text-gray-500">
This meeting does not exist. Contact the meeting owner for an updated link.
</p>
<p className="text-sm text-center text-gray-500">{t("no_meeting_found_description")}</p>
</div>
</div>
<div className="mt-5 text-center sm:mt-6">
<div className="mt-5">
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRightIcon}>
Go back home
{t("go_back_home")}
</Button>
</div>
</div>

View file

@ -2,9 +2,12 @@ import { CalendarIcon, XIcon } from "@heroicons/react/solid";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { getSession } from "next-auth/client";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useRouter } from "next/router";
import { useState } from "react";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
@ -14,6 +17,7 @@ import { Button } from "@components/ui/Button";
dayjs.extend(utc);
export default function Type(props) {
const { t } = useLocale();
// Get router variables
const router = useRouter();
const { uid } = router.query;
@ -21,7 +25,7 @@ export default function Type(props) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [is24h, setIs24h] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(props.booking ? null : "This booking was already cancelled");
const [error, setError] = useState(props.booking ? null : t("booking_already_cancelled"));
const telemetry = useTelemetry();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -52,15 +56,15 @@ export default function Type(props) {
);
} else {
setLoading(false);
setError("An error with status code " + res.status + " occurred. Please try again later.");
setError(`${t("error_with_status_code_occured", { status: res.status })} ${t("please_try_again")}`);
}
};
return (
<div>
<HeadSeo
title={`Cancel ${props.booking && props.booking.title} | ${props.profile.name}`}
description={`Cancel ${props.booking && props.booking.title} | ${props.profile.name}`}
title={`${t("cancel")} ${props.booking && props.booking.title} | ${props.profile.name}`}
description={`${t("cancel")} ${props.booking && props.booking.title} | ${props.profile.name}`}
/>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed inset-0 z-50 overflow-y-auto">
@ -95,14 +99,12 @@ export default function Type(props) {
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline">
{props.cancellationAllowed
? "Really cancel your booking?"
: "You cannot cancel this booking"}
? t("really_cancel_booking")
: t("cannot_cancel_booking")}
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">
{props.cancellationAllowed
? "Instead, you could also reschedule it."
: "The event is in the past"}
{props.cancellationAllowed ? t("reschedule_instead") : t("event_is_in_the_past")}
</p>
</div>
<div className="py-4 mt-4 border-t border-b">
@ -125,9 +127,9 @@ export default function Type(props) {
data-testid="cancel"
onClick={cancellationHandler}
loading={loading}>
Cancel
{t("cancel")}
</Button>
<Button onClick={() => router.push("/reschedule/" + uid)}>Reschedule</Button>
<Button onClick={() => router.push("/reschedule/" + uid)}>{t("reschedule")}</Button>
</div>
)}
</>
@ -143,6 +145,7 @@ export default function Type(props) {
export async function getServerSideProps(context) {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const booking = await prisma.booking.findUnique({
where: {
uid: context.query.uid,
@ -199,6 +202,7 @@ export async function getServerSideProps(context) {
booking: bookingObj,
cancellationAllowed:
(!!session?.user && session.user.id == booking.user?.id) || booking.startTime >= new Date(),
...(await serverSideTranslations(locale, ["common"])),
},
};
}

View file

@ -3,17 +3,23 @@ import { ArrowRightIcon } from "@heroicons/react/solid";
import { useSession } from "next-auth/client";
import { useRouter } from "next/router";
import { useLocale } from "@lib/hooks/useLocale";
import { HeadSeo } from "@components/seo/head-seo";
import Button from "@components/ui/Button";
export default function CancelSuccess() {
const { t } = useLocale();
// Get router variables
const router = useRouter();
const { title, name, eventPage } = router.query;
const [session, loading] = useSession();
return (
<div>
<HeadSeo title={`Cancelled ${title} | ${name}`} description={`Cancelled ${title} | ${name}`} />
<HeadSeo
title={`${t("cancelled")} ${title} | ${name}`}
description={`${t("cancelled")} ${title} | ${name}`}
/>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
@ -32,11 +38,11 @@ export default function CancelSuccess() {
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
Cancellation successful
{t("cancellation_successful")}
</h3>
{!loading && !session.user && (
<div className="mt-2">
<p className="text-sm text-gray-500">Feel free to pick another event anytime.</p>
<p className="text-sm text-gray-500">{t("free_to_pick_another_event_type")}</p>
</div>
)}
</div>
@ -46,7 +52,7 @@ export default function CancelSuccess() {
{!loading && !session.user && <Button href={eventPage}>Pick another</Button>}
{!loading && session.user && (
<Button data-testid="back-to-bookings" href="/bookings" EndIcon={ArrowRightIcon}>
Back to bookings
{t("back_to_bookings")}
</Button>
)}
</div>

View file

@ -1,4 +1,45 @@
{
"back_to_bookings": "Back to bookings",
"free_to_pick_another_event_type": "Feel free to pick another event anytime.",
"cancelled": "Cancelled",
"cancellation_successful": "Cancellation successful",
"really_cancel_booking": "Really cancel your booking?",
"cannot_cancel_booking": "You cannot cancel this booking",
"reschedule_instead": "Instead, you could also reschedule it.",
"event_is_in_the_past": "The event is in the past",
"error_with_status_code_occured": "An error with status code {{status}} occurred.",
"booking_already_cancelled": "This booking was already cancelled",
"go_back_home": "Go back home",
"no_meeting_found": "No Meeting Found",
"no_meeting_found_description": "This meeting does not exist. Contact the meeting owner for an updated link.",
"no_status_bookings_yet": "No {{status}} bookings, yet",
"no_status_bookings_yet_description": "You have no {{status}} bookings. {{description}}",
"bookings": "Bookings",
"bookings_description": "See upcoming and past events booked through your event type links.",
"upcoming_bookings": "As soon as someone books a time with you it will show up here.",
"past_bookings": "Your past bookings will show up here.",
"cancelled_bookings": "Your cancelled bookings will show up here.",
"on": "on",
"and": "and",
"calendar_shows_busy_between": "Your calendar shows you as busy between",
"troubleshoot": "Troubleshoot",
"troubleshoot_description": "Understand why certain times are available and others are blocked.",
"overview_of_day": "Here is an overview of your day on",
"hover_over_bold_times_tip": "Tip: Hover over the bold times for a full timestamp",
"start_time": "Start time",
"end_time": "End time",
"buffer": "Buffer",
"your_day_starts_at": "Your day starts at",
"your_day_ends_at": "Your day ends at",
"launch_troubleshooter": "Launch troubleshooter",
"troubleshoot_availability": "Troubleshoot your availability to explore why your times are showing as they are.",
"change_available_times": "Change available times",
"change_your_available_times": "Change your available times",
"change_start_end": "Change the start and end times of your day",
"change_start_end_buffer": "Set the start and end time of your day and a minimum buffer between your meetings.",
"current_start_date": "Currently, your day is set to start at",
"start_end_changed_successfully": "The start and end times for your day have been changed successfully.",
"and_end_at": "and end at",
"light": "Light",
"dark": "Dark",
"automatically_adjust_theme": "Automatically adjust theme based on invitee preferences",
@ -50,6 +91,7 @@
"password_has_been_changed": "Your password has been successfully changed.",
"error_changing_password": "Error changing password",
"something_went_wrong": "Something went wrong",
"something_doesnt_look_right": "Something doesn't look right?",
"please_try_again": "Please try again",
"super_secure_new_password": "Your super secure new password",
"new_password": "New Password",
@ -158,7 +200,7 @@
"collective": "Collective",
"collective_description": "Schedule meetings when all selected team members are available.",
"duration": "Duration",
"minutes": "minutes",
"minutes": "Minutes",
"round_robin": "Round Robin",
"round_robin_description": "Cycle meetings between multiple team members.",
"url": "URL",
@ -210,7 +252,7 @@
"billing": "Billing",
"manage_your_billing_info": "Manage your billing information and cancel your subscription.",
"availability": "Availability",
"configure_times_available_bookings": "Configure times when you are available for bookings.",
"configure_availability": "Configure times when you are available for bookings.",
"change_weekly_schedule": "Change your weekly schedule",
"logo": "Logo",
"error": "Error",